diff --git a/frontend/src/views/Docs.jsx b/frontend/src/views/Docs.jsx
index e8c4b8b0..58c2983f 100644
--- a/frontend/src/views/Docs.jsx
+++ b/frontend/src/views/Docs.jsx
@@ -1,14 +1,12 @@
-import React, {useState, useEffect} from 'react';
+import React, {useState,} from 'react';
-import Divider from '@material-ui/core/Divider';
+import { useTheme } from '@material-ui/core/styles';
import ReactMarkdown from 'react-markdown';
import {BrowserView, MobileView} from "react-device-detect";
-import Button from '@material-ui/core/Button';
-import Menu from '@material-ui/core/Menu';
-import MenuItem from '@material-ui/core/MenuItem';
-
import {Link} from 'react-router-dom';
+import {Divider, Button, Menu, MenuItem, Typography, Paper, List} from '@material-ui/core';
+
const Body = {
maxWidth: '1000px',
minWidth: '768px',
@@ -20,26 +18,21 @@ const Body = {
};
const dividerColor = "rgb(225, 228, 232)"
-
-const SideBar = {
- maxWidth: 250,
- flex: "1",
- position: "fixed",
-}
-
const hrefStyle = {
color: "rgba(255, 255, 255, 0.40)",
textDecoration: "none"
}
const Docs = (props) => {
- const { isLoaded, globalUrl, inputColor } = props;
+ const { globalUrl, selectedDoc, serverside, isMobile, } = props;
+ const theme = useTheme();
const [data, setData] = useState("");
const [firstrequest, setFirstrequest] = useState(true);
const [list, setList] = useState([]);
- const [listLoaded, setListLoaded] = useState(false);
+ const [, setListLoaded] = useState(false);
const [anchorEl, setAnchorEl] = React.useState(null);
+ const [baseUrl, setBaseUrl] = React.useState(serverside === true ? "" : window.location.href)
function handleClick(event) {
setAnchorEl(event.currentTarget);
@@ -49,77 +42,21 @@ const Docs = (props) => {
setAnchorEl(null);
}
- useEffect(() => {
- if (firstrequest) {
- setFirstrequest(false)
- fetchDocList()
- fetchDocs(props.match.params.key)
- return
- }
+ const SidebarPaperStyle = {
+ backgroundColor: theme.palette.surfaceColor,
+ overflowX: "hidden",
+ position: "relative",
+ padding: 30,
+ paddingTop: 15,
+ borderRadius: 5,
+ }
- // Continue this, and find the h2 with the data in it lol
- if (window.location.hash.length > 0) {
- var parent = document.getElementById("markdown_wrapper")
- if (parent !== null) {
- var elements = parent.getElementsByTagName('h2')
-
- const name = window.location.hash.slice(1, window.location.hash.lenth).toLowerCase().split("%20").join(" ").split("_").join(" ").split("-").join(" ")
-
- console.log(name)
- var found = false
- for (var key in elements) {
- const element = elements[key]
- if (element.innerHTML === undefined) {
- continue
- }
-
- // Fix location..
- if (element.innerHTML.toLowerCase() === name) {
- element.scrollIntoView({behavior: "smooth"})
- found = true
- //element.scrollTo({
- // top: element.offsetTop-100,
- // behavior: "smooth"
- //})
- }
- }
-
- // H#
- if (!found) {
- var elements = parent.getElementsByTagName('h3')
- console.log(name)
- var found = false
- for (var key in elements) {
- const element = elements[key]
- if (element.innerHTML === undefined) {
- continue
- }
-
- // Fix location..
- if (element.innerHTML.toLowerCase() === name) {
- element.scrollIntoView({behavior: "smooth"})
- found = true
- //element.scrollTo({
- // top: element.offsetTop-100,
- // behavior: "smooth"
- //})
- }
- }
- }
- }
- //console.log(element)
-
- //console.log("NAME: ", name)
- //console.log(document.body.innerHTML)
- // parent = document.getElementById(parent);
-
- //var descendants = parent.getElementsByTagName(tagname);
-
- // this.scrollDiv.current.scrollIntoView({ behavior: 'smooth' });
-
- //$(".parent").find("h2:contains('Statistics')").parent();
- }
- })
+ const SideBar = {
+ maxWidth: 250,
+ flex: "1",
+ position: "fixed",
+ marginTop: 35,
+ }
const fetchDocList = () => {
fetch(globalUrl+"/api/v1/docs", {
@@ -143,16 +80,17 @@ const Docs = (props) => {
const fetchDocs = (docId) => {
fetch(globalUrl+"/api/v1/docs/"+docId, {
- method: 'GET',
+ method: 'GET',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
- })
+ })
.then((response) => response.json())
- .then((responseJson) => {
+ .then((responseJson) => {
if (responseJson.success) {
setData(responseJson.reason)
+ document.title = "Shuffle "+docId+" documentation"
} else {
setData("# Error\nThis page doesn't exist.")
}
@@ -160,13 +98,99 @@ const Docs = (props) => {
.catch(error => {});
}
+ if (firstrequest) {
+ setFirstrequest(false)
+
+ if (selectedDoc !== undefined) {
+ setData(selectedDoc.reason)
+ setList(selectedDoc.list)
+ setListLoaded(true)
+ } else {
+ fetchDocList()
+ fetchDocs(props.match.params.key)
+ }
+ }
+
+ // Handles search-based changes that origin from outside this file
+ if (serverside !== true && window.location.href !== baseUrl) {
+ setBaseUrl(window.location.href)
+ fetchDocs(props.match.params.key)
+ }
+
+ const parseElementScroll = () => {
+ var parent = document.getElementById("markdown_wrapper_outer")
+ if (parent !== null) {
+ //console.log("IN PARENT")
+ var elements = parent.getElementsByTagName('h2')
+
+ const name = window.location.hash.slice(1, window.location.hash.lenth).toLowerCase().split("%20").join(" ").split("_").join(" ").split("-").join(" ")
+
+ //console.log(name)
+ var found = false
+ for (var key in elements) {
+ const element = elements[key]
+ if (element.innerHTML === undefined) {
+ continue
+ }
+
+ // Fix location..
+ if (element.innerHTML.toLowerCase() === name) {
+ element.scrollIntoView({behavior: "smooth"})
+ found = true
+ //element.scrollTo({
+ // top: element.offsetTop-100,
+ // behavior: "smooth"
+ //})
+ }
+ }
+
+ // H#
+ if (!found) {
+ elements = parent.getElementsByTagName('h3')
+ console.log(name)
+ found = false
+ for (key in elements) {
+ const element = elements[key]
+ if (element.innerHTML === undefined) {
+ continue
+ }
+
+ // Fix location..
+ if (element.innerHTML.toLowerCase() === name) {
+ element.scrollIntoView({behavior: "smooth"})
+ found = true
+ //element.scrollTo({
+ // top: element.offsetTop-100,
+ // behavior: "smooth"
+ //})
+ }
+ }
+ }
+ }
+ //console.log(element)
+
+ //console.log("NAME: ", name)
+ //console.log(document.body.innerHTML)
+ // parent = document.getElementById(parent);
+
+ //var descendants = parent.getElementsByTagName(tagname);
+
+ // this.scrollDiv.current.scrollIntoView({ behavior: 'smooth' });
+
+ //$(".parent").find("h2:contains('Statistics')").parent();
+ }
+
+ if (serverside !== true && window.location.hash.length > 0) {
+ parseElementScroll()
+ }
+
const markdownStyle = {
color: "rgba(255, 255, 255, 0.65)",
flex: "1",
- maxWidth: 750,
+ maxWidth: isMobile ? "100%" : 750,
overflow: "hidden",
paddingBottom: 200,
- marginLeft: 250,
+ marginLeft: isMobile ? 0 : 275,
}
function OuterLink(props) {
@@ -182,7 +206,7 @@ const Docs = (props) => {
function CodeHandler(props) {
return (
-
+
{props.value}
@@ -193,10 +217,10 @@ const Docs = (props) => {
function Heading(props) {
const element = React.createElement(`h${props.level}`, {style: {marginTop: 40}}, props.children)
return (
-
- {props.level !== 1 ? : null}
+
+ {props.level !== 1 ? : null}
{element}
-
+
)
}
//React.createElement("p", {style: {color: "red", backgroundColor: "blue"}}, this.props.paragraph)
@@ -213,26 +237,28 @@ const Docs = (props) => {
const postDataBrowser =
-
- {list.map((item, index) => {
- const path = "/docs/"+item
- const newname = item.charAt(0).toUpperCase()+item.substring(1).split("_").join(" ").split("-").join(" ")
- return (
- -
- {fetchDocs(item)}}>
-
{newname}
-
-
- )
- })}
-
+
+
+ {list.map((item, index) => {
+ const path = "/docs/"+item
+ const newname = item.charAt(0).toUpperCase()+item.substring(1).split("_").join(" ").split("-").join(" ")
+ return (
+
+ {fetchDocs(item)}}>
+ {newname}
+
+
+ )
+ })}
+
+
-
+
{
const mobileStyle = {
color: "white",
- marginLeft: "15px",
- marginRight: "15px",
- paddingBottom: "50px",
+ marginLeft: 25,
+ marginRight: 25,
+ paddingBottom: 50,
backgroundColor: "inherit",
+ display: "flex",
+ flexDirection: "column",
}
const postDataMobile =
-
-
-
+
+
-
: null}
+ {isCloud ? null :
setLoadWorkflowsModalOpen(true)}>
+ }
const WorkflowView = () => {
@@ -1203,59 +1550,105 @@ const Workflows = (props) => {
-
-
Workflows
+
+
Workflows
-
+
+
+ {/*
+
+
+
+
+
+
{workflows.length}
+
ACTIVE WORKFLOWS
+
+
+
+
+
+
+
+
{workflows.length}
+
AVAILABE WORKFLOWS
+
+
+
+
+
+
+
+
{workflows.length}
+
NOTIFICATIONS
+
+
+
+
+ */}
+
+ {/*
+ chipRenderer={({ value, isFocused, isDisabled, handleClick, handleRequestDelete }, key) => {
+ console.log("VALUE: ", value)
+
+ return (
+
+ {value}
+
+ )
+ }}
+ */}
+
+
+
+ {
+ addFilter(chip)
+ }}
+ onDelete={(chip, index) => {
+ removeFilter(index)
+ }}
+ />
+
+
{workflowButtons}
-
-
-
- {workflows.map((data, index) => {
- return (
+
+ {view === "grid" && (
+
+ {filteredWorkflows.map((data, index) => {
+ return (
- )
- })}
-
-
-
-
-
-
Executions: {selectedWorkflow.name}
-
-
- {
- alert.info("Refreshing executions");
- getWorkflowExecution(selectedWorkflow.id)
- }}>
-
-
-
-
-
-
-
-
-
-
-
-
-
Execution Timeline
-
-
- Collapse results
}
- control={
{setCollapseJson(!collapseJson)}} />}
- />
-
-
-
-
-
-
+ )
+ })}
+
+ )}
+
+ {view === "list" && (
+
+ )}
+
+
)
@@ -1348,7 +1741,7 @@ const Workflows = (props) => {
style={{backgroundColor: inputColor}}
variant="outlined"
margin="normal"
- value={downloadUrl}
+ defaultValue={userdata.active_org.defaults.workflow_download_repo !== undefined && userdata.active_org.defaults.workflow_download_repo.length > 0 ? userdata.active_org.defaults.workflow_download_repo : downloadUrl}
InputProps={{
style:{
color: "white",
@@ -1359,7 +1752,7 @@ const Workflows = (props) => {
onChange={e => setDownloadUrl(e.target.value)}
placeholder="https://github.com/frikky/shuffle-apps"
fullWidth
- />
+ />
Branch (default value is "master"):
@@ -1367,7 +1760,7 @@ const Workflows = (props) => {
style={{backgroundColor: inputColor}}
variant="outlined"
margin="normal"
- value={downloadBranch}
+ defaultValue={userdata.active_org.defaults.workflow_download_branch !== undefined && userdata.active_org.defaults.workflow_download_branch.length > 0 ? userdata.active_org.defaults.workflow_download_branch : downloadBranch}
InputProps={{
style:{
color: "white",
@@ -1432,7 +1825,9 @@ const Workflows = (props) => {
const loadedCheck = isLoaded && isLoggedIn && workflowDone ?
-
+
1366 ? 1366 : 1200, margin: "auto", padding: 20 }} onDrop={uploadFile}>
+
+
{modalView}
{deleteModal}
{workflowDownloadModalOpen}
diff --git a/functions/extensions/aws-lambda/deploy.sh b/functions/extensions/aws-lambda/deploy.sh
new file mode 100644
index 00000000..a3187a27
--- /dev/null
+++ b/functions/extensions/aws-lambda/deploy.sh
@@ -0,0 +1,9 @@
+GOOS=linux go build main.go
+zip function.zip main
+
+aws lambda update-function-code \
+ --function-name shuffler-forwarder \
+ --runtime go1.* \
+ --zip-file fileb://function.zip \
+ --handler main \
+ --role arn:aws:iam::123456789012:role/execution_role
diff --git a/functions/extensions/aws-lambda/main.go b/functions/extensions/aws-lambda/main.go
new file mode 100644
index 00000000..c78467d6
--- /dev/null
+++ b/functions/extensions/aws-lambda/main.go
@@ -0,0 +1,66 @@
+package main
+
+import (
+ "context"
+ //"encoding/json"
+ //"fmt"
+ "github.com/aws/aws-lambda-go/lambda"
+ "net/http"
+)
+
+type LambdaPayload struct {
+ RequestContext struct {
+ Elb struct {
+ TargetGroupArn string `json:"targetGroupArn"`
+ } `json:"elb"`
+ } `json:"requestContext"`
+ HTTPMethod string `json:"httpMethod"`
+ Path string `json:"path"`
+ Headers map[string]string `json:"headers"`
+ QueryStringParameters map[string]string `json:"queryStringParameters"`
+ Body string `json:"body"`
+ IsBase64Encoded bool `json:"isBase64Encoded"`
+}
+
+type LambdaResponse struct {
+ IsBase64Encoded bool `json:"isBase64Encoded"`
+ StatusCode int `json:"statusCode"`
+ StatusDescription string `json:"statusDescription"`
+ Headers struct {
+ SetCookie string `json:"Set-cookie"`
+ ContentType string `json:"Content-Type"`
+ } `json:"headers"`
+ Body string `json:"body"`
+}
+
+func lambda_handler(ctx context.Context, payload LambdaPayload) (LambdaResponse, error) {
+ response := &LambdaResponse{}
+ response.Headers.ContentType = "text/html"
+ response.StatusCode = http.StatusBadRequest
+ response.StatusDescription = http.StatusText(http.StatusBadRequest)
+ if payload.HTTPMethod == http.MethodGet && payload.Path == "/myfavoritecar" {
+ res := "TEST"
+ //car := &Car{}
+ //car.Model = "Corvette"
+ //car.Color = "Red"
+ //car.Year = 1999
+ //res, err := json.Marshal(car)
+ //if err != nil {
+ // fmt.Println(err)
+ // response.StatusCode = http.StatusInternalServerError
+ // response.StatusDescription = http.StatusText(http.StatusInternalServerError)
+ // return *response, err
+ //}
+ response.Headers.ContentType = "application/json"
+ response.Body = string(res)
+ response.StatusCode = http.StatusOK
+ response.StatusDescription = http.StatusText(http.StatusOK)
+ return *response, nil
+ } else {
+ return *response, nil
+ }
+}
+
+func main() {
+ lambda.Start(lambda_handler)
+}
diff --git a/functions/extensions/cortex-responders/Shuffle/shuffle.json b/functions/extensions/cortex-responders/Shuffle/shuffle.json
new file mode 100644
index 00000000..ef2610dd
--- /dev/null
+++ b/functions/extensions/cortex-responders/Shuffle/shuffle.json
@@ -0,0 +1,35 @@
+{
+ "name": "Shuffle",
+ "version": "1.0",
+ "author": "@frikkylikeme",
+ "url": "https://github.com/frikky/shuffle",
+ "license": "AGPL-V3",
+ "description": "Execute a workflow in Shuffle",
+ "dataTypeList": ["thehive:case", "thehive:alert"],
+ "command": "Shuffle/shuffle.py",
+ "baseConfig": "Shuffle",
+ "configurationItems": [
+ {
+ "name": "url",
+ "description": "The URL to your shuffle instance",
+ "type": "string",
+ "multi": false,
+ "required": true,
+ "defaultValue": "https://shuffler.io"
+ },
+ {
+ "name": "api_key",
+ "description": "The API key to your Shuffle user",
+ "type": "string",
+ "multi": false,
+ "required": true
+ },
+ {
+ "name": "workflow_id",
+ "description": "The ID of the workflow to execute",
+ "type": "string",
+ "multi": false,
+ "required": true
+ }
+ ]
+}
diff --git a/functions/extensions/cortex-responders/Shuffle/shuffle.py b/functions/extensions/cortex-responders/Shuffle/shuffle.py
new file mode 100644
index 00000000..0816ca53
--- /dev/null
+++ b/functions/extensions/cortex-responders/Shuffle/shuffle.py
@@ -0,0 +1,28 @@
+
+#!/usr/bin/env python
+# encoding: utf-8
+
+from cortexutils.responder import Responder
+import requests
+
+class Shuffle(Responder):
+ def __init__(self):
+ Responder.__init__(self)
+ self.api_key = self.get_param("config.api_key", "")
+ self.url = self.get_param("config.url", "")
+ self.workflow_id = self.get_param("config.workflow_id", "")
+
+ def run(self):
+ Responder.run(self)
+
+ parsed_url = "%s/api/v1/workflows/%s/execute" % (self.url, self.workflow_id)
+ headers = {
+ "Authorization": "Bearer %s" % self.api_key
+ }
+ requests.post(parsed_url, headers=headers)
+
+ self.report({'message': 'message sent'})
+
+if __name__ == '__main__':
+ Shuffle().run()
+
diff --git a/functions/extensions/wazuh/custom-shuffle b/functions/extensions/wazuh/custom-shuffle
new file mode 100644
index 00000000..bd540414
--- /dev/null
+++ b/functions/extensions/wazuh/custom-shuffle
@@ -0,0 +1,36 @@
+#!/bin/sh
+# Created by Shuffle, AS.
.
+
+WPYTHON_BIN="framework/python/bin/python3"
+
+SCRIPT_PATH_NAME="$0"
+
+DIR_NAME="$(cd $(dirname ${SCRIPT_PATH_NAME}); pwd -P)"
+SCRIPT_NAME="$(basename ${SCRIPT_PATH_NAME})"
+
+case ${DIR_NAME} in
+ */active-response/bin | */wodles*)
+ if [ -z "${WAZUH_PATH}" ]; then
+ WAZUH_PATH="$(cd ${DIR_NAME}/../..; pwd)"
+ fi
+
+ PYTHON_SCRIPT="${DIR_NAME}/${SCRIPT_NAME}.py"
+ ;;
+ */bin)
+ if [ -z "${WAZUH_PATH}" ]; then
+ WAZUH_PATH="$(cd ${DIR_NAME}/..; pwd)"
+ fi
+
+ PYTHON_SCRIPT="${WAZUH_PATH}/framework/scripts/${SCRIPT_NAME}.py"
+ ;;
+ */integrations)
+ if [ -z "${WAZUH_PATH}" ]; then
+ WAZUH_PATH="$(cd ${DIR_NAME}/..; pwd)"
+ fi
+
+ PYTHON_SCRIPT="${DIR_NAME}/${SCRIPT_NAME}.py"
+ ;;
+esac
+
+
+${WAZUH_PATH}/${WPYTHON_BIN} ${PYTHON_SCRIPT} "$@"
diff --git a/functions/extensions/wazuh/custom-shuffle.py b/functions/extensions/wazuh/custom-shuffle.py
new file mode 100644
index 00000000..06fa4c7d
--- /dev/null
+++ b/functions/extensions/wazuh/custom-shuffle.py
@@ -0,0 +1,177 @@
+#!/usr/bin/env python
+# Created by Shuffle, AS. .
+# Based on the Slack integration using Webhooks
+
+import json
+import sys
+import time
+import os
+
+try:
+ import requests
+ from requests.auth import HTTPBasicAuth
+except Exception as e:
+ print("No module 'requests' found. Install: pip install requests")
+ sys.exit(1)
+
+# ADD THIS TO ossec.conf configuration:
+#
+# custom-shuffle
+# http://:3001/api/v1/hooks/
+# 3
+# json
+#
+
+# Global vars
+
+debug_enabled = False
+pwd = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+json_alert = {}
+now = time.strftime("%a %b %d %H:%M:%S %Z %Y")
+
+# Set paths
+log_file = '{0}/logs/integrations.log'.format(pwd)
+
+
+def main(args):
+ debug("# Starting")
+
+ # Read args
+ alert_file_location = args[1]
+ webhook = args[3]
+
+ debug("# Webhook")
+ debug(webhook)
+
+ debug("# File location")
+ debug(alert_file_location)
+
+ # Load alert. Parse JSON object.
+ with open(alert_file_location) as alert_file:
+ json_alert = json.load(alert_file)
+ debug("# Processing alert")
+ debug(json_alert)
+
+ debug("# Generating message")
+ msg = generate_msg(json_alert)
+ if isinstance(msg, str):
+ if len(msg) == 0:
+ return
+ debug(msg)
+
+ debug("# Sending message")
+ send_msg(msg, webhook)
+
+
+def debug(msg):
+ if debug_enabled:
+ msg = "{0}: {1}\n".format(now, msg)
+ print(msg)
+ f = open(log_file, "a")
+ f.write(msg)
+ f.close()
+
+# Skips container kills to stop self-recursion
+def filter_msg(alert):
+ # These are things that recursively happen because Shuffle starts Docker containers
+ # Docker integration rules: https://github.com/wazuh/wazuh-ruleset/blob/ae36745db1d3f312db0392f5925c2f2b0ec009a9/rules/0560-docker_integration_rules.xml
+ skip = ["87924", "87900", "87901", "87902", "87903", "87904", "86001", "86002", "86003", "87932", "80710", "87929", "87928",]
+ if alert["rule"]["id"] in skip:
+ return False
+
+ #try:
+ # if "docker" in alert["rule"]["description"].lower() and "
+ #msg['text'] = alert.get('full_log')
+ #except:
+ # pass
+ #msg['title'] = alert['rule']['description'] if 'description' in alert['rule'] else "N/A"
+
+ return True
+
+def generate_msg(alert):
+ if not filter_msg(alert):
+ print("Skipping rule %s" % alert["rule"]["id"])
+ return ""
+
+ level = alert['rule']['level']
+
+ if (level <= 4):
+ color = "good"
+ elif (level >= 5 and level <= 7):
+ color = "warning"
+ else:
+ color = "danger"
+
+ msg = {}
+ msg['color'] = color
+ msg['pretext'] = "WAZUH Alert"
+ msg['title'] = alert['rule']['description'] if 'description' in alert['rule'] else "N/A"
+ msg['text'] = alert.get('full_log')
+ msg['rule_id'] = alert["rule"]["id"]
+ msg['timestamp'] = alert["timestamp"]
+ msg['id'] = alert['id']
+ msg["all_fields"] = alert
+
+ #msg['fields'] = []
+ # msg['fields'].append({
+ # "title": "Agent",
+ # "value": "({0}) - {1}".format(
+ # alert['agent']['id'],
+ # alert['agent']['name']
+ # ),
+ # })
+ #if 'agentless' in alert:
+ # msg['fields'].append({
+ # "title": "Agentless Host",
+ # "value": alert['agentless']['host'],
+ # })
+
+ #msg['fields'].append({"title": "Location", "value": alert['location']})
+ #msg['fields'].append({
+ # "title": "Rule ID",
+ # "value": "{0} _(Level {1})_".format(alert['rule']['id'], level),
+ #})
+
+ #attach = {'attachments': [msg]}
+
+ return json.dumps(msg)
+
+
+def send_msg(msg, url):
+ headers = {'content-type': 'application/json', 'Accept-Charset': 'UTF-8'}
+ res = requests.post(url, data=msg, headers=headers)
+ debug(res)
+
+
+if __name__ == "__main__":
+ try:
+ # Read arguments
+ bad_arguments = False
+ if len(sys.argv) >= 4:
+ msg = '{0} {1} {2} {3} {4}'.format(
+ now,
+ sys.argv[1],
+ sys.argv[2],
+ sys.argv[3],
+ sys.argv[4] if len(sys.argv) > 4 else '',
+ )
+ debug_enabled = (len(sys.argv) > 4 and sys.argv[4] == 'debug')
+ else:
+ msg = '{0} Wrong arguments'.format(now)
+ bad_arguments = True
+
+ # Logging the call
+ f = open(log_file, 'a')
+ f.write(msg + '\n')
+ f.close()
+
+ if bad_arguments:
+ debug("# Exiting: Bad arguments.")
+ sys.exit(1)
+
+ # Main function
+ main(sys.argv)
+
+ except Exception as e:
+ debug(str(e))
+ raise
diff --git a/functions/extensions/wazuh/ossec.conf b/functions/extensions/wazuh/ossec.conf
new file mode 100644
index 00000000..5b55f5d3
--- /dev/null
+++ b/functions/extensions/wazuh/ossec.conf
@@ -0,0 +1,5 @@
+
+ custom-shuffle
+ http://:3001/api/v1/hooks/webhook_
+ json
+
diff --git a/functions/onprem/orborus/Dockerfile b/functions/onprem/orborus/Dockerfile
index 3db4c027..6479c09e 100644
--- a/functions/onprem/orborus/Dockerfile
+++ b/functions/onprem/orborus/Dockerfile
@@ -1,11 +1,17 @@
-from golang as builder
+FROM golang:1.16.0-buster as builder
RUN mkdir /app
WORKDIR /app
-RUN go get github.com/docker/docker/api/types github.com/docker/docker/api/types/container github.com/docker/docker/client
COPY orborus.go /app/orborus.go
RUN go mod init orborus
+RUN go get github.com/docker/docker/api/types && \
+ go get github.com/docker/docker/api/types/container && \
+ go get github.com/docker/docker/client && \
+ go get github.com/mackerelio/go-osstat/cpu && \
+ go get github.com/mackerelio/go-osstat/memory && \
+ go get github.com/satori/go.uuid && \
+ go get github.com/frikky/shuffle-shared
RUN go build
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o orborus .
diff --git a/functions/onprem/orborus/build.sh b/functions/onprem/orborus/build.sh
index 14499755..c6df2439 100644
--- a/functions/onprem/orborus/build.sh
+++ b/functions/onprem/orborus/build.sh
@@ -1,11 +1,11 @@
NAME=shuffle-orborus
-VERSION=0.8.0
+VERSION=0.8.71
echo "Running docker build with $NAME:$VERSION"
#docker rmi frikky/shuffle:$NAME --force
docker build . -t frikky/shuffle:$NAME -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t frikky/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
#docker push frikky/$NAME:$VERSION
-#docker push frikky/shuffle:$NAME
# docker push docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION
+docker push frikky/shuffle:$NAME
docker push ghcr.io/frikky/$NAME:$VERSION
diff --git a/functions/onprem/orborus/go.mod b/functions/onprem/orborus/go.mod
new file mode 100644
index 00000000..2991cf7f
--- /dev/null
+++ b/functions/onprem/orborus/go.mod
@@ -0,0 +1,20 @@
+module orborus
+
+go 1.13
+
+require (
+ github.com/Microsoft/go-winio v0.4.16 // indirect
+ github.com/containerd/containerd v1.4.3 // indirect
+ github.com/docker/distribution v2.7.1+incompatible // indirect
+ github.com/docker/docker v20.10.1+incompatible
+ github.com/docker/go-connections v0.4.0 // indirect
+ github.com/docker/go-units v0.4.0 // indirect
+ github.com/frikky/shuffle-shared v0.0.23 // indirect
+ github.com/gogo/protobuf v1.3.1 // indirect
+ github.com/mackerelio/go-osstat v0.1.0
+ github.com/opencontainers/go-digest v1.0.0 // indirect
+ github.com/opencontainers/image-spec v1.0.1 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/satori/go.uuid v1.2.0
+ github.com/sirupsen/logrus v1.7.0 // indirect
+)
diff --git a/functions/onprem/orborus/go.sum b/functions/onprem/orborus/go.sum
new file mode 100644
index 00000000..d3ea7e29
--- /dev/null
+++ b/functions/onprem/orborus/go.sum
@@ -0,0 +1,502 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
+cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
+cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
+cloud.google.com/go v0.66.0/go.mod h1:dgqGAjKCDxyhGTtC9dAREQGUJpkceNm1yt590Qno0Ko=
+cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
+cloud.google.com/go v0.75.0 h1:XgtDnVJRCPEUG21gjFiRPz4zI1Mjg16R+NYQjfmU4XY=
+cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
+cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
+cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
+cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
+cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/datastore v1.4.0 h1:CFDJm15RpYXeEblQ0TMDUrYtqmBmbAWTy536nA8JIc8=
+cloud.google.com/go/datastore v1.4.0/go.mod h1:d18825/a9bICdAIJy2EkHs9joU4RlIZ1t6l8WDdbdY0=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
+cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+cloud.google.com/go/storage v1.12.0 h1:4y3gHptW1EHVtcPAVE0eBBlFuGqEejTTG3KdIE0lUX4=
+cloud.google.com/go/storage v1.12.0/go.mod h1:fFLk2dp2oAhDz8QFKwqrjdJvxSp/W2g7nillojlL5Ho=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk=
+github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/containerd/containerd v1.4.3 h1:ijQT13JedHSHrQGWFcGEwzcNKrAGIiZ+jSD5QQG07SY=
+github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
+github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
+github.com/docker/docker v20.10.1+incompatible h1:u0HIBLwOJdemyBdTCkoBX34u3lb5KyBo0rQE3a5Yg+E=
+github.com/docker/docker v20.10.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
+github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
+github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
+github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
+github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/frikky/kin-openapi v0.38.0 h1:V7ttwIJS8Vks4KL+mZVj1ZSqhIcQtgaG8akeqXEQgsE=
+github.com/frikky/kin-openapi v0.38.0/go.mod h1:Fr28TtCHL4K0kIqtqui8HWxN1LG5uAh3z/tDfFyiA1s=
+github.com/frikky/shuffle-shared v0.0.12 h1:+0EIfThmK47Po+LogPYZR4XjbS4Ds19WNMFu2YUSjhw=
+github.com/frikky/shuffle-shared v0.0.12/go.mod h1:SEY432/xs4oBkOUnGwxiYKCZWZLRaheGInhAoW7N8ww=
+github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
+github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
+github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/mackerelio/go-osstat v0.1.0 h1:e57QHeHob8kKJ5FhcXGdzx5O6Ktuc5RHMDIkeqhgkFA=
+github.com/mackerelio/go-osstat v0.1.0/go.mod h1:1K3NeYLhMHPvzUu+ePYXtoB58wkaRpxZsGClZBJyIFw=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
+github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
+github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
+github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
+go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
+golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
+golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI=
+golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY=
+golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210113160501-8b1d76fa0423 h1:/hEknzWkMPCjTo7StMHRrBRa8YBbXuBWfck8680k3RE=
+golang.org/x/oauth2 v0.0.0-20210113160501-8b1d76fa0423/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190410235845-0ad05ae3009d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3 h1:kzM6+9dur93BcC2kVlYl34cHU+TYZLanmpSJHVMmL64=
+golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
+golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200828161849-5deb26317202/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
+golang.org/x/tools v0.0.0-20200915173823-2db8f0ff891c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
+golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
+golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963 h1:K+NlvTLy0oONtRtkl1jRD9xIhnItbG2PiE7YOdjPb+k=
+golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
+google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
+google.golang.org/api v0.31.0/go.mod h1:CL+9IBCa2WWU6gRuBWaKqGWLFFwbEUXkfeMkHLQWYWo=
+google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
+google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
+google.golang.org/api v0.36.0 h1:l2Nfbl2GPXdWorv+dT2XfinX2jOOw4zv1VhLstx+6rE=
+google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
+google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200831141814-d751682dd103/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200914193844-75d14daec038/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200921151605-7abf4a1a14d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20210113195801-ae06605f4595 h1:x7nk+/4+SvuTDI4wnzQUlhvi+DTpyfncXBo3QWTFs7U=
+google.golang.org/genproto v0.0.0-20210113195801-ae06605f4595/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
+google.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI=
+google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
+google.golang.org/grpc v1.34.1 h1:ugq+9++ZQPFzM2pKUMCIK8gj9M0pFyuUWO9Q8kwEDQw=
+google.golang.org/grpc v1.34.1/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
+google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go
index 78984425..a3fd36dd 100644
--- a/functions/onprem/orborus/orborus.go
+++ b/functions/onprem/orborus/orborus.go
@@ -5,6 +5,8 @@ package main
*/
import (
+ "github.com/frikky/shuffle-shared"
+
"bytes"
"context"
"encoding/json"
@@ -21,17 +23,22 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
+ //"github.com/docker/docker/api/types/filters"
dockerclient "github.com/docker/docker/client"
"github.com/satori/go.uuid"
//network "github.com/docker/docker/api/types/network"
//natting "github.com/docker/go-connections/nat"
+ "github.com/mackerelio/go-osstat/cpu"
+ "github.com/mackerelio/go-osstat/memory"
)
// Starts jobs in bulk, so this could be increased
var sleepTime = 3
+var maxConcurrency = 50
// Timeout if something rashes
var workerTimeoutEnv = os.Getenv("SHUFFLE_ORBORUS_EXECUTION_TIMEOUT")
+var concurrencyEnv = os.Getenv("SHUFFLE_ORBORUS_EXECUTION_CONCURRENCY")
var appSdkVersion = os.Getenv("SHUFFLE_APP_SDK_VERSION")
var workerVersion = os.Getenv("SHUFFLE_WORKER_VERSION")
@@ -47,19 +54,8 @@ var baseUrl = os.Getenv("BASE_URL")
var environment = os.Getenv("ENVIRONMENT_NAME")
var dockerApiVersion = os.Getenv("DOCKER_API_VERSION")
var runningMode = strings.ToLower(os.Getenv("RUNNING_MODE"))
-
-type ExecutionRequestWrapper struct {
- Data []ExecutionRequest `json:"data"`
-}
-
-type ExecutionRequest struct {
- ExecutionId string `json:"execution_id"`
- ExecutionArgument string `json:"execution_argument"`
- WorkflowId string `json:"workflow_id"`
- Authorization string `json:"authorization"`
- Status string `json:"status"`
- Type string `json:"type"`
-}
+var cleanupEnv = strings.ToLower(os.Getenv("CLEANUP"))
+var executionIds = []string{}
var dockercli *dockerclient.Client
var containerId string
@@ -99,17 +95,31 @@ func getThisContainerId() {
}
if fCol != "" {
- cmd := fmt.Sprintf("cat /proc/self/cgroup | grep memory | tail -1 | cut -d/ -f%s", fCol)
+ cmd := fmt.Sprintf("cat /proc/self/cgroup | grep memory | tail -1 | cut -d/ -f%s | grep -o -E '[0-9A-z]{64}'", fCol)
out, err := exec.Command("bash", "-c", cmd).Output()
if err == nil {
containerId = strings.TrimSpace(string(out))
+
+ // cgroup error. Hardcoding this.
+ // https://github.com/moby/moby/issues/7015
+ //log.Printf("Checking if %s is in %s", ".scope", string(out))
+ if strings.Contains(string(out), ".scope") {
+ containerId = "shuffle-orborus"
+ //docker-76c537e9a4b7c7233011f5d70e6b7f2d600b6413ac58a96519b8dca7a3f7117a.scope
+ }
} else {
- log.Printf("Failed getting container ID: %s", err)
+ if fCol == "0" {
+ containerId = "shuffle-orborus"
+ log.Printf("[WARNING] Failed getting container ID: %s", err)
+ }
}
}
+
+ log.Printf(`[INFO] Started with containerId "%s"`, containerId)
}
// Deploys the internal worker whenever something happens
+// https://docs.docker.com/engine/api/sdk/examples/
func deployWorker(image string, identifier string, env []string) {
// Binds is the actual "-v" volume.
hostConfig := &container.HostConfig{
@@ -124,23 +134,28 @@ func deployWorker(image string, identifier string, env []string) {
// form container id and use it as network source if it's not empty
if containerId != "" {
- log.Printf("[INFO] Found container ID %s", containerId)
+ //log.Printf("[INFO] Found container ID %s", containerId)
hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", containerId))
} else {
//log.Printf("[INFO] Empty self container id, continue without NetworkMode")
}
+ if cleanupEnv == "true" {
+ hostConfig.AutoRemove = true
+ }
+
config := &container.Config{
Image: image,
Env: env,
}
- log.Printf("Identifier: %s", identifier)
+ //log.Printf("[INFO] Identifier: %s", identifier)
cont, err := dockercli.ContainerCreate(
context.Background(),
config,
hostConfig,
nil,
+ nil,
identifier,
)
@@ -148,12 +163,13 @@ func deployWorker(image string, identifier string, env []string) {
if strings.Contains(fmt.Sprintf("%s", err), "Conflict. The container name ") {
uuid := uuid.NewV4()
identifier = fmt.Sprintf("%s-%s", identifier, uuid)
- log.Printf("2 - Identifier: %s", identifier)
+ log.Printf("[INFO] 2 - Identifier: %s", identifier)
cont, err = dockercli.ContainerCreate(
context.Background(),
config,
hostConfig,
nil,
+ nil,
identifier,
)
@@ -167,7 +183,8 @@ func deployWorker(image string, identifier string, env []string) {
}
}
- err = dockercli.ContainerStart(context.Background(), cont.ID, types.ContainerStartOptions{})
+ containerStartOptions := types.ContainerStartOptions{}
+ 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
@@ -227,11 +244,11 @@ func initializeImages() {
ctx := context.Background()
if appSdkVersion == "" {
- appSdkVersion = "0.8.0"
+ appSdkVersion = "0.8.60"
log.Printf("[WARNING] SHUFFLE_APP_SDK_VERSION not defined. Defaulting to %s", appSdkVersion)
}
if workerVersion == "" {
- workerVersion = "0.8.0"
+ workerVersion = "0.8.70"
log.Printf("[WARNING] SHUFFLE_WORKER_VERSION not defined. Defaulting to %s", workerVersion)
}
@@ -248,9 +265,7 @@ func initializeImages() {
// check whether they are the same first
images := []string{
- //fmt.Sprintf("%s/%s:app_sdk%s", baseimageregistry, baseimagename, baseimagetagsuffix),
- //fmt.Sprintf("%s/%s:worker%s", baseimageregistry, baseimagename, baseimagetagsuffix),
-
+ fmt.Sprintf("frikky/shuffle:app_sdk"),
fmt.Sprintf("%s/%s/shuffle-app_sdk:%s", baseimageregistry, baseimagename, appSdkVersion),
fmt.Sprintf("%s/%s/shuffle-worker:%s", baseimageregistry, baseimagename, workerVersion),
// fmt.Sprintf("docker.io/%s:app_sdk", baseimagename),
@@ -275,6 +290,40 @@ func initializeImages() {
}
}
+// Will be used for checking if there's enough to deploy based on a threshold
+// E.g. having maximum CPU and maxmimum RAM
+// Does this work containerized?
+func getStats() {
+ fmt.Printf("\n")
+
+ memory, err := memory.Get()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "%s\n", err)
+ return
+ }
+
+ before, err := cpu.Get()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "%s\n", err)
+ return
+ }
+ time.Sleep(time.Duration(250) * time.Millisecond)
+ after, err := cpu.Get()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "%s\n", err)
+ return
+ }
+ total := float64(after.Total - before.Total)
+
+ fmt.Printf("[INFO] memory total: %d bytes\n", memory.Total)
+ fmt.Printf("[INFO] memory used: %d bytes\n", memory.Used)
+ fmt.Printf("[INFO] cpu used : %f%%\n", float64(after.User-before.User)/total*100)
+ fmt.Printf("[INFO] cpu system: %f%%\n", float64(after.System-before.System)/total*100)
+ fmt.Printf("[INFO] cpu idle : %f%%\n", float64(after.Idle-before.Idle)/total*100)
+
+ fmt.Printf("\n")
+}
+
// Initial loop etc
func main() {
log.Println("[INFO] Setting up execution environment")
@@ -302,7 +351,19 @@ func main() {
log.Printf("[INFO] Cleanup process running every %d seconds", workerTimeout)
}
- go zombiecheck(workerTimeout)
+ if concurrencyEnv != "" {
+ //var concurrencyEnv = os.Getenv("SHUFFLE_ORBORUS_EXECUTION_CONCURRENCY")
+ tmpInt, err := strconv.Atoi(concurrencyEnv)
+ if err == nil {
+ maxConcurrency = tmpInt
+ log.Printf("[INFO] Max workflow execution concurrency set to %d", maxConcurrency)
+ } else {
+ log.Printf("[WARNING] Env SHUFFLE_ORBORUS_EXECUTION_CONCURRENCY must be a number, not %s. Defaulted to %d", workerTimeoutEnv, maxConcurrency)
+ }
+ }
+
+ ctx := context.Background()
+ go zombiecheck(ctx, workerTimeout)
log.Printf("[INFO] Running towards %s with Org %s", baseUrl, orgId)
httpProxy := os.Getenv("HTTP_PROXY")
@@ -337,6 +398,8 @@ func main() {
},
}
+ //getStats()
+
if (len(httpProxy) > 0 || len(httpsProxy) > 0) && baseUrl != "http://shuffle-backend:5001" {
client = &http.Client{}
} else {
@@ -367,13 +430,15 @@ func main() {
hasStarted := false
for {
//log.Printf("Prerequest")
+ //go getStats()
newresp, err := client.Do(req)
+ executionCount := getRunningWorkers(ctx, workerTimeout)
//log.Printf("Postrequest")
if err != nil {
log.Printf("[WARNING] Failed making request: %s", err)
zombiecounter += 1
if zombiecounter*sleepTime > workerTimeout {
- go zombiecheck(workerTimeout)
+ go zombiecheck(ctx, workerTimeout)
zombiecounter = 0
}
time.Sleep(time.Duration(sleepTime) * time.Second)
@@ -394,21 +459,21 @@ func main() {
log.Printf("[ERROR] Failed reading body: %s", err)
zombiecounter += 1
if zombiecounter*sleepTime > workerTimeout {
- go zombiecheck(workerTimeout)
+ go zombiecheck(ctx, workerTimeout)
zombiecounter = 0
}
time.Sleep(time.Duration(sleepTime) * time.Second)
continue
}
- var executionRequests ExecutionRequestWrapper
+ var executionRequests shuffle.ExecutionRequestWrapper
err = json.Unmarshal(body, &executionRequests)
if err != nil {
log.Printf("[WARNING] Failed executionrequest in queue unmarshaling: %s", err)
sleepTime = 10
zombiecounter += 1
if zombiecounter*sleepTime > workerTimeout {
- go zombiecheck(workerTimeout)
+ go zombiecheck(ctx, workerTimeout)
zombiecounter = 0
}
time.Sleep(time.Duration(sleepTime) * time.Second)
@@ -423,15 +488,31 @@ func main() {
if len(executionRequests.Data) == 0 {
zombiecounter += 1
if zombiecounter*sleepTime > workerTimeout {
- go zombiecheck(workerTimeout)
+ go zombiecheck(ctx, workerTimeout)
zombiecounter = 0
}
time.Sleep(time.Duration(sleepTime) * time.Second)
continue
}
+ // Anything below here verifies concurrency virification
+ if executionCount >= maxConcurrency {
+ if zombiecounter*sleepTime > workerTimeout {
+ go zombiecheck(ctx, workerTimeout)
+ zombiecounter = 0
+ }
+ time.Sleep(time.Duration(sleepTime) * time.Second)
+ continue
+ }
+
+ allowed := maxConcurrency - executionCount
+ if len(executionRequests.Data) > allowed {
+ log.Printf("[WARNING] Throttle - Cutting down requests from %d to %d (MAX: %d, CUR: %d)", len(executionRequests.Data), allowed, maxConcurrency, executionCount)
+ executionRequests.Data = executionRequests.Data[0:allowed]
+ }
+
// New, abortable version. Should check executionid and remove everything else
- var toBeRemoved ExecutionRequestWrapper
+ var toBeRemoved shuffle.ExecutionRequestWrapper
for _, execution := range executionRequests.Data {
if len(execution.ExecutionArgument) > 0 {
log.Printf("[INFO] Argument: %#v", execution.ExecutionArgument)
@@ -445,6 +526,24 @@ func main() {
if execution.Status == "ABORT" || execution.Status == "FAILED" {
log.Printf("[INFO] Executionstatus issue: ", execution.Status)
}
+
+ found := false
+ for _, executionId := range executionIds {
+ if execution.ExecutionId == executionId {
+ found = true
+ break
+ }
+ }
+
+ // Doesn't work because of USER INPUT
+ if found {
+ log.Printf("[INFO] Skipping duplicate %s", execution.ExecutionId)
+ continue
+ } else {
+ //log.Printf("[INFO] Adding to be ran %s", execution.ExecutionId)
+ executionIds = append(executionIds, execution.ExecutionId)
+ }
+
// Now, how do I execute this one?
// FIXME - if error, check the status of the running one. If it's bad, send data back.
containerName := fmt.Sprintf("worker-%s", execution.ExecutionId)
@@ -453,6 +552,7 @@ func main() {
fmt.Sprintf("EXECUTIONID=%s", execution.ExecutionId),
fmt.Sprintf("ENVIRONMENT_NAME=%s", environment),
fmt.Sprintf("BASE_URL=%s", baseUrl),
+ fmt.Sprintf("CLEANUP=%s", cleanupEnv),
}
if strings.ToLower(os.Getenv("SHUFFLE_PASS_WORKER_PROXY")) != "false" {
@@ -466,7 +566,7 @@ func main() {
go deployWorker(workerImage, containerName, env)
- log.Printf("[INFO] %s is deployed and to be removed from queue.", execution.ExecutionId)
+ log.Printf("[INFO] ExecutionID %s was deployed and to be removed from queue.", execution.ExecutionId)
zombiecounter += 1
toBeRemoved.Data = append(toBeRemoved.Data, execution)
}
@@ -528,25 +628,22 @@ func main() {
}
}
-// FIXME - add this to remove exited workers
-// Should it check what happened to the execution? idk
-func zombiecheck(workerTimeout int) error {
- log.Println("[INFO] Looking for old containers")
- ctx := context.Background()
-
+// Is this ok to do with Docker? idk :)
+func getRunningWorkers(ctx context.Context, workerTimeout int) int {
containers, err := dockercli.ContainerList(ctx, types.ContainerListOptions{
All: true,
})
+ //Filters: filters.Args{
+ // map[string][]string{"ancestor": {":"}},
+ //},
if err != nil {
- log.Printf("[ERROR] Failed creating Containerlist: %s", err)
- return err
+ log.Printf("[ERROR] Error getting containers: %s", err)
+ return maxConcurrency
}
- containerNames := map[string]string{}
-
- stopContainers := []string{}
- removeContainers := []string{}
+ currenttime := time.Now().Unix()
+ counter := 0
for _, container := range containers {
// Skip random containers. Only handle things related to Shuffle.
if !strings.Contains(container.Image, baseimagename) {
@@ -568,14 +665,76 @@ func zombiecheck(workerTimeout int) error {
for _, name := range container.Names {
// FIXME - add name_version_uid_uid regex check as well
- if strings.HasPrefix(name, "/shuffle") {
+ if !strings.HasPrefix(name, "/worker") {
continue
}
- log.Printf("[INFO] NAME: %s", name)
+ //log.Printf("Time: %d - %d", currenttime-container.Created, int64(workerTimeout))
+ if container.State == "running" && currenttime-container.Created < int64(workerTimeout) {
+ counter += 1
+ break
+ }
+ }
+ }
+
+ return counter
+}
+
+// FIXME - add this to remove exited workers
+// Should it check what happened to the execution? idk
+func zombiecheck(ctx context.Context, workerTimeout int) error {
+ executionIds = []string{}
+ log.Println("[INFO] Looking for old containers (zombies)")
+ containers, err := dockercli.ContainerList(ctx, types.ContainerListOptions{
+ All: true,
+ })
+
+ //log.Printf("Len: %d", len(containers))
+
+ if err != nil {
+ log.Printf("[ERROR] Failed creating Containerlist: %s", err)
+ return err
+ }
+
+ containerNames := map[string]string{}
+
+ stopContainers := []string{}
+ removeContainers := []string{}
+ log.Printf("[INFO] Baseimage: %s, Workertimeout: %d", baseimagename, int64(workerTimeout))
+ baseString := `/bin/sh -c 'python app.py --log-level DEBUG'`
+ for _, container := range containers {
+ // Skip random containers. Only handle things related to Shuffle.
+ if !strings.Contains(container.Image, baseimagename) && container.Command != baseString && container.Command != "./worker" {
+ shuffleFound := false
+ for _, item := range container.Labels {
+ if item == "shuffle" {
+ shuffleFound = true
+ break
+ }
+ }
+
+ // Check image name
+ if !shuffleFound {
+ log.Printf("Skipping: %s, %s", container.Labels, container.Image)
+ continue
+ }
+ //} else {
+ // log.Printf("NAME: %s", container.Image)
+ } else {
+ //log.Printf("Img: %s", container.Image)
+ //log.Printf("Names: %s", container.Names)
+ }
+
+ for _, name := range container.Names {
+ // FIXME - add name_version_uid_uid regex check as well
+ if strings.HasPrefix(name, "/shuffle") && !strings.HasPrefix(name, "/shuffle-subflow") {
+ continue
+ }
+
+ currenttime := time.Now().Unix()
+ //log.Printf("[INFO] (%s) NAME: %s. TIME: %d", container.State, name, currenttime-container.Created)
// Need to check time here too because a container can be removed the same instant as its created
- currenttime := time.Now().Unix()
if container.State != "running" && currenttime-container.Created > int64(workerTimeout) {
removeContainers = append(removeContainers, container.ID)
containerNames[container.ID] = name
@@ -591,9 +750,10 @@ func zombiecheck(workerTimeout int) error {
}
// FIXME - add killing of apps with same execution ID too
+ log.Printf("[INFO] Should STOP %d containers.", len(stopContainers))
for _, containername := range stopContainers {
log.Printf("[INFO] Stopping and removing container %s", containerNames[containername])
- go dockercli.ContainerStop(ctx, containername, nil)
+ dockercli.ContainerStop(ctx, containername, nil)
removeContainers = append(removeContainers, containername)
}
@@ -602,8 +762,9 @@ func zombiecheck(workerTimeout int) error {
Force: true,
}
+ log.Printf("[INFO] Should REMOVE %d containers.", len(removeContainers))
for _, containername := range removeContainers {
- go dockercli.ContainerRemove(ctx, containername, removeOptions)
+ dockercli.ContainerRemove(ctx, containername, removeOptions)
}
return nil
diff --git a/functions/onprem/worker/Dockerfile b/functions/onprem/worker/Dockerfile
index c381e73b..826ef4d3 100644
--- a/functions/onprem/worker/Dockerfile
+++ b/functions/onprem/worker/Dockerfile
@@ -1,19 +1,28 @@
-from golang as builder
+FROM golang:1.16.0-buster as builder
WORKDIR /app
+RUN go get github.com/docker/docker/api/types github.com/docker/docker/api/types/container github.com/docker/docker/client
-RUN go get -u github.com/docker/docker/api/types
-RUN go get -u github.com/docker/docker/api/types/container
-RUN go get -u github.com/docker/docker/client
-
+#RUN go env -w GO111MODULE=auto
COPY worker.go /app/worker.go
+RUN go mod init worker
+RUN go get github.com/docker/docker/api/types && \
+ go get github.com/docker/docker/api/types/container && \
+ go get github.com/docker/docker/client && \
+ go get github.com/gorilla/mux && \
+ go get github.com/patrickmn/go-cache && \
+ go get github.com/frikky/shuffle-shared && \
+ go get github.com/satori/go.uuid
+
+RUN go build
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker .
+## ALPINE IMAGE
FROM alpine:3.12
ENV SHUFFLE_BASE_IMAGE_REGISTRY=docker.io
ENV SHUFFLE_BASE_IMAGE_NAME=frikky/shuffle
-ENV SHUFFLE_BASE_IMAGE_TAG_SUFFIX=0.6.0
+ENV SHUFFLE_BASE_IMAGE_TAG_SUFFIX=0.8.70
RUN apk add --no-cache bash
COPY --from=builder /app/ /
diff --git a/functions/onprem/worker/build.sh b/functions/onprem/worker/build.sh
index 363df000..10f2c70c 100644
--- a/functions/onprem/worker/build.sh
+++ b/functions/onprem/worker/build.sh
@@ -1,12 +1,14 @@
NAME=shuffle-worker
-VERSION=0.8.0
+VERSION=0.8.71
echo "Running docker build with $NAME:$VERSION"
#CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker.bin .
-docker build . -t frikky/shuffle:$NAME -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
+docker build . -t frikky/shuffle:$NAME -t frikky/shuffle:$NAME_$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION
# Push both for now..
#docker push frikky/$NAME:$VERSION
-docker push frikky/shuffle:$NAME
+#docker push frikky/shuffle:$NAME_$VERSION
#docker push docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION
+#docker tag frikky/shuffle:0.8.51 ghcr.io/frikky/shuffle-worker:0.8.5
+#docker tag frikky/shuffle:$NAME ghcr.io/frikky/shuffle-worker:0.8.52
docker push ghcr.io/frikky/$NAME:$VERSION
diff --git a/functions/onprem/worker/go.mod b/functions/onprem/worker/go.mod
new file mode 100644
index 00000000..44d51077
--- /dev/null
+++ b/functions/onprem/worker/go.mod
@@ -0,0 +1,18 @@
+module worker
+
+go 1.15
+
+require (
+ github.com/containerd/containerd v1.4.4 // indirect
+ github.com/docker/distribution v2.7.1+incompatible // indirect
+ github.com/docker/docker v20.10.5+incompatible // indirect
+ github.com/docker/go-connections v0.4.0 // indirect
+ github.com/docker/go-units v0.4.0 // indirect
+ github.com/frikky/shuffle-shared v0.0.20 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/gorilla/mux v1.8.0 // indirect
+ github.com/opencontainers/go-digest v1.0.0 // indirect
+ github.com/opencontainers/image-spec v1.0.1 // indirect
+ github.com/pkg/errors v0.9.1 // indirect
+ github.com/sirupsen/logrus v1.8.1 // indirect
+)
diff --git a/functions/onprem/worker/worker.go b/functions/onprem/worker/worker.go
index dbaea55f..cc981636 100644
--- a/functions/onprem/worker/worker.go
+++ b/functions/onprem/worker/worker.go
@@ -1,15 +1,19 @@
package main
import (
+ "github.com/frikky/shuffle-shared"
+
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
- //"io"
+ "io"
"io/ioutil"
"log"
+ "net"
"net/http"
+ "net/url"
"os"
"os/exec"
"strings"
@@ -17,23 +21,57 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
+ //"github.com/docker/docker/api/types/filters"
+ "github.com/docker/docker/api/types/mount"
dockerclient "github.com/docker/docker/client"
+ "github.com/satori/go.uuid"
+
+ "github.com/gorilla/mux"
+ "github.com/patrickmn/go-cache"
)
+// This is getting out of hand :)
var environment = os.Getenv("ENVIRONMENT_NAME")
var baseUrl = os.Getenv("BASE_URL")
+var appCallbackUrl = os.Getenv("BASE_URL")
+var cleanupEnv = strings.ToLower(os.Getenv("CLEANUP"))
var baseimagename = "frikky/shuffle"
+var registryName = "registry.hub.docker.com"
+var fallbackName = "shuffle-orborus"
var sleepTime = 2
+var requestCache *cache.Cache
+var topClient *http.Client
+var data string
+var requestsSent = 0
+
+var environments []string
+var parents map[string][]string
+var children map[string][]string
+var visited []string
+var executed []string
+var nextActions []string
+var containerIds []string
+var extra int
+var startAction string
var containerId string
// form container id of current running container
func getThisContainerId() string {
+ if len(containerId) > 0 {
+ return containerId
+ }
+
id := ""
- cmd := fmt.Sprintf("cat /proc/self/cgroup | grep memory | tail -1 | cut -d/ -f3")
+ cmd := fmt.Sprintf("cat /proc/self/cgroup | grep memory | tail -1 | cut -d/ -f3 | grep -o -E '[0-9A-z]{64}'")
out, err := exec.Command("bash", "-c", cmd).Output()
if err == nil {
id = strings.TrimSpace(string(out))
+
+ //log.Printf("Checking if %s is in %s", ".scope", string(out))
+ if strings.Contains(string(out), ".scope") {
+ id = fallbackName
+ }
}
return id
@@ -42,353 +80,68 @@ func getThisContainerId() string {
func init() {
containerId = getThisContainerId()
if len(containerId) == 0 {
- log.Printf("[ERROR] No container ID found.")
+ log.Printf("[WARNING] No container ID found. Not running containerized? This should only show during testing")
} else {
- log.Printf("[INFO] Found container ID: %s", containerId)
+ log.Printf("[INFO] Found container ID for this worker: %s", containerId)
}
}
-type User struct {
- Username string `datastore:"Username" json:"username"`
- Password string `datastore:"password,noindex" password:"password,omitempty"`
- Session string `datastore:"session,noindex" json:"session"`
- Verified bool `datastore:"verified,noindex" json:"verified"`
- PrivateApps []WorkflowApp `datastore:"privateapps" json:"privateapps":`
- Role string `datastore:"role" json:"role"`
- Roles []string `datastore:"roles" json:"roles"`
- VerificationToken string `datastore:"verification_token" json:"verification_token"`
- ApiKey string `datastore:"apikey" json:"apikey"`
- ResetReference string `datastore:"reset_reference" json:"reset_reference"`
- ResetTimeout int64 `datastore:"reset_timeout,noindex" json:"reset_timeout"`
- Id string `datastore:"id" json:"id"`
- Orgs []string `datastore:"orgs" json:"orgs"`
- CreationTime int64 `datastore:"creation_time" json:"creation_time"`
- Active bool `datastore:"active" json:"active"`
-}
-
-type ExecutionRequest struct {
- ExecutionId string `json:"execution_id"`
- ExecutionArgument string `json:"execution_argument"`
- ExecutionSource string `json:"execution_source"`
- WorkflowId string `json:"workflow_id"`
- Environments []string `json:"environments"`
- Authorization string `json:"authorization"`
- Status string `json:"status"`
- Start string `json:"start"`
- Type string `json:"type"`
-}
-
-type Org struct {
- Name string `json:"name"`
- Org string `json:"org"`
- Users []User `json:"users"`
- Id string `json:"id"`
-}
-
-type AppAuthenticationStorage struct {
- Active bool `json:"active" datastore:"active"`
- Label string `json:"label" datastore:"label"`
- Id string `json:"id" datastore:"id"`
- App WorkflowApp `json:"app" datastore:"app"`
- Fields []AuthenticationStore `json:"fields" datastore:"fields"`
- Usage []AuthenticationUsage `json:"usage" datastore:"usage"`
- WorkflowCount int64 `json:"workflow_count" datastore:"workflow_count"`
- NodeCount int64 `json:"node_count" datastore:"node_count"`
-}
-
-type AuthenticationUsage struct {
- WorkflowId string `json:"workflow_id" datastore:"workflow_id"`
- Nodes []string `json:"nodes" datastore:"nodes"`
-}
-
-// An app inside Shuffle
-type WorkflowApp struct {
- Name string `json:"name" yaml:"name" required:true datastore:"name"`
- IsValid bool `json:"is_valid" yaml:"is_valid" required:true datastore:"is_valid"`
- ID string `json:"id" yaml:"id,omitempty" required:false datastore:"id"`
- Link string `json:"link" yaml:"link" required:false datastore:"link,noindex"`
- AppVersion string `json:"app_version" yaml:"app_version" required:true datastore:"app_version"`
- Generated bool `json:"generated" yaml:"generated" required:false datastore:"generated"`
- Downloaded bool `json:"downloaded" yaml:"downloaded" required:false datastore:"downloaded"`
- Sharing bool `json:"sharing" yaml:"sharing" required:false datastore:"sharing"`
- Verified bool `json:"verified" yaml:"verified" required:false datastore:"verified"`
- Activated bool `json:"activated" yaml:"activated" required:false datastore:"activated"`
- Tested bool `json:"tested" yaml:"tested" required:false datastore:"tested"`
- Owner string `json:"owner" datastore:"owner" yaml:"owner"`
- Hash string `json:"hash" datastore:"hash" yaml:"hash"` // api.yaml+dockerfile+src/app.py for apps
- PrivateID string `json:"private_id" yaml:"private_id" required:false datastore:"private_id"`
- Description string `json:"description" datastore:"description,noindex" required:false yaml:"description"`
- Environment string `json:"environment" datastore:"environment" required:true yaml:"environment"`
- SmallImage string `json:"small_image" datastore:"small_image,noindex" required:false yaml:"small_image"`
- LargeImage string `json:"large_image" datastore:"large_image,noindex" yaml:"large_image" required:false`
- ContactInfo struct {
- Name string `json:"name" datastore:"name" yaml:"name"`
- Url string `json:"url" datastore:"url" yaml:"url"`
- } `json:"contact_info" datastore:"contact_info" yaml:"contact_info" required:false`
- Actions []WorkflowAppAction `json:"actions" yaml:"actions" required:true datastore:"actions"`
- Authentication Authentication `json:"authentication" yaml:"authentication" required:false datastore:"authentication"`
-}
-
-type WorkflowAppActionParameter struct {
- Description string `json:"description" datastore:"description" yaml:"description"`
- ID string `json:"id" datastore:"id" yaml:"id,omitempty"`
- Name string `json:"name" datastore:"name" yaml:"name"`
- Example string `json:"example" datastore:"example" yaml:"example"`
- Value string `json:"value" datastore:"value" yaml:"value,omitempty"`
- Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"`
- ActionField string `json:"action_field" datastore:"action_field" yaml:"actionfield,omitempty"`
- Variant string `json:"variant" datastore:"variant" yaml:"variant,omitempty"`
- Required bool `json:"required" datastore:"required" yaml:"required"`
- Configuration bool `json:"configuration" datastore:"configuration" yaml:"configuration"`
- Schema SchemaDefinition `json:"schema" datastore:"schema" yaml:"schema"`
-}
-
-type SchemaDefinition struct {
- Type string `json:"type" datastore:"type"`
-}
-
-type WorkflowAppAction struct {
- Description string `json:"description" datastore:"description"`
- ID string `json:"id" datastore:"id" yaml:"id,omitempty"`
- Name string `json:"name" datastore:"name"`
- Label string `json:"label" datastore:"label"`
- NodeType string `json:"node_type" datastore:"node_type"`
- Environment string `json:"environment" datastore:"environment"`
- Sharing bool `json:"sharing" datastore:"sharing"`
- PrivateID string `json:"private_id" datastore:"private_id"`
- AppID string `json:"app_id" datastore:"app_id"`
- Authentication []AuthenticationStore `json:"authentication" datastore:"authentication" yaml:"authentication,omitempty"`
- Tested bool `json:"tested" datastore:"tested" yaml:"tested"`
- Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters"`
- ExecutionVariable struct {
- Description string `json:"description" datastore:"description"`
- ID string `json:"id" datastore:"id"`
- Name string `json:"name" datastore:"name"`
- Value string `json:"value" datastore:"value"`
- } `json:"execution_variable" datastore:"execution_variables"`
- Returns struct {
- Description string `json:"description" datastore:"returns" yaml:"description,omitempty"`
- Example string `json:"example" datastore:"example" yaml:"example"`
- ID string `json:"id" datastore:"id" yaml:"id,omitempty"`
- Schema SchemaDefinition `json:"schema" datastore:"schema" yaml:"schema"`
- } `json:"returns" datastore:"returns"`
- AuthenticationId string `json:"authentication_id" datastore:"authentication_id"`
- Example string `json:"example" datastore:"example" yaml:"example"`
- AuthNotRequired bool `json:"auth_not_required" datastore:"auth_not_required" yaml:"auth_not_required"`
-}
-
-// FIXME: Generate a callback authentication ID?
-type WorkflowExecution struct {
- Type string `json:"type" datastore:"type"`
- Status string `json:"status" datastore:"status"`
- Start string `json:"start" datastore:"start"`
- ExecutionArgument string `json:"execution_argument" datastore:"execution_argument"`
- ExecutionId string `json:"execution_id" datastore:"execution_id"`
- ExecutionSource string `json:"execution_source" datastore:"execution_source"`
- WorkflowId string `json:"workflow_id" datastore:"workflow_id"`
- LastNode string `json:"last_node" datastore:"last_node"`
- Authorization string `json:"authorization" datastore:"authorization"`
- Result string `json:"result" datastore:"result,noindex"`
- StartedAt int64 `json:"started_at" datastore:"started_at"`
- CompletedAt int64 `json:"completed_at" datastore:"completed_at"`
- ProjectId string `json:"project_id" datastore:"project_id"`
- Locations []string `json:"locations" datastore:"locations"`
- Workflow Workflow `json:"workflow" datastore:"workflow,noindex"`
- Results []ActionResult `json:"results" datastore:"results,noindex"`
- ExecutionVariables []struct {
- Description string `json:"description" datastore:"description"`
- ID string `json:"id" datastore:"id"`
- Name string `json:"name" datastore:"name"`
- Value string `json:"value" datastore:"value"`
- } `json:"execution_variables,omitempty" datastore:"execution_variables,omitempty"`
-}
-
-// This is for the nodes in a workflow, NOT the app action itself.
-type Action struct {
- AppName string `json:"app_name" datastore:"app_name"`
- AppVersion string `json:"app_version" datastore:"app_version"`
- AppID string `json:"app_id" datastore:"app_id"`
- Errors []string `json:"errors" datastore:"errors"`
- ID string `json:"id" datastore:"id"`
- IsValid bool `json:"is_valid" datastore:"is_valid"`
- IsStartNode bool `json:"isStartNode" datastore:"isStartNode"`
- Sharing bool `json:"sharing" datastore:"sharing"`
- PrivateID string `json:"private_id" datastore:"private_id"`
- Label string `json:"label" datastore:"label"`
- SmallImage string `json:"small_image" datastore:"small_image,noindex" required:false yaml:"small_image"`
- LargeImage string `json:"large_image" datastore:"large_image,noindex" yaml:"large_image" required:false`
- Environment string `json:"environment" datastore:"environment"`
- Name string `json:"name" datastore:"name"`
- Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters,noindex"`
- ExecutionVariable struct {
- Description string `json:"description" datastore:"description"`
- ID string `json:"id" datastore:"id"`
- Name string `json:"name" datastore:"name"`
- Value string `json:"value" datastore:"value"`
- } `json:"execution_variable,omitempty" datastore:"execution_variable,omitempty"`
- Position struct {
- X float64 `json:"x" datastore:"x"`
- Y float64 `json:"y" datastore:"y"`
- } `json:"position"`
- Priority int `json:"priority" datastore:"priority"`
- AuthenticationId string `json:"authentication_id" datastore:"authentication_id"`
- Example string `json:"example" datastore:"example"`
- AuthNotRequired bool `json:"auth_not_required" datastore:"auth_not_required" yaml:"auth_not_required"`
-}
-
-type Trigger struct {
- AppName string `json:"app_name" datastore:"app_name"`
- Description string `json:"description" datastore:"description,noindex"`
- LongDescription string `json:"long_description" datastore:"long_description"`
- Status string `json:"status" datastore:"status"`
- AppVersion string `json:"app_version" datastore:"app_version"`
- Errors []string `json:"errors" datastore:"errors"`
- ID string `json:"id" datastore:"id"`
- IsValid bool `json:"is_valid" datastore:"is_valid"`
- IsStartNode bool `json:"isStartNode" datastore:"isStartNode"`
- Label string `json:"label" datastore:"label"`
- SmallImage string `json:"small_image" datastore:"small_image,noindex" required:false yaml:"small_image"`
- LargeImage string `json:"large_image" datastore:"large_image,noindex" yaml:"large_image" required:false`
- Environment string `json:"environment" datastore:"environment"`
- TriggerType string `json:"trigger_type" datastore:"trigger_type"`
- Name string `json:"name" datastore:"name"`
- Tags []string `json:"tags" datastore:"tags" yaml:"tags"`
- Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters,noindex"`
- Position struct {
- X float64 `json:"x" datastore:"x"`
- Y float64 `json:"y" datastore:"y"`
- } `json:"position"`
- Priority int `json:"priority" datastore:"priority"`
-}
-
-type Branch struct {
- DestinationID string `json:"destination_id" datastore:"destination_id"`
- ID string `json:"id" datastore:"id"`
- SourceID string `json:"source_id" datastore:"source_id"`
- Label string `json:"label" datastore:"label"`
- HasError bool `json:"has_errors" datastore: "has_errors"`
- Conditions []Condition `json:"conditions" datastore: "conditions"`
-}
-
-// Same format for a lot of stuff
-type Condition struct {
- Condition WorkflowAppActionParameter `json:"condition" datastore:"condition"`
- Source WorkflowAppActionParameter `json:"source" datastore:"source"`
- Destination WorkflowAppActionParameter `json:"destination" datastore:"destination"`
-}
-
-type Schedule struct {
- Name string `json:"name" datastore:"name"`
- Frequency string `json:"frequency" datastore:"frequency"`
- ExecutionArgument string `json:"execution_argument" datastore:"execution_argument"`
- Id string `json:"id" datastore:"id"`
-}
-
-type Workflow struct {
- Actions []Action `json:"actions" datastore:"actions,noindex"`
- Branches []Branch `json:"branches" datastore:"branches,noindex"`
- Triggers []Trigger `json:"triggers" datastore:"triggers,noindex"`
- Schedules []Schedule `json:"schedules" datastore:"schedules,noindex"`
- Configuration struct {
- ExitOnError bool `json:"exit_on_error" datastore:"exit_on_error"`
- StartFromTop bool `json:"start_from_top" datastore:"start_from_top"`
- } `json:"configuration,omitempty" datastore:"configuration"`
- Errors []string `json:"errors,omitempty" datastore:"errors"`
- Tags []string `json:"tags,omitempty" datastore:"tags"`
- ID string `json:"id" datastore:"id"`
- IsValid bool `json:"is_valid" datastore:"is_valid"`
- Name string `json:"name" datastore:"name"`
- Description string `json:"description" datastore:"description"`
- Start string `json:"start" datastore:"start"`
- Owner string `json:"owner" datastore:"owner"`
- Sharing string `json:"sharing" datastore:"sharing"`
- Org []Org `json:"org,omitempty" datastore:"org"`
- ExecutingOrg Org `json:"execution_org,omitempty" datastore:"execution_org"`
- WorkflowVariables []struct {
- Description string `json:"description" datastore:"description"`
- ID string `json:"id" datastore:"id"`
- Name string `json:"name" datastore:"name"`
- Value string `json:"value" datastore:"value"`
- } `json:"workflow_variables" datastore:"workflow_variables"`
- ExecutionVariables []struct {
- Description string `json:"description" datastore:"description"`
- ID string `json:"id" datastore:"id"`
- Name string `json:"name" datastore:"name"`
- Value string `json:"value" datastore:"value"`
- } `json:"execution_variables,omitempty" datastore:"execution_variables"`
-}
-
-type ActionResult struct {
- Action Action `json:"action" datastore:"action"`
- ExecutionId string `json:"execution_id" datastore:"execution_id"`
- Authorization string `json:"authorization" datastore:"authorization"`
- Result string `json:"result" datastore:"result,noindex"`
- StartedAt int64 `json:"started_at" datastore:"started_at"`
- CompletedAt int64 `json:"completed_at" datastore:"completed_at"`
- Status string `json:"status" datastore:"status"`
-}
-
-type Authentication struct {
- Required bool `json:"required" datastore:"required" yaml:"required" `
- Parameters []AuthenticationParams `json:"parameters" datastore:"parameters" yaml:"parameters"`
-}
-
-type AuthenticationParams struct {
- Description string `json:"description" datastore:"description" yaml:"description"`
- ID string `json:"id" datastore:"id" yaml:"id"`
- Name string `json:"name" datastore:"name" yaml:"name"`
- Example string `json:"example" datastore:"example" yaml:"example"`
- Value string `json:"value,omitempty" datastore:"value" yaml:"value"`
- Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"`
- Required bool `json:"required" datastore:"required" yaml:"required"`
- In string `json:"in" datastore:"in" yaml:"in"`
- Schema SchemaDefinition `json:"schema" datastore:"schema" yaml:"schema"`
- Scheme string `json:"scheme" datastore:"scheme" yaml:"scheme"` // Deprecated
-}
-
-type AuthenticationStore struct {
- Key string `json:"key" datastore:"key"`
- Value string `json:"value" datastore:"value"`
-}
-
-type ExecutionRequestWrapper struct {
- Data []ExecutionRequest `json:"data"`
-}
-
// removes every container except itself (worker)
-func shutdown(executionId, workflowId string) {
- dockercli, err := dockerclient.NewEnvClient()
- if err != nil {
- log.Printf("[ERROR] Unable to create docker client: %s", err)
- os.Exit(3)
- }
+func shutdown(workflowExecution shuffle.WorkflowExecution, nodeId string, reason string, handleResultSend bool) {
+ log.Printf("[INFO] Shutdown (%s) started with reason %s", workflowExecution.Status, reason)
+ //reason := "Error in execution"
- containerOptions := types.ContainerListOptions{
- All: true,
- }
-
- containers, err := dockercli.ContainerList(context.Background(), containerOptions)
- if err != nil {
- panic(err)
- }
- _ = containers
-
- for _, container := range containers {
- for _, name := range container.Names {
- if strings.Contains(name, executionId) {
- // FIXME - reinstate - not here for debugging
- //err = removeContainer(container.ID)
- //if err != nil {
- // log.Printf("Failed removing %s before shutdown.", name)
- //}
-
- break
- }
+ sleepDuration := 1
+ if handleResultSend && requestsSent < 2 {
+ data, err := json.Marshal(workflowExecution)
+ if err == nil {
+ sendResult(workflowExecution, data)
+ log.Printf("[WARNING] Sent shutdown update")
+ } else {
+ log.Printf("[WARNING] DIDNT send update")
}
+ time.Sleep(time.Duration(sleepDuration) * time.Second)
}
- fullUrl := fmt.Sprintf("%s/api/v1/workflows/%s/executions/%s/abort", baseUrl, workflowId, executionId)
+ // Might not be necessary because of cleanupEnv hostconfig autoremoval
+ if cleanupEnv == "true" && len(containerIds) > 0 {
+ /*
+ ctx := context.Background()
+ dockercli, err := dockerclient.NewEnvClient()
+ if err == nil {
+ log.Printf("[INFO] Cleaning up %d containers", len(containerIds))
+ removeOptions := types.ContainerRemoveOptions{
+ RemoveVolumes: true,
+ Force: true,
+ }
+
+ for _, containername := range containerIds {
+ log.Printf("[INFO] Should stop and and remove container %s (deprecated)", containername)
+ //dockercli.ContainerStop(ctx, containername, nil)
+ //dockercli.ContainerRemove(ctx, containername, removeOptions)
+ //removeContainers = append(removeContainers, containername)
+ }
+ }
+ */
+ } else {
+ log.Printf("[INFO] NOT cleaning up containers. IDS: %d, CLEANUP env: %s", len(containerIds), cleanupEnv)
+ }
+
+ fullUrl := fmt.Sprintf("%s/api/v1/workflows/%s/executions/%s/abort", baseUrl, workflowExecution.Workflow.ID, workflowExecution.ExecutionId)
+
+ path := fmt.Sprintf("?reason=%s", url.QueryEscape(reason))
+ if len(nodeId) > 0 {
+ path += fmt.Sprintf("&node=%s", url.QueryEscape(nodeId))
+ }
+ if len(environment) > 0 {
+ path += fmt.Sprintf("&env=%s", url.QueryEscape(environment))
+ }
+
+ //fmt.Println(url.QueryEscape(query))
+ fullUrl += path
+ log.Printf("[INFO] Abort URL: %s", fullUrl)
+
req, err := http.NewRequest(
"GET",
fullUrl,
@@ -431,34 +184,78 @@ func shutdown(executionId, workflowId string) {
log.Printf("[INFO] Failed abort request: %s", err)
}
- log.Printf("[INFO] Finished shutdown.")
+ log.Printf("[INFO] Finished shutdown (after %d seconds).", sleepDuration)
+ // Allows everything to finish in subprocesses
+ time.Sleep(time.Duration(sleepDuration) * time.Second)
os.Exit(3)
}
// Deploys the internal worker whenever something happens
-func deployApp(cli *dockerclient.Client, image string, identifier string, env []string) error {
+func deployApp(cli *dockerclient.Client, image string, identifier string, env []string, workflowExecution shuffle.WorkflowExecution) error {
// form basic hostConfig
+ ctx := context.Background()
hostConfig := &container.HostConfig{
LogConfig: container.LogConfig{
Type: "json-file",
Config: map[string]string{},
},
+ Resources: container.Resources{
+ CPUShares: 256,
+ CPUPeriod: 10000,
+ },
}
// form container id and use it as network source if it's not empty
+ containerId = getThisContainerId()
if containerId != "" {
hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", containerId))
} else {
log.Printf("[WARNING] Empty self container id, continue without NetworkMode")
}
+ // Removing because log extraction should happen first
+ if cleanupEnv == "true" {
+ hostConfig.AutoRemove = true
+ }
+
+ // FIXME: Add proper foldermounts here
+ //log.Printf("\n\nPRE FOLDERMOUNT\n\n")
+ //volumeBinds := []string{"/tmp/shuffle-mount:/rules"}
+ //volumeBinds := []string{"/tmp/shuffle-mount:/rules"}
+ volumeBinds := []string{}
+ if len(volumeBinds) > 0 {
+ log.Printf("[INFO] Setting up binds for container!")
+ hostConfig.Binds = volumeBinds
+ hostConfig.Mounts = []mount.Mount{}
+ for _, bind := range volumeBinds {
+ if !strings.Contains(bind, ":") || strings.Contains(bind, "..") || strings.HasPrefix(bind, "~") {
+ log.Printf("[WARNING] Bind %s is invalid.", bind)
+ continue
+ }
+
+ log.Printf("[INFO] Appending bind %s", bind)
+ bindSplit := strings.Split(bind, ":")
+ sourceFolder := bindSplit[0]
+ destinationFolder := bindSplit[0]
+ hostConfig.Mounts = append(hostConfig.Mounts, mount.Mount{
+ Type: mount.TypeBind,
+ Source: sourceFolder,
+ Target: destinationFolder,
+ })
+ }
+ } else {
+ log.Printf("[WARNING] No mounted folders")
+ }
+ // hostConfig.Binds = volumeBinds
+ //}
+
config := &container.Config{
Image: image,
Env: env,
}
cont, err := cli.ContainerCreate(
- context.Background(),
+ ctx,
config,
hostConfig,
nil,
@@ -467,12 +264,91 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env []
)
if err != nil {
- log.Printf("Container error: %s", err)
+ log.Printf("[WARNING] Container CREATE error: %s", err)
return err
}
- cli.ContainerStart(context.Background(), cont.ID, types.ContainerStartOptions{})
- log.Printf("[INFO] Container %s is created", cont.ID)
+ err = cli.ContainerStart(ctx, cont.ID, types.ContainerStartOptions{})
+ if err != nil {
+ log.Printf("[ERROR] Failed to start container in environment %s: %s", environment, err)
+ //shutdown(workflowExecution, workflowExecution.Workflow.ID, true)
+ return err
+ }
+
+ log.Printf("[INFO] Container %s was created for %s", cont.ID, identifier)
+
+ // Waiting to see if it exits.. Stupid, but stable(r)
+ if workflowExecution.ExecutionSource != "default" {
+ log.Printf("[INFO] Handling NON-default execution source %s - NOT waiting and validating!", workflowExecution.ExecutionSource)
+ } else if workflowExecution.ExecutionSource == "default" {
+ time.Sleep(2 * time.Second)
+
+ stats, err := cli.ContainerInspect(ctx, cont.ID)
+ if err != nil {
+ log.Printf("[ERROR] Failed getting container stats")
+ } else {
+ //log.Printf("[INFO] Info for container: %#v", stats)
+ //log.Printf("%#v", stats.Config)
+ //log.Printf("%#v", stats.ContainerJSONBase.State)
+ log.Printf("[INFO] EXECUTION STATUS: %s", stats.ContainerJSONBase.State.Status)
+ if stats.ContainerJSONBase.State.Status == "exited" {
+ logOptions := types.ContainerLogsOptions{
+ ShowStdout: true,
+ }
+
+ out, err := cli.ContainerLogs(ctx, cont.ID, logOptions)
+ if err != nil {
+ log.Printf("[INFO] Failed getting logs: %s", err)
+ } else {
+ log.Printf("IN ELSE FOR DEPLOY")
+ buf := new(strings.Builder)
+ io.Copy(buf, out)
+ logs := buf.String()
+ log.Printf("Logs: %s", logs)
+
+ //log.Printf(logs)
+ // check errors
+ /*
+ if strings.Contains(logs, "Error") {
+ log.Printf("ERROR IN %s?", cont.ID)
+ log.Println(logs)
+ //return errors.New(fmt.Sprintf("ERROR FROM CONTAINER %s", cont.ID))
+ } else {
+ log.Printf("NORMAL EXEC OF %s?", cont.ID)
+ }
+ */
+ }
+
+ log.Printf("ERROR IN CONTAINER DEPLOYMENT - ITS EXITED!")
+
+ return errors.New(fmt.Sprintf(`{"success": false, "reason": "Container %s exited prematurely.","debug": "docker logs -f %s"}`, cont.ID, cont.ID))
+ }
+ }
+ }
+
+ /*
+ //log.Printf("%#v", stats.Config.Status)
+ //ContainerJSONtoConfig(cj dockType.ContainerJSON) ContainerConfig {
+ listOptions := types.ContainerListOptions{
+ Filters: filters.Args{
+ map[string][]string{"ancestor": {":"}},
+ },
+ }
+ containers, err := cli.ContainerList(ctx, listOptions)
+ */
+
+ //log.Printf("%#v", cont.Status)
+ //config := ContainerJSONtoConfig(stats)
+ //log.Printf("CONFIG: %#v", config)
+
+ /*
+ logOptions := types.ContainerLogsOptions{
+ ShowStdout: true,
+ }
+
+ */
+
+ containerIds = append(containerIds, cont.ID)
return nil
}
@@ -510,7 +386,7 @@ func removeContainer(containername string) error {
return nil
}
-func runFilter(workflowExecution WorkflowExecution, action Action) {
+func runFilter(workflowExecution shuffle.WorkflowExecution, action shuffle.Action) {
// 1. Get the parameter $.#.id
if action.Label == "filter_cases" && len(action.Parameters) > 0 {
if action.Parameters[0].Variant == "ACTION_RESULT" {
@@ -526,30 +402,732 @@ func runFilter(workflowExecution WorkflowExecution, action Action) {
}
-func handleExecution(client *http.Client, req *http.Request, workflowExecution WorkflowExecution) error {
- // if no onprem runs (shouldn't happen, but extra check), exit
- // if there are some, load the images ASAP for the app
- dockercli, err := dockerclient.NewEnvClient()
- if err != nil {
- log.Printf("Unable to create docker client: %s", err)
- shutdown(workflowExecution.ExecutionId, workflowExecution.Workflow.ID)
+func handleSubworkflowExecution(client *http.Client, workflowExecution shuffle.WorkflowExecution, action shuffle.Trigger, baseAction shuffle.Action) error {
+ apikey := ""
+ workflowId := ""
+ executionArgument := ""
+ for _, parameter := range action.Parameters {
+ log.Printf("Parameter name: %s", parameter.Name)
+ if parameter.Name == "user_apikey" {
+ apikey = parameter.Value
+ } else if parameter.Name == "workflow" {
+ workflowId = parameter.Value
+ } else if parameter.Name == "data" {
+ executionArgument = parameter.Value
+ }
}
- onpremApps := []string{}
- startAction := workflowExecution.Start
+ //handleSubworkflowExecution(workflowExecution, action)
+ status := "SUCCESS"
+ baseResult := `{"success": true}`
+ if len(apikey) == 0 || len(workflowId) == 0 {
+ status = "FAILURE"
+ baseResult = `{"success": false}`
+ } else {
+ log.Printf("Should execute workflow %s with APIKEY %s and data %s", workflowId, apikey, executionArgument)
+ fullUrl := fmt.Sprintf("%s/api/workflows/%s/execute", baseUrl, workflowId)
+ req, err := http.NewRequest(
+ "POST",
+ fullUrl,
+ bytes.NewBuffer([]byte(executionArgument)),
+ )
+
+ if err != nil {
+ log.Printf("Error building test request: %s", err)
+ return err
+ }
+
+ req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", apikey))
+ newresp, err := client.Do(req)
+ if err != nil {
+ log.Printf("Error running test request: %s", err)
+ return err
+ }
+
+ body, err := ioutil.ReadAll(newresp.Body)
+ if err != nil {
+ log.Printf("Failed reading body when waiting: %s", err)
+ return err
+ }
+
+ log.Printf("Execution Result: %s", body)
+ }
+
+ timeNow := time.Now().Unix()
+ //curaction := shuffle.Action{
+ // AppName: baseAction.AppName,
+ // AppVersion: baseAction.AppVersion,
+ // Label: baseAction.Label,
+ // Name: baseAction.Name,
+ // ID: baseAction.ID,
+ //}
+ result := shuffle.ActionResult{
+ Action: baseAction,
+ ExecutionId: workflowExecution.ExecutionId,
+ Authorization: workflowExecution.Authorization,
+ Result: baseResult,
+ StartedAt: timeNow,
+ CompletedAt: 0,
+ Status: status,
+ }
+
+ resultData, err := json.Marshal(result)
+ if err != nil {
+ return err
+ }
+
+ fullUrl := fmt.Sprintf("%s/api/v1/streams", baseUrl)
+ req, err := http.NewRequest(
+ "POST",
+ fullUrl,
+ bytes.NewBuffer([]byte(resultData)),
+ )
+
+ if err != nil {
+ log.Printf("Error building test request: %s", err)
+ return err
+ }
+
+ newresp, err := client.Do(req)
+ if err != nil {
+ log.Printf("Error running test request: %s", err)
+ return err
+ }
+
+ body, err := ioutil.ReadAll(newresp.Body)
+ if err != nil {
+ log.Printf("Failed reading body when waiting: %s", err)
+ return err
+ }
+
+ log.Printf("[INFO] Subworkflow Body: %s", string(body))
+
+ if status == "FAILURE" {
+ return errors.New("[ERROR] Failed to execute subworkflow")
+ } else {
+ return nil
+ }
+}
+
+func removeIndex(s []string, i int) []string {
+ s[len(s)-1], s[i] = s[i], s[len(s)-1]
+ return s[:len(s)-1]
+}
+
+func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) {
if len(startAction) == 0 {
- log.Printf("Didn't find execution start action. Setting it to workflow start action.")
+ startAction = workflowExecution.Start
+ if len(startAction) == 0 {
+ log.Printf("Didn't find execution start action. Setting it to workflow start action.")
+ startAction = workflowExecution.Workflow.Start
+ }
+ }
+
+ //log.Printf("NEXTACTIONS: %s", nextActions)
+ queueNodes := []string{}
+ //if len(nextActions) == 0 {
+ // nextActions = append(nextActions, startAction)
+ //}
+
+ if len(workflowExecution.Results) == 0 {
+ nextActions = []string{startAction}
+ } else {
+ // This is to re-check the nodes that exist and whether they should continue
+ appendActions := []string{}
+ for _, item := range workflowExecution.Results {
+
+ // FIXME: Check whether the item should be visited or not
+ // Do the same check as in walkoff.go - are the parents done?
+ // If skipped and both parents are skipped: keep as skipped, otherwise queue
+ if item.Status == "SKIPPED" {
+ isSkipped := true
+
+ for _, branch := range workflowExecution.Workflow.Branches {
+ // 1. Finds branches where the destination is our node
+ // 2. Finds results of those branches, and sees the status
+ // 3. If the status isn't skipped or failure, then it will still run this node
+ if branch.DestinationID == item.Action.ID {
+ for _, subresult := range workflowExecution.Results {
+ if subresult.Action.ID == branch.SourceID {
+ if subresult.Status != "SKIPPED" && subresult.Status != "FAILURE" {
+ log.Printf("\n\n\nSUBRESULT PARENT STATUS: %s\n\n\n", subresult.Status)
+ isSkipped = false
+
+ break
+ }
+ }
+ }
+ }
+ }
+
+ if isSkipped {
+ //log.Printf("Skipping %s as all parents are done", item.Action.Label)
+ if !arrayContains(visited, item.Action.ID) {
+ log.Printf("[INFO] Adding visited (1): %s", item.Action.Label)
+ visited = append(visited, item.Action.ID)
+ }
+ } else {
+ log.Printf("[INFO] Continuing %s as all parents are NOT done", item.Action.Label)
+ appendActions = append(appendActions, item.Action.ID)
+ }
+ } else {
+ if item.Status == "FINISHED" {
+ log.Printf("[INFO] Adding visited (2): %s", item.Action.Label)
+ visited = append(visited, item.Action.ID)
+ }
+ }
+
+ //if len(nextActions) == 0 {
+ //nextActions = append(nextActions, children[item.Action.ID]...)
+ for _, child := range children[item.Action.ID] {
+ if !arrayContains(nextActions, child) && !arrayContains(visited, child) && !arrayContains(visited, child) {
+ nextActions = append(nextActions, child)
+ }
+ }
+
+ if len(appendActions) > 0 {
+ log.Printf("APPENDED NODES: %#v", appendActions)
+ nextActions = append(nextActions, appendActions...)
+ }
+ }
+ }
+
+ //log.Printf("Nextactions: %s", nextActions)
+ // This is a backup in case something goes wrong in this complex hellhole.
+ // Max default execution time is 5 minutes for now anyway, which should take
+ // care if it gets stuck in a loop.
+ // FIXME: Force killing a worker should result in a notification somewhere
+ if len(nextActions) == 0 {
+ log.Printf("[INFO] No next action. Finished? Result vs shuffle.Actions: %d - %d", len(workflowExecution.Results), len(workflowExecution.Workflow.Actions))
+ exit := true
+ for _, item := range workflowExecution.Results {
+ if item.Status == "EXECUTING" {
+ exit = false
+ break
+ }
+ }
+
+ if len(environments) == 1 {
+ log.Printf("[INFO] Should send results to the backend because environments are %s", environments)
+ validateFinished(workflowExecution)
+ }
+
+ if exit && len(workflowExecution.Results) == len(workflowExecution.Workflow.Actions) {
+ log.Printf("Shutting down.")
+ shutdown(workflowExecution, "", "", true)
+ }
+
+ // Look for the NEXT missing action
+ notFound := []string{}
+ for _, action := range workflowExecution.Workflow.Actions {
+ found := false
+ for _, result := range workflowExecution.Results {
+ if action.ID == result.Action.ID {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ notFound = append(notFound, action.ID)
+ }
+ }
+
+ //log.Printf("SOMETHING IS MISSING!: %#v", notFound)
+ for _, item := range notFound {
+ if arrayContains(executed, item) {
+ log.Printf("%s has already executed but no result!", item)
+ return
+ }
+
+ // Visited means it's been touched in any way.
+ outerIndex := -1
+ for index, visit := range visited {
+ if visit == item {
+ outerIndex = index
+ break
+ }
+ }
+
+ if outerIndex >= 0 {
+ log.Printf("Removing index %s from visited")
+ visited = append(visited[:outerIndex], visited[outerIndex+1:]...)
+ }
+
+ fixed := 0
+ for _, parent := range parents[item] {
+ parentResult := getResult(workflowExecution, parent)
+ if parentResult.Status == "FINISHED" || parentResult.Status == "SUCCESS" || parentResult.Status == "SKIPPED" || parentResult.Status == "FAILURE" {
+ fixed += 1
+ }
+ }
+
+ if fixed == len(parents[item]) {
+ nextActions = append(nextActions, item)
+ }
+
+ // If it's not executed and not in nextActions
+ // FIXME: Check if the item's parents are finished. If they're not, skip.
+ }
+ }
+
+ //log.Printf("Checking nextactions: %s", nextActions)
+ for _, node := range nextActions {
+ nodeChildren := children[node]
+ for _, child := range nodeChildren {
+ if !arrayContains(queueNodes, child) {
+ queueNodes = append(queueNodes, child)
+ }
+ }
+ }
+
+ // IF NOT VISITED && IN toExecuteOnPrem
+ // SKIP if it's not onprem
+ toRemove := []int{}
+ //log.Printf("\n\nNEXTACTIONS: %#v\n\n", nextActions)
+ for index, nextAction := range nextActions {
+ action := getAction(workflowExecution, nextAction, environment)
+ // check visited and onprem
+ if arrayContains(visited, nextAction) {
+ //log.Printf("ALREADY VISITIED (%s): %s", action.Label, nextAction)
+ toRemove = append(toRemove, index)
+ //nextActions = removeIndex(nextActions, index)
+
+ //validateFinished(workflowExecution)
+ _ = index
+
+ continue
+ }
+
+ if action.AppName == "Shuffle Workflow" {
+ //log.Printf("SHUFFLE WORKFLOW: %#v", action)
+ action.Environment = environment
+ action.AppName = "shuffle-subflow"
+ action.Name = "run_subflow"
+ action.AppVersion = "1.0.0"
+
+ //appname := action.AppName
+ //appversion := action.AppVersion
+ //appname = strings.Replace(appname, ".", "-", -1)
+ //appversion = strings.Replace(appversion, ".", "-", -1)
+ // shuffle-subflow_1.0.0
+
+ //visited = append(visited, action.ID)
+ //executed = append(executed, action.ID)
+
+ trigger := shuffle.Trigger{}
+ for _, innertrigger := range workflowExecution.Workflow.Triggers {
+ if innertrigger.ID == action.ID {
+ trigger = innertrigger
+ break
+ }
+ }
+
+ // FIXME: Add startnode from frontend
+ action.Parameters = []shuffle.WorkflowAppActionParameter{}
+ for _, parameter := range trigger.Parameters {
+ parameter.Variant = "STATIC_VALUE"
+ action.Parameters = append(action.Parameters, parameter)
+ }
+
+ action.Parameters = append(action.Parameters, shuffle.WorkflowAppActionParameter{
+ Name: "source_workflow",
+ Value: workflowExecution.Workflow.ID,
+ })
+
+ action.Parameters = append(action.Parameters, shuffle.WorkflowAppActionParameter{
+ Name: "source_execution",
+ Value: workflowExecution.ExecutionId,
+ })
+
+ //trigger.LargeImage = ""
+ //err = handleSubworkflowExecution(client, workflowExecution, trigger, action)
+ //if err != nil {
+ // log.Printf("[ERROR] Failed to execute subworkflow: %s", err)
+ //} else {
+ // log.Printf("[INFO] Executed subworkflow!")
+ //}
+ //continue
+ } else if action.AppName == "User Input" {
+ log.Printf("USER INPUT!")
+
+ if action.ID == workflowExecution.Start {
+ log.Printf("Skipping because it's the startnode")
+ visited = append(visited, action.ID)
+ executed = append(executed, action.ID)
+ continue
+ } else {
+ log.Printf("Should stop after this iteration because it's user-input based. %#v", action)
+ trigger := shuffle.Trigger{}
+ for _, innertrigger := range workflowExecution.Workflow.Triggers {
+ if innertrigger.ID == action.ID {
+ trigger = innertrigger
+ break
+ }
+ }
+
+ trigger.LargeImage = ""
+ triggerData, err := json.Marshal(trigger)
+ if err != nil {
+ log.Printf("Failed unmarshalling action: %s", err)
+ triggerData = []byte("Failed unmarshalling. Cancel execution!")
+ }
+
+ err = runUserInput(topClient, action, workflowExecution.Workflow.ID, workflowExecution.ExecutionId, workflowExecution.Authorization, string(triggerData))
+ if err != nil {
+ log.Printf("Failed launching backend magic: %s", err)
+ os.Exit(3)
+ } else {
+ log.Printf("Launched user input node succesfully!")
+ os.Exit(3)
+ }
+
+ break
+ }
+ } else {
+ //log.Printf("Handling action %#v", action)
+ }
+
+ if len(toRemove) > 0 {
+ //toRemove = []int{}
+ //for index, nextAction := range nextActions {
+ }
+
+ // Not really sure how this edgecase happens.
+
+ // FIXME
+ // Execute, as we don't really care if env is not set? IDK
+ if action.Environment != environment { //&& action.Environment != "" {
+ //log.Printf("Action: %#v", action)
+ log.Printf("Bad environment for node: %s. Want %s", action.Environment, environment)
+ continue
+ }
+
+ // check whether the parent is finished executing
+ //log.Printf("%s has %d parents", nextAction, len(parents[nextAction]))
+
+ continueOuter := true
+ if action.IsStartNode {
+ continueOuter = false
+ } else if len(parents[nextAction]) > 0 {
+ // FIXME - wait for parents to finishe executing
+ fixed := 0
+ for _, parent := range parents[nextAction] {
+ parentResult := getResult(workflowExecution, parent)
+ if parentResult.Status == "FINISHED" || parentResult.Status == "SUCCESS" || parentResult.Status == "SKIPPED" || parentResult.Status == "FAILURE" {
+ fixed += 1
+ }
+ }
+
+ if fixed == len(parents[nextAction]) {
+ continueOuter = false
+ }
+ } else {
+ continueOuter = false
+ }
+
+ if continueOuter {
+ log.Printf("[INFO] Parents of %s aren't finished: %s", nextAction, strings.Join(parents[nextAction], ", "))
+ //for _, tmpaction := range parents[nextAction] {
+ // action := getAction(workflowExecution, tmpaction)
+ // _ = action
+ // //log.Printf("Parent: %s", action.Label)
+ //}
+ // Find the result of the nodes?
+ continue
+ }
+
+ // get action status
+ actionResult := getResult(workflowExecution, nextAction)
+ if actionResult.Action.ID == action.ID {
+ log.Printf("[INFO] %s already has status %s.", action.ID, actionResult.Status)
+ continue
+ } else {
+ log.Printf("[INFO] %s:%s has no status result yet. Should execute.", action.Name, action.ID)
+ }
+
+ appname := action.AppName
+ appversion := action.AppVersion
+ appname = strings.Replace(appname, ".", "-", -1)
+ appversion = strings.Replace(appversion, ".", "-", -1)
+
+ image := fmt.Sprintf("%s:%s_%s", baseimagename, strings.ToLower(action.AppName), action.AppVersion)
+ if strings.Contains(image, " ") {
+ image = strings.ReplaceAll(image, " ", "-")
+ }
+
+ // Added UUID to identifier just in case
+ identifier := fmt.Sprintf("%s_%s_%s_%s_%s", appname, appversion, action.ID, workflowExecution.ExecutionId, uuid.NewV4())
+ if strings.Contains(identifier, " ") {
+ identifier = strings.ReplaceAll(identifier, " ", "-")
+ }
+
+ // FIXME - check whether it's running locally yet too
+ dockercli, err := dockerclient.NewEnvClient()
+ if err != nil {
+ log.Printf("[ERROR] Unable to create docker client (2): %s", err)
+ //return err
+ return
+ }
+
+ stats, err := dockercli.ContainerInspect(context.Background(), identifier)
+ if err != nil || stats.ContainerJSONBase.State.Status != "running" {
+ // REMOVE
+ if err == nil {
+ log.Printf("Status: %s, should kill: %s", stats.ContainerJSONBase.State.Status, identifier)
+ err = removeContainer(identifier)
+ if err != nil {
+ log.Printf("Error killing container: %s", err)
+ }
+ } else {
+ //log.Printf("WHAT TO DO HERE?: %s", err)
+ }
+ } else if stats.ContainerJSONBase.State.Status == "running" {
+ //log.Printf("
+ continue
+ }
+
+ if len(action.Parameters) == 0 {
+ action.Parameters = []shuffle.WorkflowAppActionParameter{}
+ }
+
+ if len(action.Errors) == 0 {
+ action.Errors = []string{}
+ }
+
+ // marshal action and put it in there rofl
+ log.Printf("[INFO] Time to execute %s (%s) with app %s:%s, function %s, env %s with %d parameters.", action.ID, action.Label, action.AppName, action.AppVersion, action.Name, action.Environment, len(action.Parameters))
+
+ actionData, err := json.Marshal(action)
+ if err != nil {
+ log.Printf("Failed unmarshalling action: %s", err)
+ continue
+ }
+
+ if action.AppID == "0ca8887e-b4af-4e3e-887c-87e9d3bc3d3e" {
+ log.Printf("\nShould run filter: %#v\n\n", action)
+ runFilter(workflowExecution, action)
+ continue
+ }
+
+ executionData, err := json.Marshal(workflowExecution)
+ if err != nil {
+ log.Printf("Failed marshalling executiondata: %s", err)
+ executionData = []byte("")
+ }
+
+ // Sending full execution so that it won't have to load in every app
+ // This might be an issue if they can read environments, but that's alright
+ // if everything is generated during execution
+ log.Printf("[INFO] Deployed with CALLBACK_URL %s and BASE_URL %s", appCallbackUrl, baseUrl)
+ env := []string{
+ fmt.Sprintf("ACTION=%s", string(actionData)),
+ fmt.Sprintf("EXECUTIONID=%s", workflowExecution.ExecutionId),
+ fmt.Sprintf("AUTHORIZATION=%s", workflowExecution.Authorization),
+ fmt.Sprintf("CALLBACK_URL=%s", baseUrl),
+ fmt.Sprintf("BASE_URL=%s", appCallbackUrl),
+ }
+
+ // Fixes issue:
+ // standard_init_linux.go:185: exec user process caused "argument list too long"
+ // https://devblogs.microsoft.com/oldnewthing/20100203-00/?p=15083
+ maxSize := 32700 - len(string(actionData)) - 2000
+ if len(executionData) < maxSize {
+ log.Printf("[INFO] ADDING FULL_EXECUTION because size is smaller than %d", maxSize)
+ env = append(env, fmt.Sprintf("FULL_EXECUTION=%s", string(executionData)))
+ } else {
+ log.Printf("[WARNING] Skipping FULL_EXECUTION because size is larger than %d", maxSize)
+ }
+
+ // Uses a few ways of getting / checking if an app is available
+ // 1. Try original with lowercase
+ // 2. Go to original
+ // 3. Add remote repo location
+ // 4. Actually download last repo
+
+ images := []string{
+ image,
+ fmt.Sprintf("%s:%s_%s", baseimagename, action.AppName, action.AppVersion),
+ fmt.Sprintf("%s/%s:%s_%s", registryName, baseimagename, strings.ToLower(action.AppName), action.AppVersion),
+ }
+
+ // If cleanup is set, it should run for efficiency
+ pullOptions := types.ImagePullOptions{}
+ if cleanupEnv == "true" {
+ err = deployApp(dockercli, images[0], identifier, env, workflowExecution)
+ if err != nil {
+ if strings.Contains(err.Error(), "exited prematurely") {
+ shutdown(workflowExecution, action.ID, err.Error(), true)
+ }
+
+ log.Printf("[WARNING] Failed CLEANUP execution. Downloading image remotely.")
+ image = images[2]
+ reader, err := dockercli.ImagePull(context.Background(), image, pullOptions)
+ if err != nil {
+ log.Printf("[ERROR] Failed getting %s. The couldn't be find locally, AND is missing.", image)
+ shutdown(workflowExecution, action.ID, err.Error(), true)
+ }
+
+ buildBuf := new(strings.Builder)
+ _, err = io.Copy(buildBuf, reader)
+ if err != nil {
+ log.Printf("[ERROR] Error in IO copy: %s", err)
+ shutdown(workflowExecution, action.ID, err.Error(), true)
+ } else {
+ if strings.Contains(buildBuf.String(), "errorDetail") {
+ log.Printf("[ERROR] Docker build:\n%s\nERROR ABOVE: Trying to pull tags from: %s", buildBuf.String(), image)
+ shutdown(workflowExecution, action.ID, err.Error(), true)
+ }
+
+ log.Printf("[INFO] Successfully downloaded %s", image)
+ }
+
+ err = deployApp(dockercli, image, identifier, env, workflowExecution)
+ if err != nil {
+
+ log.Printf("[ERROR] Failed deploying image for the FOURTH time. Aborting if the image doesn't exist")
+ if strings.Contains(err.Error(), "exited prematurely") {
+ shutdown(workflowExecution, action.ID, err.Error(), true)
+ }
+
+ if strings.Contains(err.Error(), "No such image") {
+ //log.Printf("[WARNING] Failed deploying %s from image %s: %s", identifier, image, err)
+ log.Printf("[ERROR] Image doesn't exist. Shutting down")
+ shutdown(workflowExecution, action.ID, err.Error(), true)
+ }
+ }
+ }
+ } else {
+
+ err = deployApp(dockercli, images[0], identifier, env, workflowExecution)
+ if err != nil {
+ if strings.Contains(err.Error(), "exited prematurely") {
+ shutdown(workflowExecution, action.ID, err.Error(), true)
+ }
+
+ // 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.
+ image = images[1]
+ if strings.Contains(image, " ") {
+ image = strings.ReplaceAll(image, " ", "-")
+ }
+
+ err = deployApp(dockercli, image, identifier, env, workflowExecution)
+ if err != nil {
+ if strings.Contains(err.Error(), "exited prematurely") {
+ shutdown(workflowExecution, action.ID, err.Error(), true)
+ }
+
+ image = images[2]
+ if strings.Contains(image, " ") {
+ image = strings.ReplaceAll(image, " ", "-")
+ }
+
+ err = deployApp(dockercli, image, identifier, env, workflowExecution)
+ if err != nil {
+ if strings.Contains(err.Error(), "exited prematurely") {
+ shutdown(workflowExecution, action.ID, err.Error(), true)
+ }
+
+ log.Printf("[WARNING] Failed deploying image THREE TIMES. Attempting to download the latter as last resort.")
+ reader, err := dockercli.ImagePull(context.Background(), image, pullOptions)
+ if err != nil {
+ log.Printf("[ERROR] Failed getting %s. The couldn't be find locally, AND is missing.", image)
+ shutdown(workflowExecution, action.ID, err.Error(), true)
+ }
+
+ buildBuf := new(strings.Builder)
+ _, err = io.Copy(buildBuf, reader)
+ if err != nil {
+ log.Printf("[ERROR] Error in IO copy: %s", err)
+ shutdown(workflowExecution, action.ID, err.Error(), true)
+ } else {
+ if strings.Contains(buildBuf.String(), "errorDetail") {
+ log.Printf("[ERROR] Docker build:\n%s\nERROR ABOVE: Trying to pull tags from: %s", buildBuf.String(), image)
+ shutdown(workflowExecution, action.ID, err.Error(), true)
+ }
+
+ log.Printf("[INFO] Successfully downloaded %s", image)
+ }
+
+ err = deployApp(dockercli, image, identifier, env, workflowExecution)
+ if err != nil {
+ log.Printf("[ERROR] Failed deploying image for the FOURTH time. Aborting if the image doesn't exist")
+ if strings.Contains(err.Error(), "exited prematurely") {
+ shutdown(workflowExecution, action.ID, err.Error(), true)
+ }
+
+ if strings.Contains(err.Error(), "No such image") {
+ //log.Printf("[WARNING] Failed deploying %s from image %s: %s", identifier, image, err)
+ log.Printf("[ERROR] Image doesn't exist. Shutting down")
+ shutdown(workflowExecution, action.ID, err.Error(), true)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ log.Printf("[INFO] Adding visited (3): %s", action.Label)
+
+ visited = append(visited, action.ID)
+ executed = append(executed, action.ID)
+
+ // If children of action.ID are NOT in executed:
+ // Remove them from visited.
+ //log.Printf("EXECUTED: %#v", executed)
+ }
+
+ //log.Println(nextAction)
+ //log.Println(startAction, children[startAction])
+
+ // FIXME - new request here
+ // FIXME - clean up stopped (remove) containers with this execution id
+
+ if len(workflowExecution.Results) == len(workflowExecution.Workflow.Actions)+extra {
+ shutdownCheck := true
+ for _, result := range workflowExecution.Results {
+ if result.Status == "EXECUTING" {
+ // Cleaning up executing stuff
+ shutdownCheck = false
+ // USED TO BE CONTAINER REMOVAL
+ // FIXME - send POST request to kill the container
+ //log.Printf("Should remove (POST request) stopped containers")
+ //ret = requests.post("%s%s" % (self.url, stream_path), headers=headers, json=action_result)
+ }
+ }
+
+ if shutdownCheck {
+ log.Println("[INFO] BREAKING BECAUSE RESULTS IS SAME LENGTH AS ACTIONS. SHOULD CHECK ALL RESULTS FOR WHETHER THEY'RE DONE")
+ validateFinished(workflowExecution)
+ shutdown(workflowExecution, "", "", true)
+ }
+ }
+
+ time.Sleep(time.Duration(sleepTime) * time.Second)
+ return
+}
+
+func executionInit(workflowExecution shuffle.WorkflowExecution) error {
+ parents = map[string][]string{}
+ children = map[string][]string{}
+
+ startAction = workflowExecution.Start
+ log.Printf("[INFO] STARTACTION: %s", startAction)
+ if len(startAction) == 0 {
+ log.Printf("[INFO] Didn't find execution start action. Setting it to workflow start action.")
startAction = workflowExecution.Workflow.Start
}
- log.Printf("Startaction: %s", startAction)
- toExecuteOnprem := []string{}
- parents := map[string][]string{}
- children := map[string][]string{}
+ // Setting up extra counter
+ 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
+ }
+ }
- // source = parent node, dest = child node
- // parent can have more children, child can have more parents
- extra := 0
+ nextActions = append(nextActions, startAction)
for _, branch := range workflowExecution.Workflow.Branches {
// Check what the parent is first. If it's trigger - skip
sourceFound := false
@@ -565,34 +1143,44 @@ func handleExecution(client *http.Client, req *http.Request, workflowExecution W
}
for _, trigger := range workflowExecution.Workflow.Triggers {
- if trigger.AppName != "User Input" {
- continue
- }
+ //log.Printf("Appname trigger (0): %s", trigger.AppName)
+ if trigger.AppName == "User Input" || trigger.AppName == "Shuffle Workflow" {
+ if branch.SourceID == "c9560766-3f85-4589-8324-311acd6be820" {
+ log.Printf("BRANCH: %#v", branch)
+ }
- if trigger.ID == branch.SourceID {
- sourceFound = true
- extra += 1
- }
-
- if trigger.ID == branch.DestinationID {
- destinationFound = true
+ if trigger.ID == branch.SourceID {
+ log.Printf("[INFO] shuffle.Trigger %s is the source!", trigger.AppName)
+ sourceFound = true
+ } else if trigger.ID == branch.DestinationID {
+ log.Printf("[INFO] shuffle.Trigger %s is the destination!", trigger.AppName)
+ destinationFound = true
+ }
}
}
if sourceFound {
parents[branch.DestinationID] = append(parents[branch.DestinationID], branch.SourceID)
} else {
- log.Printf("ID %s was not found in actions! Skipping parent. (TRIGGER?)", branch.SourceID)
+ log.Printf("[INFO] ID %s was not found in actions! Skipping parent. (TRIGGER?)", branch.SourceID)
}
if destinationFound {
children[branch.SourceID] = append(children[branch.SourceID], branch.DestinationID)
} else {
- log.Printf("ID %s was not found in actions! Skipping child. (TRIGGER?)", branch.SourceID)
+ log.Printf("[INFO] ID %s was not found in actions! Skipping child. (TRIGGER?)", branch.SourceID)
}
}
- log.Printf("Actions: %d + Special Triggers: %d", len(workflowExecution.Workflow.Actions), extra)
+ /*
+ log.Printf("\n\n\n[INFO] CHILDREN FOUND: %#v", children)
+ log.Printf("[INFO] PARENTS FOUND: %#v", parents)
+ log.Printf("[INFO] NEXT ACTIONS: %#v\n\n", nextActions)
+ */
+
+ log.Printf("[INFO] shuffle.Actions: %d + Special shuffle.Triggers: %d", len(workflowExecution.Workflow.Actions), extra)
+ onpremApps := []string{}
+ toExecuteOnprem := []string{}
for _, action := range workflowExecution.Workflow.Actions {
if action.Environment != environment {
continue
@@ -619,7 +1207,7 @@ func handleExecution(client *http.Client, req *http.Request, workflowExecution W
pullOptions := types.ImagePullOptions{}
_ = pullOptions
for _, image := range onpremApps {
- log.Printf("Image: %s", image)
+ log.Printf("[INFO] Image: %s", image)
// Kind of gambling that the image exists.
if strings.Contains(image, " ") {
image = strings.ReplaceAll(image, " ", "-")
@@ -630,7 +1218,7 @@ func handleExecution(client *http.Client, req *http.Request, workflowExecution W
//reader, err := dockercli.ImagePull(context.Background(), image, pullOptions)
//if err != nil {
// log.Printf("Failed getting %s. The app is missing or some other issue", image)
- // shutdown(workflowExecution.ExecutionId)
+ // shutdown(workflowExecution)
//}
////io.Copy(os.Stdout, reader)
@@ -638,470 +1226,79 @@ func handleExecution(client *http.Client, req *http.Request, workflowExecution W
//log.Printf("Successfully downloaded and built %s", image)
}
+ return nil
+}
+
+func handleExecution(client *http.Client, req *http.Request, workflowExecution shuffle.WorkflowExecution) error {
+ // if no onprem runs (shouldn't happen, but extra check), exit
+ // if there are some, load the images ASAP for the app
+
+ err := executionInit(workflowExecution)
+ if err != nil {
+ log.Printf("[INFO] Workflow setup failed: %s", workflowExecution.ExecutionId, err)
+ shutdown(workflowExecution, "", "", true)
+ }
+
+ log.Printf("Startaction: %s", startAction)
+
+ // source = parent node, dest = child node
+ // parent can have more children, child can have more parents
// Process the parents etc. How?
- visited := []string{}
- executed := []string{}
- nextActions := []string{startAction}
- firstIteration := true
for {
- queueNodes := []string{}
+ handleExecutionResult(workflowExecution)
- if len(workflowExecution.Results) == 0 {
- nextActions = []string{startAction}
- } else if firstIteration {
- firstIteration = false
- } else {
- // This is to re-check the nodes that exist and whether they should continue
- appendActions := []string{}
- for _, item := range workflowExecution.Results {
+ //fullUrl := fmt.Sprintf("%s/api/v1/workflows/%s/executions/%s/abort", baseUrl, workflowExecution.Workflow.ID, workflowExecution.ExecutionId)
+ fullUrl := fmt.Sprintf("%s/api/v1/streams", baseUrl)
+ log.Printf("URL: %s", fullUrl)
+ req, err := http.NewRequest(
+ "POST",
+ fullUrl,
+ bytes.NewBuffer([]byte(data)),
+ )
- // FIXME: Check whether the item should be visited or not
- // Do the same check as in walkoff.go - are the parents done?
- // If skipped and both parents are skipped: keep as skipped, otherwise queue
- if item.Status == "SKIPPED" {
- isSkipped := true
-
- for _, branch := range workflowExecution.Workflow.Branches {
- // 1. Finds branches where the destination is our node
- // 2. Finds results of those branches, and sees the status
- // 3. If the status isn't skipped or failure, then it will still run this node
- if branch.DestinationID == item.Action.ID {
- for _, subresult := range workflowExecution.Results {
- if subresult.Action.ID == branch.SourceID {
- if subresult.Status != "SKIPPED" && subresult.Status != "FAILURE" {
- log.Printf("\n\n\nSUBRESULT PARENT STATUS: %s\n\n\n", subresult.Status)
- isSkipped = false
-
- break
- }
- }
- }
- }
- }
-
- if isSkipped {
- //log.Printf("Skipping %s as all parents are done", item.Action.Label)
- if !arrayContains(visited, item.Action.ID) {
- log.Printf("Adding visited (1): %s", item.Action.Label)
- visited = append(visited, item.Action.ID)
- }
- } else {
- log.Printf("Continuing %s as all parents are NOT done", item.Action.Label)
- appendActions = append(appendActions, item.Action.ID)
- }
- } else {
- if item.Status == "FINISHED" {
- log.Printf("Adding visited (2): %s", item.Action.Label)
- visited = append(visited, item.Action.ID)
- }
- }
-
- nextActions = children[item.Action.ID]
- if len(appendActions) > 0 {
- log.Printf("APPENDED NODES: %#v", appendActions)
- nextActions = append(nextActions, appendActions...)
- }
- }
- }
-
- // This is a backup in case something goes wrong in this complex hellhole.
- // Max default execution time is 5 minutes for now anyway, which should take
- // care if it gets stuck in a loop.
- // FIXME: Force killing a worker should result in a notification somewhere
- if len(nextActions) == 0 {
- log.Printf("No next action. Finished? Result vs Actions: %d - %d", len(workflowExecution.Results), len(workflowExecution.Workflow.Actions))
- exit := true
- for _, item := range workflowExecution.Results {
- if item.Status == "EXECUTING" {
- exit = false
- break
- }
- }
-
- if exit && len(workflowExecution.Results) == len(workflowExecution.Workflow.Actions) {
- log.Printf("Shutting down.")
- shutdown(workflowExecution.ExecutionId, workflowExecution.Workflow.ID)
- }
-
- // Look for the NEXT missing action
- notFound := []string{}
- for _, action := range workflowExecution.Workflow.Actions {
- found := false
- for _, result := range workflowExecution.Results {
- if action.ID == result.Action.ID {
- found = true
- break
- }
- }
-
- if !found {
- notFound = append(notFound, action.ID)
- }
- }
-
- //log.Printf("SOMETHING IS MISSING!: %#v", notFound)
- for _, item := range notFound {
- if arrayContains(executed, item) {
- log.Printf("%s has already executed but no result!", item)
- continue
- }
-
- // Visited means it's been touched in any way.
- outerIndex := -1
- for index, visit := range visited {
- if visit == item {
- outerIndex = index
- break
- }
- }
-
- if outerIndex >= 0 {
- log.Printf("Removing index %s from visited")
- visited = append(visited[:outerIndex], visited[outerIndex+1:]...)
- }
-
- fixed := 0
- for _, parent := range parents[item] {
- parentResult := getResult(workflowExecution, parent)
- if parentResult.Status == "FINISHED" || parentResult.Status == "SUCCESS" || parentResult.Status == "SKIPPED" || parentResult.Status == "FAILURE" {
- fixed += 1
- }
- }
-
- if fixed == len(parents[item]) {
- nextActions = append(nextActions, item)
- }
-
- // If it's not executed and not in nextActions
- // FIXME: Check if the item's parents are finished. If they're not, skip.
- }
- }
-
- for _, node := range nextActions {
- nodeChildren := children[node]
- for _, child := range nodeChildren {
- if !arrayContains(queueNodes, child) {
- queueNodes = append(queueNodes, child)
- }
- }
- }
- //log.Printf("NEXT: %s", nextActions)
- //log.Printf("queueNodes: %s", queueNodes)
-
- // IF NOT VISITED && IN toExecuteOnPrem
- // SKIP if it's not onprem
- for _, nextAction := range nextActions {
- action := getAction(workflowExecution, nextAction, environment)
- // check visited and onprem
- if arrayContains(visited, nextAction) {
- log.Printf("ALREADY VISITIED (%s): %s", action.Label, nextAction)
- continue
- }
-
- if action.AppName == "User Input" {
- log.Printf("USER INPUT!")
-
- if action.ID == workflowExecution.Start {
- log.Printf("Skipping because it's the startnode")
- visited = append(visited, action.ID)
- executed = append(executed, action.ID)
- continue
- } else {
- log.Printf("Should stop after this iteration because it's user-input based. %#v", action)
- trigger := Trigger{}
- for _, innertrigger := range workflowExecution.Workflow.Triggers {
- if innertrigger.ID == action.ID {
- trigger = innertrigger
- break
- }
- }
-
- trigger.LargeImage = ""
- triggerData, err := json.Marshal(trigger)
- if err != nil {
- log.Printf("Failed unmarshalling action: %s", err)
- triggerData = []byte("Failed unmarshalling. Cancel execution!")
- }
-
- err = runUserInput(client, action, workflowExecution.Workflow.ID, workflowExecution.ExecutionId, workflowExecution.Authorization, string(triggerData))
- if err != nil {
- log.Printf("Failed launching backend magic: %s", err)
- os.Exit(3)
- } else {
- log.Printf("Launched user input node succesfully!")
- os.Exit(3)
- }
-
- break
- }
- }
-
- // Not really sure how this edgecase happens.
-
- // FIXME
- // Execute, as we don't really care if env is not set? IDK
- if action.Environment != environment { //&& action.Environment != "" {
- log.Printf("Bad environment for node: %s. Want %s", action.Environment, environment)
- continue
- }
-
- // check whether the parent is finished executing
- //log.Printf("%s has %d parents", nextAction, len(parents[nextAction]))
-
- continueOuter := true
- if action.IsStartNode {
- continueOuter = false
- } else if len(parents[nextAction]) > 0 {
- // FIXME - wait for parents to finishe executing
- fixed := 0
- for _, parent := range parents[nextAction] {
- parentResult := getResult(workflowExecution, parent)
- if parentResult.Status == "FINISHED" || parentResult.Status == "SUCCESS" || parentResult.Status == "SKIPPED" || parentResult.Status == "FAILURE" {
- fixed += 1
- }
- }
-
- if fixed == len(parents[nextAction]) {
- continueOuter = false
- }
- } else {
- continueOuter = false
- }
-
- if continueOuter {
- log.Printf("Parents of %s aren't finished: %s", nextAction, strings.Join(parents[nextAction], ", "))
- //for _, tmpaction := range parents[nextAction] {
- // action := getAction(workflowExecution, tmpaction)
- // _ = action
- // //log.Printf("Parent: %s", action.Label)
- //}
- // Find the result of the nodes?
- continue
- }
-
- // get action status
- actionResult := getResult(workflowExecution, nextAction)
- if actionResult.Action.ID == action.ID {
- log.Printf("%s already has status %s.", action.ID, actionResult.Status)
- continue
- } else {
- log.Printf("%s:%s has no status result yet. Should execute.", action.Name, action.ID)
- }
-
- appname := action.AppName
- appversion := action.AppVersion
- appname = strings.Replace(appname, ".", "-", -1)
- appversion = strings.Replace(appversion, ".", "-", -1)
-
- image := fmt.Sprintf("%s:%s_%s", baseimagename, action.AppName, action.AppVersion)
- if strings.Contains(image, " ") {
- image = strings.ReplaceAll(image, " ", "-")
- }
-
- identifier := fmt.Sprintf("%s_%s_%s_%s", appname, appversion, action.ID, workflowExecution.ExecutionId)
- if strings.Contains(identifier, " ") {
- identifier = strings.ReplaceAll(identifier, " ", "-")
- }
-
- // FIXME - check whether it's running locally yet too
- stats, err := dockercli.ContainerInspect(context.Background(), identifier)
- if err != nil || stats.ContainerJSONBase.State.Status != "running" {
- // REMOVE
- if err == nil {
- log.Printf("Status: %s, should kill: %s", stats.ContainerJSONBase.State.Status, identifier)
- err = removeContainer(identifier)
- if err != nil {
- log.Printf("Error killing container: %s", err)
- }
- } else {
- //log.Printf("WHAT TO DO HERE?: %s", err)
- }
- } else if stats.ContainerJSONBase.State.Status == "running" {
- continue
- }
-
- if len(action.Parameters) == 0 {
- action.Parameters = []WorkflowAppActionParameter{}
- }
-
- if len(action.Errors) == 0 {
- action.Errors = []string{}
- }
-
- // marshal action and put it in there rofl
- log.Printf("Time to execute %s (%s) with app %s:%s, function %s, env %s with %d parameters.", action.ID, action.Label, action.AppName, action.AppVersion, action.Name, action.Environment, len(action.Parameters))
-
- actionData, err := json.Marshal(action)
- if err != nil {
- log.Printf("Failed unmarshalling action: %s", err)
- continue
- }
-
- if action.AppID == "0ca8887e-b4af-4e3e-887c-87e9d3bc3d3e" {
- log.Printf("\nShould run filter: %#v\n\n", action)
- runFilter(workflowExecution, action)
- continue
- }
-
- executionData, err := json.Marshal(workflowExecution)
- if err != nil {
- log.Printf("Failed marshalling executiondata: %s", err)
- executionData = []byte("")
- }
-
- // Sending full execution so that it won't have to load in every app
- // This might be an issue if they can read environments, but that's alright
- // if everything is generated during execution
- env := []string{
- fmt.Sprintf("ACTION=%s", string(actionData)),
- fmt.Sprintf("EXECUTIONID=%s", workflowExecution.ExecutionId),
- fmt.Sprintf("AUTHORIZATION=%s", workflowExecution.Authorization),
- fmt.Sprintf("CALLBACK_URL=%s", baseUrl),
- }
-
- // Fixes issue:
- // standard_init_linux.go:185: exec user process caused "argument list too long"
- // https://devblogs.microsoft.com/oldnewthing/20100203-00/?p=15083
- maxSize := 32700 - len(string(actionData)) - 2000
- if len(executionData) < maxSize {
- log.Printf("ADDING FULL_EXECUTION because size is smaller than %d", maxSize)
- env = append(env, fmt.Sprintf("FULL_EXECUTION=%s", string(executionData)))
- } else {
- log.Printf("Skipping FULL_EXECUTION because size is larger than %d", maxSize)
- }
-
- err = deployApp(dockercli, image, identifier, env)
- if err != nil {
- log.Printf("[ERROR] Failed deploying %s from image %s: %s", identifier, image, err)
- if strings.Contains(err.Error(), "No such image") {
- log.Printf("[ERROR] Image doesn't exist. Shutting down")
- shutdown(workflowExecution.ExecutionId, workflowExecution.Workflow.ID)
- }
- }
-
- log.Printf("Adding visited (3): %s", action.Label)
-
- visited = append(visited, action.ID)
- executed = append(executed, action.ID)
-
- // If children of action.ID are NOT in executed:
- // Remove them from visited.
- //log.Printf("EXECUTED: %#v", executed)
- }
-
- //log.Println(nextAction)
- //log.Println(startAction, children[startAction])
-
- // FIXME - new request here
- // FIXME - clean up stopped (remove) containers with this execution id
- newresp, err := client.Do(req)
+ newresp, err := topClient.Do(req)
if err != nil {
- log.Printf("Failed making request: %s", err)
+ log.Printf("[ERROR] Failed making request: %s", err)
time.Sleep(time.Duration(sleepTime) * time.Second)
continue
}
body, err := ioutil.ReadAll(newresp.Body)
if err != nil {
- log.Printf("Failed reading body: %s", err)
+ log.Printf("[ERROR] Failed reading body: %s", err)
time.Sleep(time.Duration(sleepTime) * time.Second)
continue
}
if newresp.StatusCode != 200 {
- log.Printf("Err: %s\nStatusCode: %d", string(body), newresp.StatusCode)
+ log.Printf("[ERROR] Bad statuscode: %d, %s", newresp.StatusCode, string(body))
+
+ if strings.Contains(string(body), "Workflowexecution is already finished") {
+ shutdown(workflowExecution, "", "", false)
+ }
+
time.Sleep(time.Duration(sleepTime) * time.Second)
continue
}
err = json.Unmarshal(body, &workflowExecution)
if err != nil {
- log.Printf("Failed workflowExecution unmarshal: %s", err)
+ log.Printf("[ERROR] Failed workflowExecution unmarshal: %s", err)
time.Sleep(time.Duration(sleepTime) * time.Second)
continue
}
if workflowExecution.Status == "FINISHED" || workflowExecution.Status == "SUCCESS" {
- log.Printf("Workflow %s is finished. Exiting worker.", workflowExecution.ExecutionId)
- shutdown(workflowExecution.ExecutionId, workflowExecution.Workflow.ID)
+ log.Printf("[INFO] Workflow %s is finished. Exiting worker.", workflowExecution.ExecutionId)
+ shutdown(workflowExecution, "", "", true)
}
- log.Printf("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" {
- log.Printf("Exiting as worker execution has status %s!", workflowExecution.Status)
- shutdown(workflowExecution.ExecutionId, workflowExecution.Workflow.ID)
+ log.Printf("[WARNING] Exiting as worker execution has status %s!", workflowExecution.Status)
+ shutdown(workflowExecution, "", "", true)
}
- if len(workflowExecution.Results) == len(workflowExecution.Workflow.Actions)+extra {
- shutdownCheck := true
- ctx := context.Background()
- for _, result := range workflowExecution.Results {
- if result.Status == "EXECUTING" {
- // Cleaning up executing stuff
- shutdownCheck = false
- // Check status
-
- containers, err := dockercli.ContainerList(ctx, types.ContainerListOptions{
- All: true,
- })
- if err != nil {
- log.Printf("Failed listing containers: %s", err)
- continue
- }
-
- stopContainers := []string{}
- removeContainers := []string{}
- for _, container := range containers {
- for _, name := range container.Names {
- if !strings.Contains(name, result.Action.ID) {
- continue
- }
-
- if container.State != "running" {
- removeContainers = append(removeContainers, container.ID)
- stopContainers = append(stopContainers, container.ID)
- }
- }
- }
-
- // FIXME - add killing of apps with same execution ID too
- // FIXME - stahp
- //for _, containername := range stopContainers {
- // if err := dockercli.ContainerStop(ctx, containername, nil); err != nil {
- // log.Printf("Unable to stop container: %s", err)
- // } else {
- // log.Printf("Stopped container %s", containername)
- // }
- //}
-
- removeOptions := types.ContainerRemoveOptions{
- RemoveVolumes: true,
- Force: true,
- }
-
- _ = removeOptions
-
- // FIXME - this
- //for _, containername := range removeContainers {
- // if err := dockercli.ContainerRemove(ctx, containername, removeOptions); err != nil {
- // log.Printf("Unable to remove container: %s", err)
- // } else {
- // log.Printf("Removed container %s", containername)
- // }
- //}
-
- // FIXME - send POST request to kill the container
- log.Printf("Should remove (POST request) stopped containers")
- //ret = requests.post("%s%s" % (self.url, stream_path), headers=headers, json=action_result)
- }
- }
-
- if shutdownCheck {
- log.Println("BREAKING BECAUSE RESULTS IS SAME LENGTH AS ACTIONS. SHOULD CHECK ALL RESULTS FOR WHETHER THEY'RE DONE")
- shutdown(workflowExecution.ExecutionId, workflowExecution.Workflow.ID)
- }
- }
- time.Sleep(time.Duration(sleepTime) * time.Second)
}
return nil
@@ -1118,17 +1315,17 @@ func arrayContains(visited []string, id string) bool {
return found
}
-func getResult(workflowExecution WorkflowExecution, id string) ActionResult {
+func getResult(workflowExecution shuffle.WorkflowExecution, id string) shuffle.ActionResult {
for _, actionResult := range workflowExecution.Results {
if actionResult.Action.ID == id {
return actionResult
}
}
- return ActionResult{}
+ return shuffle.ActionResult{}
}
-func getAction(workflowExecution WorkflowExecution, id, environment string) Action {
+func getAction(workflowExecution shuffle.WorkflowExecution, id, environment string) shuffle.Action {
for _, action := range workflowExecution.Workflow.Actions {
if action.ID == id {
return action
@@ -1137,22 +1334,23 @@ func getAction(workflowExecution WorkflowExecution, id, environment string) Acti
for _, trigger := range workflowExecution.Workflow.Triggers {
if trigger.ID == id {
- return Action{
+ return shuffle.Action{
ID: trigger.ID,
AppName: trigger.AppName,
Name: trigger.AppName,
Environment: environment,
+ Label: trigger.Label,
}
log.Printf("FOUND TRIGGER: %#v!", trigger)
}
}
- return Action{}
+ return shuffle.Action{}
}
-func runUserInput(client *http.Client, action Action, workflowId, workflowExecutionId, authorization string, configuration string) error {
+func runUserInput(client *http.Client, action shuffle.Action, workflowId, workflowExecutionId, authorization string, configuration string) error {
timeNow := time.Now().Unix()
- result := ActionResult{
+ result := shuffle.ActionResult{
Action: action,
ExecutionId: workflowExecutionId,
Authorization: authorization,
@@ -1191,7 +1389,7 @@ func runUserInput(client *http.Client, action Action, workflowId, workflowExecut
return err
}
- log.Printf("[INFO] Body: %s", string(body))
+ log.Printf("[INFO] User Input Body: %s", string(body))
return nil
}
@@ -1221,8 +1419,8 @@ func runTestExecution(client *http.Client, workflowId, apikey string) (string, s
return "", ""
}
- log.Printf("[INFO] Body: %s", string(body))
- var workflowExecution WorkflowExecution
+ log.Printf("[INFO] Test Body: %s", string(body))
+ var workflowExecution shuffle.WorkflowExecution
err = json.Unmarshal(body, &workflowExecution)
if err != nil {
log.Printf("Failed workflowExecution unmarshal: %s", err)
@@ -1232,6 +1430,823 @@ func runTestExecution(client *http.Client, workflowId, apikey string) (string, s
return workflowExecution.Authorization, workflowExecution.ExecutionId
}
+func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) {
+ body, err := ioutil.ReadAll(request.Body)
+ if err != nil {
+ log.Println("(3) Failed reading body for workflowqueue")
+ resp.WriteHeader(401)
+ resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
+ return
+ }
+
+ //log.Printf("Got result: %s", string(body))
+ var actionResult shuffle.ActionResult
+ err = json.Unmarshal(body, &actionResult)
+ if err != nil {
+ log.Printf("Failed shuffle.ActionResult unmarshaling: %s", err)
+ resp.WriteHeader(401)
+ resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
+ return
+ }
+
+ // 1. Get the shuffle.WorkflowExecution(ExecutionId) from the database
+ // 2. if shuffle.ActionResult.Authentication != shuffle.WorkflowExecution.Authentication -> exit
+ // 3. Add to and update actionResult in workflowExecution
+ // 4. Push to db
+ // IF FAIL: Set executionstatus: abort or cancel
+
+ ctx := context.Background()
+ workflowExecution, err := getWorkflowExecution(ctx, actionResult.ExecutionId)
+ if err != nil {
+ log.Printf("[ERROR] Failed getting execution (workflowqueue) %s: %s", actionResult.ExecutionId, err)
+ resp.WriteHeader(401)
+ resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed getting execution ID %s because it doesn't exist."}`, actionResult.ExecutionId)))
+ return
+ }
+
+ if workflowExecution.Authorization != actionResult.Authorization {
+ log.Printf("[INFO] Bad authorization key when updating node (workflowQueue) %s. Want: %s, Have: %s", actionResult.ExecutionId, workflowExecution.Authorization, actionResult.Authorization)
+ resp.WriteHeader(401)
+ resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad authorization key"}`)))
+ return
+ }
+
+ if workflowExecution.Status == "FINISHED" {
+ log.Printf("Workflowexecution is already FINISHED. No further action can be taken")
+ resp.WriteHeader(401)
+ resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Workflowexecution is already finished because of %s with status %s"}`, workflowExecution.LastNode, workflowExecution.Status)))
+ return
+ }
+
+ // Not sure what's up here
+ // FIXME - remove comment
+ if workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" {
+
+ if workflowExecution.Workflow.Configuration.ExitOnError {
+ log.Printf("Workflowexecution already has status %s. No further action can be taken", workflowExecution.Status)
+ resp.WriteHeader(401)
+ resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Workflowexecution is aborted because of %s with result %s and status %s"}`, workflowExecution.LastNode, workflowExecution.Result, workflowExecution.Status)))
+ return
+ } else {
+ log.Printf("Continuing even though it's aborted.")
+ }
+ }
+
+ //if actionResult.Status == "WAITING" && actionResult.Action.AppName == "User Input" {
+ // log.Printf("SHOULD WAIT A BIT AND RUN CLOUD STUFF WITH USER INPUT! WAITING!")
+
+ // var trigger shuffle.Trigger
+ // err = json.Unmarshal([]byte(actionResult.Result), &trigger)
+ // if err != nil {
+ // log.Printf("Failed unmarshaling actionresult for user input: %s", err)
+ // resp.WriteHeader(401)
+ // resp.Write([]byte(`{"success": false}`))
+ // return
+ // }
+
+ // orgId := workflowExecution.ExecutionOrg
+ // if len(workflowExecution.OrgId) == 0 && len(workflowExecution.Workflow.OrgId) > 0 {
+ // orgId = workflowExecution.Workflow.OrgId
+ // }
+
+ // err := handleUserInput(trigger, orgId, workflowExecution.Workflow.ID, workflowExecution.ExecutionId)
+ // if err != nil {
+ // log.Printf("Failed userinput handler: %s", err)
+ // actionResult.Result = fmt.Sprintf("Cloud error: %s", err)
+ // workflowExecution.Results = append(workflowExecution.Results, actionResult)
+ // workflowExecution.Status = "ABORTED"
+ // err = setshuffle.WorkflowExecution(ctx, *workflowExecution, true)
+ // if err != nil {
+ // log.Printf("Failed ")
+ // } else {
+ // log.Printf("Successfully set the execution to waiting.")
+ // }
+
+ // resp.WriteHeader(401)
+ // resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Error: %s"}`, err)))
+ // } else {
+ // log.Printf("Successful userinput handler")
+ // resp.WriteHeader(200)
+ // resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "CLOUD IS DONE"}`)))
+
+ // actionResult.Result = "Waiting for user feedback based on configuration"
+
+ // workflowExecution.Results = append(workflowExecution.Results, actionResult)
+ // workflowExecution.Status = actionResult.Status
+ // err = setshuffle.WorkflowExecution(ctx, *workflowExecution, true)
+ // if err != nil {
+ // log.Printf("Failed ")
+ // } else {
+ // log.Printf("Successfully set the execution to waiting.")
+ // }
+ // }
+
+ // return
+ //}
+
+ resp.WriteHeader(200)
+ resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
+ runWorkflowExecutionTransaction(ctx, 0, workflowExecution.ExecutionId, actionResult, resp)
+
+}
+
+func findChildNodes(workflowExecution shuffle.WorkflowExecution, nodeId string) []string {
+ //log.Printf("\nNODE TO FIX: %s\n\n", nodeId)
+ allChildren := []string{nodeId}
+
+ // 1. Find children of this specific node
+ // 2. Find the children of those nodes etc.
+ for _, branch := range workflowExecution.Workflow.Branches {
+ if branch.SourceID == nodeId {
+ //log.Printf("Children: %s", branch.DestinationID)
+ allChildren = append(allChildren, branch.DestinationID)
+
+ childNodes := findChildNodes(workflowExecution, branch.DestinationID)
+ for _, bottomChild := range childNodes {
+ found := false
+ for _, topChild := range allChildren {
+ if topChild == bottomChild {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ allChildren = append(allChildren, bottomChild)
+ }
+ }
+ }
+ }
+
+ // Remove potential duplicates
+ newNodes := []string{}
+ for _, tmpnode := range allChildren {
+ found := false
+ for _, newnode := range newNodes {
+ if newnode == tmpnode {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ newNodes = append(newNodes, tmpnode)
+ }
+ }
+
+ return newNodes
+}
+
+// Will make sure transactions are always ran for an execution. This is recursive if it fails. Allowed to fail up to 5 times
+func runWorkflowExecutionTransaction(ctx context.Context, attempts int64, workflowExecutionId string, actionResult shuffle.ActionResult, resp http.ResponseWriter) {
+ //log.Printf("IN WORKFLOWEXECUTION SUB!")
+ // Should start a tx for the execution here
+ workflowExecution, err := getWorkflowExecution(ctx, workflowExecutionId)
+ if err != nil {
+ log.Printf("[ERROR] Failed getting execution cache: %s", err)
+ resp.WriteHeader(401)
+ resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed getting execution"}`)))
+ return
+ }
+
+ log.Printf(`[INFO] Got result %s from %s`, actionResult.Status, actionResult.Action.ID)
+ resultLength := len(workflowExecution.Results)
+ dbSave := false
+ setExecution := true
+ //tx, err := dbclient.NewTransaction(ctx)
+ //if err != nil {
+ // log.Printf("client.NewTransaction: %v", err)
+ // resp.WriteHeader(401)
+ // resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed creating transaction"}`)))
+ // return
+ //}
+
+ //key := datastore.NameKey("workflowexecution", workflowExecutionId, nil)
+ //workflowExecution := &shuffle.WorkflowExecution{}
+ //if err := tx.Get(key, workflowExecution); err != nil {
+ // log.Printf("[ERROR] tx.Get bug: %v", err)
+ // tx.Rollback()
+ // resp.WriteHeader(401)
+ // resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed getting the workflow key"}`)))
+ // return
+ //}
+ actionResult.Action = shuffle.Action{
+ AppName: actionResult.Action.AppName,
+ AppVersion: actionResult.Action.AppVersion,
+ Label: actionResult.Action.Label,
+ Name: actionResult.Action.Name,
+ ID: actionResult.Action.ID,
+ Parameters: actionResult.Action.Parameters,
+ }
+
+ if actionResult.Status == "ABORTED" || actionResult.Status == "FAILURE" {
+ //dbSave = true
+
+ newResults := []shuffle.ActionResult{}
+ childNodes := []string{}
+ if workflowExecution.Workflow.Configuration.ExitOnError {
+ log.Printf("[WARNING] shuffle.Actionresult is %s for node %s in %s. Should set workflowExecution and exit all running functions", actionResult.Status, actionResult.Action.ID, workflowExecution.ExecutionId)
+ workflowExecution.Status = actionResult.Status
+ workflowExecution.LastNode = actionResult.Action.ID
+ // Find underlying nodes and add them
+ } else {
+ log.Printf("[WARNING] shuffle.Actionresult is %s for node %s in %s. Continuing anyway because of workflow configuration.", actionResult.Status, actionResult.Action.ID, workflowExecution.ExecutionId)
+ // Finds ALL childnodes to set them to SKIPPED
+ // Remove duplicates
+ //log.Printf("CHILD NODES: %d", len(childNodes))
+ childNodes = findChildNodes(*workflowExecution, actionResult.Action.ID)
+ for _, nodeId := range childNodes {
+ if nodeId == actionResult.Action.ID {
+ continue
+ }
+
+ // 1. Find the action itself
+ // 2. Create an actionresult
+ curAction := shuffle.Action{ID: ""}
+ for _, action := range workflowExecution.Workflow.Actions {
+ if action.ID == nodeId {
+ curAction = action
+ break
+ }
+ }
+
+ if len(curAction.ID) == 0 {
+ log.Printf("Couldn't find subnode %s", nodeId)
+ continue
+ }
+
+ resultExists := false
+ for _, result := range workflowExecution.Results {
+ if result.Action.ID == curAction.ID {
+ resultExists = true
+ break
+ }
+ }
+
+ if !resultExists {
+ // Check parents are done here. Only add it IF all parents are skipped
+ skipNodeAdd := false
+ for _, branch := range workflowExecution.Workflow.Branches {
+ if branch.DestinationID == nodeId {
+ // If the branch's source node is NOT in childNodes, it's not a skipped parent
+ sourceNodeFound := false
+ for _, item := range childNodes {
+ if item == branch.SourceID {
+ sourceNodeFound = true
+ break
+ }
+ }
+
+ if !sourceNodeFound {
+ // FIXME: Shouldn't add skip for child nodes of these nodes. Check if this node is parent of upcoming nodes.
+ log.Printf("\n\n NOT setting node %s to SKIPPED", nodeId)
+ skipNodeAdd = true
+
+ if !arrayContains(visited, nodeId) && !arrayContains(executed, nodeId) {
+ nextActions = append(nextActions, nodeId)
+ log.Printf("SHOULD EXECUTE NODE %s. Next actions: %s", nodeId, nextActions)
+ }
+ break
+ }
+ }
+ }
+
+ if !skipNodeAdd {
+ newResult := shuffle.ActionResult{
+ Action: curAction,
+ ExecutionId: actionResult.ExecutionId,
+ Authorization: actionResult.Authorization,
+ Result: "Skipped because of previous node",
+ StartedAt: 0,
+ CompletedAt: 0,
+ Status: "SKIPPED",
+ }
+
+ newResults = append(newResults, newResult)
+ } else {
+ //log.Printf("\n\nNOT adding %s as skipaction - should add to execute?", nodeId)
+ //var visited []string
+ //var executed []string
+ //var nextActions []string
+ }
+ }
+ }
+ }
+
+ // Cleans up aborted, and always gives a result
+ lastResult := ""
+ // type shuffle.ActionResult struct {
+ for _, result := range workflowExecution.Results {
+ if actionResult.Action.ID == result.Action.ID {
+ continue
+ }
+
+ if result.Status == "EXECUTING" {
+ result.Status = actionResult.Status
+ result.Result = "Aborted because of error in another node (2)"
+ }
+
+ if len(result.Result) > 0 {
+ lastResult = result.Result
+ }
+
+ newResults = append(newResults, result)
+ }
+
+ workflowExecution.Result = lastResult
+ workflowExecution.Results = newResults
+ }
+
+ // FIXME rebuild to be like this or something
+ // workflowExecution/ExecutionId/Nodes/NodeId
+ // Find the appropriate action
+ if len(workflowExecution.Results) > 0 {
+ // FIXME
+ skip := false
+ found := false
+ outerindex := 0
+ for index, item := range workflowExecution.Results {
+ if item.Action.ID == actionResult.Action.ID {
+ found = true
+
+ if item.Status == actionResult.Status {
+ skip = true
+ }
+
+ outerindex = index
+ break
+ }
+ }
+
+ if skip {
+ //log.Printf("Both are %s. Skipping this node", item.Status)
+ } else if found {
+ // If result exists and execution variable exists, update execution value
+ //log.Printf("Exec var backend: %s", workflowExecution.Results[outerindex].Action.ExecutionVariable.Name)
+ // Finds potential execution arguments
+ actionVarName := workflowExecution.Results[outerindex].Action.ExecutionVariable.Name
+ if len(actionVarName) > 0 {
+ log.Printf("EXECUTION VARIABLE LOCAL: %s", actionVarName)
+ for index, execvar := range workflowExecution.ExecutionVariables {
+ if execvar.Name == actionVarName {
+ // Sets the value for the variable
+ workflowExecution.ExecutionVariables[index].Value = actionResult.Result
+ break
+ }
+ }
+ }
+
+ log.Printf("[INFO] Updating %s in workflow %s from %s to %s", actionResult.Action.ID, workflowExecution.ExecutionId, workflowExecution.Results[outerindex].Status, actionResult.Status)
+ workflowExecution.Results[outerindex] = actionResult
+ } else {
+ workflowExecution.Results = append(workflowExecution.Results, actionResult)
+ log.Printf("[INFO] Setting value (1) of %s in execution %s to %s. New result length: %d", actionResult.Action.ID, workflowExecution.ExecutionId, actionResult.Status, len(workflowExecution.Results))
+ }
+ } else {
+ workflowExecution.Results = append(workflowExecution.Results, actionResult)
+ log.Printf("[INFO] Setting value (2) of %s in execution %s to %s. New result length: %d", actionResult.Action.ID, workflowExecution.ExecutionId, actionResult.Status, len(workflowExecution.Results))
+ }
+
+ if actionResult.Status == "SKIPPED" {
+ log.Printf("\n\n[INFO] Handling special case for SKIPPED!\n\n")
+ childNodes := findChildNodes(*workflowExecution, actionResult.Action.ID)
+ for _, nodeId := range childNodes {
+ if nodeId == actionResult.Action.ID {
+ continue
+ }
+
+ // 1. Find the action itself
+ // 2. Create an actionresult
+ curAction := shuffle.Action{ID: ""}
+ for _, action := range workflowExecution.Workflow.Actions {
+ if action.ID == nodeId {
+ curAction = action
+ break
+ }
+ }
+
+ if len(curAction.ID) == 0 {
+ log.Printf("Couldn't find subnode %s", nodeId)
+ continue
+ }
+
+ resultExists := false
+ for _, result := range workflowExecution.Results {
+ if result.Action.ID == curAction.ID {
+ resultExists = true
+ break
+ }
+ }
+
+ if !resultExists {
+ // Check parents are done here. Only add it IF all parents are skipped
+ skipNodeAdd := false
+ for _, branch := range workflowExecution.Workflow.Branches {
+ if branch.DestinationID == nodeId {
+ // If the branch's source node is NOT in childNodes, it's not a skipped parent
+ sourceNodeFound := false
+ for _, item := range childNodes {
+ if item == branch.SourceID {
+ sourceNodeFound = true
+ break
+ }
+ }
+
+ if !sourceNodeFound {
+ log.Printf("[INFO] Not setting node %s to SKIPPED", nodeId)
+ skipNodeAdd = true
+ break
+ }
+ }
+ }
+
+ if !skipNodeAdd {
+ newAction := shuffle.Action{
+ AppName: curAction.AppName,
+ AppVersion: curAction.AppVersion,
+ Label: curAction.Label,
+ Name: curAction.Name,
+ ID: curAction.ID,
+ }
+
+ newResult := shuffle.ActionResult{
+ Action: newAction,
+ ExecutionId: actionResult.ExecutionId,
+ Authorization: actionResult.Authorization,
+ Result: "Skipped because of previous node",
+ StartedAt: 0,
+ CompletedAt: 0,
+ Status: "SKIPPED",
+ }
+
+ workflowExecution.Results = append(workflowExecution.Results, newResult)
+ }
+ }
+ }
+ }
+
+ // FIXME: Have a check for skippednodes and their parents
+ /*
+ for resultIndex, result := range workflowExecution.Results {
+ if result.Status != "SKIPPED" {
+ continue
+ }
+
+ // Checks if all parents are skipped or failed.
+ // Otherwise removes them from the results
+ for _, branch := range workflowExecution.Workflow.Branches {
+ if branch.DestinationID == result.Action.ID {
+ for _, subresult := range workflowExecution.Results {
+ if subresult.Action.ID == branch.SourceID {
+ if subresult.Status != "SKIPPED" && subresult.Status != "FAILURE" {
+ //log.Printf("SUBRESULT PARENT STATUS: %s", subresult.Status)
+ //log.Printf("Should remove resultIndex: %d", resultIndex)
+
+ // FIXME: Reinstate this?
+ //workflowExecution.Results = append(workflowExecution.Results[:resultIndex], workflowExecution.Results[resultIndex+1:]...)
+ _ = resultIndex
+
+ break
+ }
+ }
+ }
+ }
+ }
+ }
+
+ log.Printf("NEW LENGTH: %d", len(workflowExecution.Results))
+ */
+
+ extraInputs := 0
+ for _, trigger := range workflowExecution.Workflow.Triggers {
+ if trigger.Name == "User Input" && trigger.AppName == "User Input" {
+ extraInputs += 1
+ } else if trigger.Name == "Shuffle Workflow" && trigger.AppName == "Shuffle Workflow" {
+ extraInputs += 1
+ }
+ }
+
+ //log.Printf("EXTRA: %d", extraInputs)
+ //log.Printf("LENGTH: %d - %d", len(workflowExecution.Results), len(workflowExecution.Workflow.Actions)+extraInputs)
+
+ if len(workflowExecution.Results) == len(workflowExecution.Workflow.Actions)+extraInputs {
+ //log.Printf("\nIN HERE WITH RESULTS %d vs %d\n", len(workflowExecution.Results), len(workflowExecution.Workflow.Actions)+extraInputs)
+ finished := true
+ lastResult := ""
+
+ // Doesn't have to be SUCCESS and FINISHED everywhere anymore.
+ skippedNodes := false
+ for _, result := range workflowExecution.Results {
+ if result.Status == "EXECUTING" {
+ finished = false
+ break
+ }
+
+ // FIXME: Check if ALL parents are skipped or if its just one. Otherwise execute it
+ if result.Status == "SKIPPED" {
+ skippedNodes = true
+
+ // Checks if all parents are skipped or failed. Otherwise removes them from the results
+ for _, branch := range workflowExecution.Workflow.Branches {
+ if branch.DestinationID == result.Action.ID {
+ for _, subresult := range workflowExecution.Results {
+ if subresult.Action.ID == branch.SourceID {
+ if subresult.Status != "SKIPPED" && subresult.Status != "FAILURE" {
+ //log.Printf("SUBRESULT PARENT STATUS: %s", subresult.Status)
+ //log.Printf("Should remove resultIndex: %d", resultIndex)
+ finished = false
+ break
+ }
+ }
+ }
+ }
+
+ if !finished {
+ break
+ }
+ }
+ }
+
+ lastResult = result.Result
+ }
+
+ // FIXME: Handle skip nodes - change status?
+ _ = skippedNodes
+
+ if finished {
+ dbSave = true
+ log.Printf("[INFO] Execution of %s finished.", workflowExecution.ExecutionId)
+ //log.Println("Might be finished based on length of results and everything being SUCCESS or FINISHED - VERIFY THIS. Setting status to finished.")
+
+ workflowExecution.Result = lastResult
+ workflowExecution.Status = "FINISHED"
+ workflowExecution.CompletedAt = int64(time.Now().Unix())
+ if workflowExecution.LastNode == "" {
+ workflowExecution.LastNode = actionResult.Action.ID
+ }
+
+ }
+ }
+
+ // FIXME - why isn't this how it works otherwise, wtf?
+ //workflow, err := getWorkflow(workflowExecution.Workflow.ID)
+ //newActions := []Action{}
+ //for _, action := range workflowExecution.Workflow.Actions {
+ // log.Printf("Name: %s, Env: %s", action.Name, action.Environment)
+ //}
+
+ tmpJson, err := json.Marshal(workflowExecution)
+ if err == nil {
+ if len(tmpJson) >= 1048487 {
+ dbSave = true
+ log.Printf("[ERROR] Result length is too long! Need to reduce result size")
+
+ // Result string `json:"result" datastore:"result,noindex"`
+ // Arbitrary reduction size
+ maxSize := 500000
+ newResults := []shuffle.ActionResult{}
+ for _, item := range workflowExecution.Results {
+ if len(item.Result) > maxSize {
+ item.Result = "[ERROR] Result too large to handle (https://github.com/frikky/shuffle/issues/171)"
+ }
+
+ newResults = append(newResults, item)
+ }
+
+ workflowExecution.Results = newResults
+ }
+ }
+
+ // Validating that action results hasn't changed
+ // Handled using cachhing, so actually pretty fast
+ cacheKey := fmt.Sprintf("workflowexecution-%s", workflowExecution.ExecutionId)
+ if value, found := requestCache.Get(cacheKey); found {
+ parsedValue := value.(*shuffle.WorkflowExecution)
+ if len(parsedValue.Results) > 0 && len(parsedValue.Results) != resultLength {
+ setExecution = false
+ if attempts > 5 {
+ //log.Printf("\n\nSkipping execution input - %d vs %d. Attempts: (%d)\n\n", len(parsedValue.Results), resultLength, attempts)
+ }
+
+ attempts += 1
+ if len(workflowExecution.Results) <= len(workflowExecution.Workflow.Actions) {
+ runWorkflowExecutionTransaction(ctx, attempts, workflowExecutionId, actionResult, resp)
+ return
+ }
+ }
+ }
+
+ if setExecution || workflowExecution.Status == "FINISHED" || workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" {
+ err = setWorkflowExecution(ctx, *workflowExecution, dbSave)
+ if err != nil {
+ resp.WriteHeader(401)
+ resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed setting workflowexecution actionresult: %s"}`, err)))
+ return
+ }
+ } else {
+ log.Printf("[INFO] Skipping setexec with status %s", workflowExecution.Status)
+
+ // Just in case. Should MAYBE validate finishing another time as well.
+ // This fixes issues with e.g. shuffle.Action -> shuffle.Trigger -> shuffle.Action.
+ handleExecutionResult(*workflowExecution)
+ //validateFinished(workflowExecution)
+ }
+
+ //if newExecutions && len(nextActions) > 0 {
+ // handleExecutionResult(*workflowExecution)
+ //}
+
+ //resp.WriteHeader(200)
+ //resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
+}
+
+func getWorkflowExecution(ctx context.Context, id string) (*shuffle.WorkflowExecution, error) {
+ //log.Printf("IN GET WORKFLOW EXEC!")
+ cacheKey := fmt.Sprintf("workflowexecution-%s", id)
+ if value, found := requestCache.Get(cacheKey); found {
+ parsedValue := value.(*shuffle.WorkflowExecution)
+ //log.Printf("Found execution for id %s with %d results", parsedValue.ExecutionId, len(parsedValue.Results))
+
+ //validateFinished(*parsedValue)
+ return parsedValue, nil
+ }
+
+ return &shuffle.WorkflowExecution{}, errors.New("No workflowexecution defined yet")
+}
+
+func sendResult(workflowExecution shuffle.WorkflowExecution, data []byte) {
+ fullUrl := fmt.Sprintf("%s/api/v1/streams", baseUrl)
+ req, err := http.NewRequest(
+ "POST",
+ fullUrl,
+ bytes.NewBuffer([]byte(data)),
+ )
+
+ if err != nil {
+ log.Printf("[ERROR] Failed creating finishing request: %s", err)
+ shutdown(workflowExecution, "", "", false)
+ }
+
+ newresp, err := topClient.Do(req)
+ if err != nil {
+ log.Printf("[ERROR] Error running finishing request: %s", err)
+ shutdown(workflowExecution, "", "", false)
+ }
+
+ body, err := ioutil.ReadAll(newresp.Body)
+ log.Printf("[INFO] BACKEND STATUS: %d", newresp.StatusCode)
+ if err != nil {
+ log.Printf("[ERROR] Failed reading body: %s", err)
+ } else {
+ log.Printf("[INFO] NEWRESP (from backend): %s", string(body))
+ }
+}
+
+func validateFinished(workflowExecution shuffle.WorkflowExecution) {
+ 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(environments) == 1 && requestsSent == 0 && len(workflowExecution.Results) >= 1) || (len(workflowExecution.Results) >= len(workflowExecution.Workflow.Actions) && len(workflowExecution.Workflow.Actions) > 0) {
+ requestsSent += 1
+ //log.Printf("[FINISHED] Should send full result to %s", baseUrl)
+
+ //data = fmt.Sprintf(`{"execution_id": "%s", "authorization": "%s"}`, executionId, authorization)
+ data, err := json.Marshal(workflowExecution)
+ if err != nil {
+ log.Printf("[ERROR] Failed to unmarshal data for backend")
+ shutdown(workflowExecution, "", "", true)
+ }
+
+ sendResult(workflowExecution, data)
+ }
+}
+
+func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) {
+ body, err := ioutil.ReadAll(request.Body)
+ if err != nil {
+ log.Println("Failed reading body for stream result queue")
+ resp.WriteHeader(401)
+ resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
+ return
+ }
+
+ var actionResult shuffle.ActionResult
+ err = json.Unmarshal(body, &actionResult)
+ if err != nil {
+ log.Printf("Failed shuffle.ActionResult unmarshaling: %s", err)
+ resp.WriteHeader(401)
+ resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
+ return
+ }
+
+ ctx := context.Background()
+ workflowExecution, err := getWorkflowExecution(ctx, actionResult.ExecutionId)
+ if err != nil {
+ //log.Printf("Failed getting execution (streamresult) %s: %s", actionResult.ExecutionId, err)
+ resp.WriteHeader(401)
+ resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad authorization key or execution_id might not exist."}`)))
+ return
+ }
+
+ // Authorization is done here
+ if workflowExecution.Authorization != actionResult.Authorization {
+ log.Printf("Bad authorization key when getting stream results %s.", actionResult.ExecutionId)
+ resp.WriteHeader(401)
+ resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad authorization key or execution_id might not exist."}`)))
+ return
+ }
+
+ newjson, err := json.Marshal(workflowExecution)
+ if err != nil {
+ resp.WriteHeader(401)
+ resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed unpacking workflow execution"}`)))
+ return
+ }
+
+ resp.WriteHeader(200)
+ resp.Write(newjson)
+
+}
+
+func setWorkflowExecution(ctx context.Context, workflowExecution shuffle.WorkflowExecution, dbSave bool) error {
+ //log.Printf("IN SET WORKFLOW EXEC!")
+ //log.Printf("\n\n\nRESULT: %s\n\n\n", workflowExecution.Status)
+ if len(workflowExecution.ExecutionId) == 0 {
+ log.Printf("Workflowexeciton executionId can't be empty.")
+ return errors.New("ExecutionId can't be empty.")
+ }
+
+ cacheKey := fmt.Sprintf("workflowexecution-%s", workflowExecution.ExecutionId)
+ requestCache.Set(cacheKey, &workflowExecution, cache.DefaultExpiration)
+
+ handleExecutionResult(workflowExecution)
+ validateFinished(workflowExecution)
+ if dbSave {
+ shutdown(workflowExecution, "", "", false)
+ }
+
+ return nil
+}
+
+// GetLocalIP returns the non loopback local IP of the host
+func getLocalIP() string {
+ addrs, err := net.InterfaceAddrs()
+ if err != nil {
+ return ""
+ }
+ for _, address := range addrs {
+ // check the address type and if it is not a loopback the display it
+ if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
+ if ipnet.IP.To4() != nil {
+ return ipnet.IP.String()
+ }
+ }
+ }
+ return ""
+}
+
+func getAvailablePort() (net.Listener, error) {
+ listener, err := net.Listen("tcp", ":0")
+ if err != nil {
+ log.Printf("[WARNING] Failed to assign port by default. Defaulting to 5001")
+ //return ":5001"
+ return nil, err
+ }
+
+ return listener, nil
+ //return fmt.Sprintf(":%d", port)
+}
+
+func webserverSetup(workflowExecution shuffle.WorkflowExecution) net.Listener {
+ hostname := getLocalIP()
+
+ // FIXME: This MAY not work because of speed between first
+ // container being launched and port being assigned to webserver
+ listener, err := getAvailablePort()
+ if err != nil {
+ log.Printf("Failed to created listener: %s", err)
+ shutdown(workflowExecution, "", "", true)
+ }
+ port := listener.Addr().(*net.TCPAddr).Port
+
+ log.Printf("\n\nStarting webserver on port %d with hostname: %s\n\n", port, hostname)
+ log.Printf("OLD HOSTNAME: %s", appCallbackUrl)
+ appCallbackUrl = fmt.Sprintf("http://%s:%d", hostname, port)
+ log.Printf("NEW HOSTNAME: %s", appCallbackUrl)
+
+ return listener
+}
+
+func runWebserver(listener net.Listener) {
+ r := mux.NewRouter()
+ r.HandleFunc("/api/v1/streams", handleWorkflowQueue).Methods("POST")
+ r.HandleFunc("/api/v1/streams/results", handleGetStreamResults).Methods("POST", "OPTIONS")
+ http.Handle("/", r)
+
+ //log.Fatal(http.ListenAndServe(port, nil))
+ log.Fatal(http.Serve(listener, nil))
+}
+
// Initial loop etc
func main() {
log.Printf("[INFO] Setting up worker environment")
@@ -1270,20 +2285,23 @@ func main() {
} else {
authorization = os.Getenv("AUTHORIZATION")
executionId = os.Getenv("EXECUTIONID")
- log.Printf("Running normal execution with auth %s and ID %s", authorization, executionId)
+ log.Printf("[INFO] Running normal execution with auth %s and ID %s", authorization, executionId)
}
+ workflowExecution := shuffle.WorkflowExecution{
+ ExecutionId: executionId,
+ }
if len(authorization) == 0 {
log.Println("[INFO] No AUTHORIZATION key set in env")
- shutdown(executionId, "")
+ shutdown(workflowExecution, "", "", false)
}
if len(executionId) == 0 {
log.Println("[INFO] No EXECUTIONID key set in env")
- shutdown(executionId, "")
+ shutdown(workflowExecution, "", "", false)
}
- data := fmt.Sprintf(`{"execution_id": "%s", "authorization": "%s"}`, executionId, authorization)
+ data = fmt.Sprintf(`{"execution_id": "%s", "authorization": "%s"}`, executionId, authorization)
fullUrl := fmt.Sprintf("%s/api/v1/streams/results", baseUrl)
req, err := http.NewRequest(
"POST",
@@ -1293,9 +2311,11 @@ func main() {
if err != nil {
log.Println("[ERROR] Failed making request builder for backend")
- shutdown(executionId, "")
+ shutdown(workflowExecution, "", "", true)
}
+ topClient = client
+ firstRequest := true
for {
// Because of this, it always has updated data.
// Removed request requirement from app_sdk
@@ -1314,12 +2334,11 @@ func main() {
}
if newresp.StatusCode != 200 {
- log.Printf("[ERROR] %s\nStatusCode: %d", string(body), newresp.StatusCode)
+ log.Printf("[ERROR] %s\nStatusCode (1): %d", string(body), newresp.StatusCode)
time.Sleep(time.Duration(sleepTime) * time.Second)
continue
}
- var workflowExecution WorkflowExecution
err = json.Unmarshal(body, &workflowExecution)
if err != nil {
log.Printf("[ERROR] Failed workflowExecution unmarshal: %s", err)
@@ -1327,9 +2346,57 @@ func main() {
continue
}
+ if firstRequest {
+ firstRequest = false
+ //workflowExecution.StartedAt = int64(time.Now().Unix())
+
+ cacheKey := fmt.Sprintf("workflowexecution-%s", workflowExecution.ExecutionId)
+ requestCache = cache.New(5*time.Minute, 10*time.Minute)
+ requestCache.Set(cacheKey, &workflowExecution, cache.DefaultExpiration)
+ 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)
+ }
+ }
+
+ log.Printf("Environments: %s. 1 = webserver, 0 or >1 = default", environments)
+ if len(environments) == 1 { //&& workflowExecution.ExecutionSource != "default" {
+ log.Printf("[INFO] Running OPTIMIZED execution (not manual)")
+ listener := webserverSetup(workflowExecution)
+ err := executionInit(workflowExecution)
+ if err != nil {
+ log.Printf("[INFO] Workflow setup failed: %s", workflowExecution.ExecutionId, err)
+ shutdown(workflowExecution, "", "", true)
+ }
+
+ go func() {
+ time.Sleep(time.Duration(1))
+ handleExecutionResult(workflowExecution)
+ }()
+
+ runWebserver(listener)
+ //log.Printf("Before wait")
+ //wg := sync.WaitGroup{}
+ //wg.Add(1)
+ //wg.Wait()
+ } else {
+ log.Printf("[INFO] Running NON-OPTIMIZED execution for type %s with %d environments", workflowExecution.ExecutionSource, len(environments))
+
+ }
+
+ }
+
if workflowExecution.Status == "FINISHED" || workflowExecution.Status == "SUCCESS" {
log.Printf("[INFO] Workflow %s is finished. Exiting worker.", workflowExecution.ExecutionId)
- shutdown(executionId, workflowExecution.Workflow.ID)
+ shutdown(workflowExecution, "", "", true)
}
if workflowExecution.Status == "EXECUTING" || workflowExecution.Status == "RUNNING" {
@@ -1337,11 +2404,11 @@ func main() {
err = handleExecution(client, req, workflowExecution)
if err != nil {
log.Printf("[INFO] Workflow %s is finished: %s", workflowExecution.ExecutionId, err)
- shutdown(executionId, workflowExecution.Workflow.ID)
+ shutdown(workflowExecution, "", "", true)
}
} else {
log.Printf("[INFO] Workflow %s has status %s. Exiting worker.", workflowExecution.ExecutionId, workflowExecution.Status)
- shutdown(executionId, workflowExecution.Workflow.ID)
+ shutdown(workflowExecution, workflowExecution.Workflow.ID, "", true)
}
time.Sleep(time.Duration(sleepTime) * time.Second)
diff --git a/functions/stitcher.go b/functions/stitcher.go
deleted file mode 100644
index aae060f3..00000000
--- a/functions/stitcher.go
+++ /dev/null
@@ -1,703 +0,0 @@
-package main
-
-import (
- "archive/zip"
- "bytes"
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "log"
- "net/http"
- "os"
- "path/filepath"
- "strings"
-
- "archive/tar"
- "cloud.google.com/go/storage"
- "github.com/docker/docker/api/types"
- "github.com/docker/docker/client"
- "google.golang.org/api/cloudfunctions/v1"
- "gopkg.in/yaml.v2"
-)
-
-var gceProject = "shuffler"
-var bucketName = "shuffler.appspot.com"
-
-type WorkflowAppActionParameter struct {
- Description string `json:"description" datastore:"description"`
- ID string `json:"id" datastore:"id"`
- Name string `json:"name" datastore:"name"`
- Example string `json:"example" datastore:"example"`
- Value string `json:"value" datastore:"value"`
- Multiline bool `json:"multiline" datastore:"multiline"`
- ActionField string `json:"action_field" datastore:"action_field"`
- Variant string `json:"variant", datastore:"variant"`
- Required bool `json:"required" datastore:"required"`
- Schema struct {
- Type string `json:"type" datastore:"type"`
- } `json:"schema"`
-}
-
-type Authentication struct {
- Required bool `json:"required" datastore:"required" yaml:"required" `
- Parameters []AuthenticationParams `json:"parameters" datastore:"parameters" yaml:"parameters"`
-}
-
-type AuthenticationParams struct {
- Description string `json:"description" datastore:"description" yaml:"description"`
- ID string `json:"id" datastore:"id" yaml:"id"`
- Name string `json:"name" datastore:"name" yaml:"name"`
- Example string `json:"example" datastore:"example" yaml:"example"`
- Value string `json:"value" datastore:"value" yaml:"value"`
- Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"`
- Required bool `json:"required" datastore:"required" yaml:"required"`
-}
-
-type WorkflowApp struct {
- Name string `json:"name" yaml:"name" required:true datastore:"name"`
- IsValid bool `json:"is_valid" yaml:"is_valid" required:true datastore:"is_valid"`
- ID string `json:"id" yaml:"id" required:false datastore:"id"`
- Link string `json:"link" yaml:"link" required:false datastore:"link"`
- AppVersion string `json:"app_version" yaml:"app_version" required:true datastore:"app_version"`
- Description string `json:"description" datastore:"description" required:false yaml:"description"`
- Environment string `json:"environment" datastore:"environment" required:true yaml:"environment"`
- Sharing bool `json:"sharing" datastore:"sharing" yaml:"sharing"`
- SmallImage string `json:"small_image" datastore:"small_image" required:false yaml:"small_image"`
- LargeImage string `json:"large_image" datastore:"large_image" yaml:"large_image" requred:false`
- ContactInfo struct {
- Name string `json:"name" datastore:"name" yaml:"name"`
- Url string `json:"url" datastore:"url" yaml:"url"`
- } `json:"contact_info" datastore:"contact_info" yaml:"contact_info" required:false`
- Actions []WorkflowAppAction `json:"actions" yaml:"actions" required:true datastore:"actions"`
- Authentication Authentication `json:"authentication" yaml:"authentication" required:false datastore:"authentication"`
-}
-
-type AuthenticationStore struct {
- Key string `json:"key" datastore:"key"`
- Value string `json:"value" datastore:"value"`
-}
-
-type WorkflowAppAction struct {
- Description string `json:"description" datastore:"description"`
- ID string `json:"id" datastore:"id"`
- Name string `json:"name" datastore:"name"`
- NodeType string `json:"node_type" datastore:"node_type"`
- Environment string `json:"environment" datastore:"environment"`
- Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters"`
- Authentication []AuthenticationStore `json:"authentication" datastore:"authentication"`
- Returns struct {
- Description string `json:"description" datastore:"returns"`
- ID string `json:"id" datastore:"id"`
- Schema struct {
- Type string `json:"type" datastore:"type"`
- } `json:"schema" datastore:"schema"`
- } `json:"returns" datastore:"returns"`
-}
-
-func getRunner(classname string) string {
- return fmt.Sprintf(`
-# Run the actual thing after we've checked params
-def run(request):
- action = request.get_json()
- print(action)
- print(type(action))
- authorization_key = action.get("authorization")
- current_execution_id = action.get("execution_id")
-
- if action and "name" in action and "app_name" in action:
- asyncio.run(%s.run(action), debug=True)
- return f'Attempting to execute function {action["name"]} in app {action["app_name"]}'
- else:
- return f'Invalid action'
-
- `, classname)
-}
-
-// Could use some kind of linting system too for this, but meh
-func formatAppfile(filedata []byte) (string, []byte) {
- lines := strings.Split(string(filedata), "\n")
-
- newfile := []string{}
- classname := ""
- for _, line := range lines {
- if strings.Contains(line, "walkoff_app_sdk") {
- continue
- }
-
- // Remap logging. CBA this right now
- // This issue also persists in onprem apps because of await thingies.. :(
- // FIXME
- if strings.Contains(line, "console_logger") && strings.Contains(line, "await") {
- continue
- //line = strings.Replace(line, "console_logger", "logger", -1)
- //log.Println(line)
- }
-
- // Might not work with different import names
- // Could be fucked up with spaces everywhere? Idk
- if strings.Contains(line, "class") && strings.Contains(line, "(AppBase)") {
- items := strings.Split(line, " ")
- if len(items) > 0 && strings.Contains(items[1], "(AppBase)") {
- classname = strings.Split(items[1], "(")[0]
- } else {
- log.Println("Something wrong :( (horrible programming right here)")
- os.Exit(3)
- }
- }
-
- if strings.Contains(line, "if __name__ ==") {
- break
- }
-
- // asyncio.run(HelloWorld.run(), debug=True)
-
- newfile = append(newfile, line)
- }
-
- filedata = []byte(strings.Join(newfile, "\n"))
- return classname, filedata
-}
-
-// https://stackoverflow.com/questions/21060945/simple-way-to-copy-a-file-in-golang
-func Copy(src, dst string) error {
- in, err := os.Open(src)
- if err != nil {
- return err
- }
- defer in.Close()
-
- out, err := os.Create(dst)
- if err != nil {
- return err
- }
- defer out.Close()
-
- _, err = io.Copy(out, in)
- if err != nil {
- return err
- }
- return out.Close()
-}
-func ZipFiles(filename string, files []string) error {
- newZipFile, err := os.Create(filename)
- if err != nil {
- return err
- }
- defer newZipFile.Close()
-
- zipWriter := zip.NewWriter(newZipFile)
- defer zipWriter.Close()
-
- // Add files to zip
- for _, file := range files {
- zipfile, err := os.Open(file)
- if err != nil {
- return err
- }
- defer zipfile.Close()
-
- // Get the file information
- info, err := zipfile.Stat()
- if err != nil {
- return err
- }
-
- header, err := zip.FileInfoHeader(info)
- if err != nil {
- return err
- }
-
- // Using FileInfoHeader() above only uses the basename of the file. If we want
- // to preserve the folder structure we can overwrite this with the full path.
- filesplit := strings.Split(file, "/")
- if len(filesplit) > 1 {
- header.Name = filesplit[len(filesplit)-1]
- } else {
- header.Name = file
- }
-
- // Change to deflate to gain better compression
- // see http://golang.org/pkg/archive/zip/#pkg-constants
- header.Method = zip.Deflate
-
- writer, err := zipWriter.CreateHeader(header)
- if err != nil {
- return err
- }
- if _, err = io.Copy(writer, zipfile); err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func getAppbase(filepath string) []string {
- appBase, err := ioutil.ReadFile(filepath)
- if err != nil {
- log.Printf("Readerror: %s", err)
- os.Exit(1)
- }
-
- record := false
- validLines := []string{}
- for _, line := range strings.Split(string(appBase), "\n") {
- if strings.Contains(line, "#STOPCOPY") {
- log.Println("Stopping copy")
- break
- }
-
- if record {
- validLines = append(validLines, line)
- }
-
- if strings.Contains(line, "#STARTCOPY") {
- log.Println("Starting copy")
- record = true
- }
- }
-
- return validLines
-}
-
-// Puts together ./static_baseline.py, onprem/app_sdk_app_base.py and the
-// appcode in a generated_app folder based on appname+version
-func stitcher(appname string, appversion string) string {
- baselinefile := "static_baseline.py"
- appfolder := "apps"
- appbasefile := "onprem/app_sdk/app_base.py"
-
- baseline, err := ioutil.ReadFile(baselinefile)
- if err != nil {
- log.Printf("Readerror: %s", err)
- os.Exit(1)
- }
-
- sourceappfile := fmt.Sprintf("%s/%s/%s/src/app.py", appfolder, appname, appversion)
- appfile, err := ioutil.ReadFile(sourceappfile)
- if err != nil {
- log.Printf("App readerror: %s", err)
- os.Exit(1)
- }
-
- classname, appfile := formatAppfile(appfile)
- if len(classname) == 0 {
- log.Println("Failed finding classname in file.")
- os.Exit(3)
- }
-
- runner := getRunner(classname)
- appBase := getAppbase(appbasefile)
-
- foldername := fmt.Sprintf("generated_apps/%s_%s", appname, appversion)
- err = os.Mkdir(foldername, os.ModePerm)
- if err != nil {
- log.Println("Failed making temporary app folder. Probably already exists. Remaking")
- os.RemoveAll(foldername)
- os.MkdirAll(foldername, os.ModePerm)
- }
-
- stitched := []byte(string(baseline) + strings.Join(appBase, "\n") + string(appfile) + string(runner))
- err = ioutil.WriteFile(fmt.Sprintf("%s/main.py", foldername), stitched, os.ModePerm)
- if err != nil {
- log.Println("Failed writing to stitched: %s", err)
- os.Exit(3)
- }
-
- err = Copy(fmt.Sprintf("%s/%s/%s/requirements.txt", appfolder, appname, appversion), fmt.Sprintf("%s/requirements.txt", foldername))
- if err != nil {
- log.Println("Failed writing to requirement: %s", err)
- os.Exit(3)
- }
-
- log.Printf("Successfully stitched files in %s/main.py", foldername)
- // Zip the folder
- files := []string{
- fmt.Sprintf("%s/main.py", foldername),
- fmt.Sprintf("%s/requirements.txt", foldername),
- }
- outputfile := fmt.Sprintf("%s.zip", foldername)
-
- err = ZipFiles(outputfile, files)
- if err != nil {
- log.Fatal(err)
- }
-
- ctx := context.Background()
-
- // Creates a client.
- client, err := storage.NewClient(ctx)
- if err != nil {
- log.Printf("Failed to create client: %v", err)
- os.Exit(3)
- }
-
- // Create bucket handle
- bucket := client.Bucket(bucketName)
-
- remotePath := fmt.Sprintf("apps/%s_%s.zip", appname, appversion)
- err = createFileFromFile(bucket, remotePath, outputfile)
- if err != nil {
- log.Printf("Failed to upload to bucket: %v", err)
- os.Exit(3)
- }
-
- os.Remove(outputfile)
- return fmt.Sprintf("gs://%s/apps/%s_%s.zip", bucketName, appname, appversion)
-}
-
-func createFileFromFile(bucket *storage.BucketHandle, remotePath, localPath string) error {
- ctx := context.Background()
- // [START upload_file]
- f, err := os.Open(localPath)
- if err != nil {
- return err
- }
- defer f.Close()
-
- wc := bucket.Object(remotePath).NewWriter(ctx)
- if _, err = io.Copy(wc, f); err != nil {
- return err
- }
- if err := wc.Close(); err != nil {
- return err
- }
- // [END upload_file]
- return nil
-}
-
-// Deploy to google cloud function :)
-func deployFunction(appname, localization, applocation string, environmentVariables map[string]string) error {
- ctx := context.Background()
- service, err := cloudfunctions.NewService(ctx)
- if err != nil {
- return err
- }
-
- // ProjectsLocationsListCall
- projectsLocationsFunctionsService := cloudfunctions.NewProjectsLocationsFunctionsService(service)
- location := fmt.Sprintf("projects/%s/locations/%s", gceProject, localization)
- functionName := fmt.Sprintf("%s/functions/%s", location, appname)
-
- cloudFunction := &cloudfunctions.CloudFunction{
- AvailableMemoryMb: 128,
- EntryPoint: "authorization",
- EnvironmentVariables: environmentVariables,
- HttpsTrigger: &cloudfunctions.HttpsTrigger{},
- MaxInstances: 0,
- Name: functionName,
- Runtime: "python37",
- SourceArchiveUrl: applocation,
- }
-
- //getCall := projectsLocationsFunctionsService.Get(fmt.Sprintf("%s/functions/function-5", location))
- //resp, err := getCall.Do()
-
- createCall := projectsLocationsFunctionsService.Create(location, cloudFunction)
- _, err = createCall.Do()
- if err != nil {
- log.Println("Failed creating new function. Attempting patch, as it might exist already")
-
- createCall := projectsLocationsFunctionsService.Patch(fmt.Sprintf("%s/functions/%s", location, appname), cloudFunction)
- _, err = createCall.Do()
- if err != nil {
- log.Println("Failed patching function")
- return err
- }
-
- log.Printf("Successfully patched %s to %s", appname, localization)
- } else {
- log.Printf("Successfully deployed %s to %s", appname, localization)
- }
-
- // FIXME - use response to define the HTTPS entrypoint. It's default to an easy one tho
-
- return nil
-}
-
-func deployAppCloudFunc(appname string, appversion string) {
- _ = os.Mkdir("generated_apps", os.ModePerm)
-
- apikey := "eyJhbGciOiJSUzI1NiIsImtpZCI6IjYwZjQwNjBlNThkNzVmZDNmNzBiZWZmODhjNzk0YTc3NTMyN2FhMzEiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL3NodWZmbGVyLmlvL2FwaS92MS93b3JrZmxvd3MvMWQ5ZDhjZTItNTY2ZS00YzNmLThhMzctNWQ2YzdkMjAwMGI1L2V4ZWN1dGUiLCJhenAiOiIxMDMwNzY3ODIwNjE0MjQ2MTg0MjIiLCJlbWFpbCI6InNjaGVkdWxlckBzaHVmZmxlLTI0MTUxNy5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjE1NjU1Mjc1NTEsImlhdCI6MTU2NTUyMzk1MSwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTAzMDc2NzgyMDYxNDI0NjE4NDIyIn0.r0EDq9fjhf_5CPTiltyfk_L3uYJp577Uy0yYPcCAl2nv50_z_oUtbWGBpQLL8gcj-NGd3g4E52Qur8k6hCMIQweLS6WAb1279vGffEoCNDfkWb3Oy-yJGP1kzwLvqFJqnHLkSWYXNWvSyWnEimW8Rryx_m1BXS5wcA8l4NIr83kS7fPZrTwjnwFSeGSThwk91DVARzapQb8r0GEgOUyHZ1aBXnV98mikzSUt-5xFKe9eMdD22YJAj0Ru-DxAxs5nOqghX4PMRysWjshjOMrlR1piPWxqAmewp8YKZDCQ5gXskpeAFBDoULT971Wsx_NCohnJsFqx1JfPS9ZYMTW2oQ"
- fullAppname := fmt.Sprintf("%s-%s", strings.Replace(appname, "_", "-", -1), strings.Replace(appversion, ".", "-", -1))
- locations := []string{"europe-west2"}
-
- // Deploys the app to all locations
- bucketname := stitcher(appname, appversion)
- environmentVariables := map[string]string{
- "FUNCTION_APIKEY": apikey,
- }
-
- for _, location := range locations {
- err := deployFunction(fullAppname, location, bucketname, environmentVariables)
- if err != nil {
- log.Printf("Failed to deploy: %s", err)
- os.Exit(3)
- }
- }
-}
-
-func loadYaml(fileLocation string) (WorkflowApp, error) {
- action := WorkflowApp{}
-
- yamlFile, err := ioutil.ReadFile(fileLocation)
- if err != nil {
- log.Printf("yamlFile.Get err: %s", err)
- return WorkflowApp{}, err
- }
-
- //log.Printf(string(yamlFile))
- err = yaml.Unmarshal([]byte(yamlFile), &action)
- if err != nil {
- return WorkflowApp{}, err
- }
-
- return action, nil
-}
-
-// FIXME - deploy to backend (YAML config)
-func deployConfigToBackend(appname string, appversion string) error {
- // FIXME - no static path pls
- action, err := loadYaml(fmt.Sprintf("apps/%s/%s/api.yaml", appname, appversion))
- if err != nil {
- log.Println(err)
- return err
- }
-
- action.Sharing = true
-
- data, err := json.Marshal(action)
- if err != nil {
- return err
- }
-
- url := "http://localhost:5001/api/v1/workflows/apps"
- client := &http.Client{}
- req, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(data))
- if err != nil {
- return err
- }
-
- req.Header.Set("Authorization", "Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjYwZjQwNjBlNThkNzVmZDNmNzBiZWZmODhjNzk0YTc3NTMyN2FhMzEiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL3NodWZmbGVyLmlvL2FwaS92MS93b3JrZmxvd3MvMWQ5ZDhjZTItNTY2ZS00YzNmLThhMzctNWQ2YzdkMjAwMGI1L2V4ZWN1dGUiLCJhenAiOiIxMDMwNzY3ODIwNjE0MjQ2MTg0MjIiLCJlbWFpbCI6InNjaGVkdWxlckBzaHVmZmxlLTI0MTUxNy5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjE1NjU1Mjc1NTEsImlhdCI6MTU2NTUyMzk1MSwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTAzMDc2NzgyMDYxNDI0NjE4NDIyIn0.r0EDq9fjhf_5CPTiltyfk_L3uYJp577Uy0yYPcCAl2nv50_z_oUtbWGBpQLL8gcj-NGd3g4E52Qur8k6hCMIQweLS6WAb1279vGffEoCNDfkWb3Oy-yJGP1kzwLvqFJqnHLkSWYXNWvSyWnEimW8Rryx_m1BXS5wcA8l4NIr83kS7fPZrTwjnwFSeGSThwk91DVARzapQb8r0GEgOUyHZ1aBXnV98mikzSUt-5xFKe9eMdD22YJAj0Ru-DxAxs5nOqghX4PMRysWjshjOMrlR1piPWxqAmewp8YKZDCQ5gXskpeAFBDoULT971Wsx_NCohnJsFqx1JfPS9ZYMTW2oQ")
-
- ret, err := client.Do(req)
- if err != nil {
- return err
- }
-
- log.Printf("Status: %s", ret.Status)
- body, err := ioutil.ReadAll(ret.Body)
- if err != nil {
- return err
- }
-
- if ret.StatusCode != 200 {
- return errors.New(fmt.Sprintf("Status %s. App probably already exists. Raw:\n%s", ret.Status, string(body)))
- }
-
- log.Println(string(body))
- return nil
-}
-
-func tarDirectory(filecontext string) (io.Reader, error) {
-
- // Create a filereader
- //dockerFileReader, err := os.Open(dockerfile)
- //if err != nil {
- // return err
- //}
-
- //// Read the actual Dockerfile
- //readDockerFile, err := ioutil.ReadAll(dockerFileReader)
- //if err != nil {
- // return err
- //}
-
- // Make a TAR header for the file
- tarHeader := &tar.Header{
- Name: filecontext,
- Typeflag: tar.TypeDir,
- }
-
- // Writes the header described for the TAR file
- buf := new(bytes.Buffer)
- tw := tar.NewWriter(buf)
- defer tw.Close()
- err := tw.WriteHeader(tarHeader)
- if err != nil {
- return nil, err
- }
-
- dockerFileTarReader := bytes.NewReader(buf.Bytes())
- return dockerFileTarReader, nil
-}
-
-func tarDir(source string, target string) (*bytes.Reader, error) {
- filename := filepath.Base(source)
- target = filepath.Join(target, fmt.Sprintf("%s.tar", filename))
- tarfile, err := os.Create(target)
- if err != nil {
- return nil, err
- }
-
- defer tarfile.Close()
-
- buf := new(bytes.Buffer)
- _ = buf
- tarball := tar.NewWriter(tarfile)
- defer tarball.Close()
-
- info, err := os.Stat(source)
- if err != nil {
- return nil, err
- }
-
- var baseDir string
- if info.IsDir() {
- baseDir = filepath.Base(source)
- }
-
- _ = filepath.Walk(source,
- func(path string, info os.FileInfo, err error) error {
- if err != nil {
- return err
- }
- header, err := tar.FileInfoHeader(info, info.Name())
- if err != nil {
- return err
- }
-
- if baseDir != "" {
- header.Name = filepath.Join(baseDir, strings.TrimPrefix(path, source))
- }
-
- if err := tarball.WriteHeader(header); err != nil {
- return err
- }
-
- if info.IsDir() {
- return nil
- }
-
- file, err := os.Open(path)
- if err != nil {
- return err
- }
- defer file.Close()
- _, err = io.Copy(tarball, file)
- return nil
- })
-
- dockerFileTarReader := bytes.NewReader(buf.Bytes())
- return dockerFileTarReader, nil
-}
-
-func buildImage(client *client.Client, tags []string, dockerBuildCtxDir string) error {
- dockerBuildContext, err := tarDir(dockerBuildCtxDir, ".")
- if err != nil {
- log.Printf("Error in taring the docker root folder - %s", err.Error())
- return err
- }
-
- imageBuildResponse, err := client.ImageBuild(
- context.Background(),
- dockerBuildContext,
- types.ImageBuildOptions{
- Dockerfile: "Dockerfile",
- PullParent: true,
- Remove: true,
- Tags: tags,
- NetworkMode: "host",
- },
- )
-
- if err != nil {
- return err
- }
-
- // Read the STDOUT from the build process
- defer imageBuildResponse.Body.Close()
- _, err = io.Copy(os.Stdout, imageBuildResponse.Body)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-// FIXME - deploy to dockerhub
-func deployWorker(appname, appversion string) error {
- // Get dockerfile from ./apps/appname/appversion/Dockerfile
- client, err := client.NewEnvClient()
- if err != nil {
- return err
- }
-
- tags := []string{fmt.Sprintf("%s-%s", appname, appversion)}
- err = buildImage(client, tags, fmt.Sprintf("./apps/%s/%s", appname, appversion))
- if err != nil {
- log.Printf("Build error: %s", err)
- return err
- }
-
- return nil
-}
-
-// Deploys all cloud functions. Onprem thooo :(
-func deployAll() {
- allapps := []string{
- "hoxhunt",
- "secureworks",
- "servicenow",
- "lastline",
- "netcraft",
- "misp",
- "email",
- "testing",
- "http",
- "recordedfuture",
- "passivetotal",
- "carbon_black",
- "thehive",
- "cortex",
- "splunk",
- }
-
- for _, appname := range allapps {
- appversion := "1.0.0"
-
- err := deployConfigToBackend(appname, appversion)
- if err != nil {
- log.Printf("Failed uploading config: %s", err)
- continue
- }
-
- deployAppCloudFunc(appname, appversion)
- }
-}
-
-func main() {
- deployAll()
- return
-
- appname := "testing"
- appversion := "1.0.0"
-
- err := deployConfigToBackend(appname, appversion)
- if err != nil {
- log.Printf("Failed uploading config: %s", err)
- os.Exit(1)
- }
-
- deployAppCloudFunc(appname, appversion)
-
- // FIXME - build and deploy to dockerhub as well :)
- // Not able to work in remote directory propely... Even tried making an actual tar and checking it rofl
- //err := deployWorker(appname, appversion)
- //if err != nil {
- // log.Printf("Failed to deploy docker worker: %s", err)
- //}
-}