Almost finished with cloud executions
This commit is contained in:
+266
-12
@@ -72,6 +72,7 @@ var gceProject = "shuffle"
|
|||||||
var bucketName = "shuffler.appspot.com"
|
var bucketName = "shuffler.appspot.com"
|
||||||
var baseAppPath = "/home/frikky/git/shaffuru/tmp/apps"
|
var baseAppPath = "/home/frikky/git/shaffuru/tmp/apps"
|
||||||
var baseDockerName = "frikky/shuffle"
|
var baseDockerName = "frikky/shuffle"
|
||||||
|
var syncUrl = "http://192.168.3.6:5002"
|
||||||
|
|
||||||
var dbclient *datastore.Client
|
var dbclient *datastore.Client
|
||||||
|
|
||||||
@@ -139,6 +140,14 @@ type UserLimits struct {
|
|||||||
MaxWorkflows int64 `json:"max_workflows" datastore:"max_workflows"`
|
MaxWorkflows int64 `json:"max_workflows" datastore:"max_workflows"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type retStruct struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
SyncFeatures SyncFeatures `json:"sync_features"`
|
||||||
|
SessionKey string `json:"session_key"`
|
||||||
|
IntervalSeconds int64 `json:"interval_seconds"`
|
||||||
|
Reason string `json:"reason"`
|
||||||
|
}
|
||||||
|
|
||||||
// Saves some data, not sure what to have here lol
|
// Saves some data, not sure what to have here lol
|
||||||
type UserAuth struct {
|
type UserAuth struct {
|
||||||
Description string `json:"description" datastore:"description,noindex" yaml:"description"`
|
Description string `json:"description" datastore:"description,noindex" yaml:"description"`
|
||||||
@@ -1082,6 +1091,10 @@ func handleSetEnvironments(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, item := range newEnvironments {
|
for _, item := range newEnvironments {
|
||||||
|
if item.OrgId == "" {
|
||||||
|
item.OrgId = user.ActiveOrg.Id
|
||||||
|
}
|
||||||
|
|
||||||
err = setEnvironment(ctx, &item)
|
err = setEnvironment(ctx, &item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.WriteHeader(401)
|
resp.WriteHeader(401)
|
||||||
@@ -3327,8 +3340,8 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) {
|
|||||||
Body: ioutil.NopCloser(strings.NewReader(bodyWrapper)),
|
Body: ioutil.NopCloser(strings.NewReader(bodyWrapper)),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OrgId: activeOrgs[0].Id,
|
||||||
workflowExecution, executionResp, err := handleExecution(item, workflow, newRequest)
|
workflowExecution, executionResp, err := handleExecution(item, workflow, newRequest)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = increaseStatisticsField(ctx, "total_webhooks_ran", workflowExecution.Workflow.ID, 1)
|
err = increaseStatisticsField(ctx, "total_webhooks_ran", workflowExecution.Workflow.ID, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -6559,11 +6572,13 @@ func runInit(ctx context.Context) {
|
|||||||
log.Printf("Found %d users.", len(users))
|
log.Printf("Found %d users.", len(users))
|
||||||
if len(activeOrgs) == 1 && len(users) > 0 {
|
if len(activeOrgs) == 1 && len(users) > 0 {
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
if user.ActiveOrg.Id == "" {
|
if user.ActiveOrg.Id == "" && len(user.Username) > 0 {
|
||||||
user.ActiveOrg = activeOrgs[0]
|
user.ActiveOrg = activeOrgs[0]
|
||||||
err = setUser(ctx, &user)
|
err = setUser(ctx, &user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed updating user %s", user.Username)
|
log.Printf("Failed updating user %s with org", user.Username)
|
||||||
|
} else {
|
||||||
|
log.Printf("Updated user %s to have org", user.Username)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6606,6 +6621,35 @@ func runInit(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fixing workflows to have real activeorg IDs
|
||||||
|
if len(activeOrgs) == 1 {
|
||||||
|
q := datastore.NewQuery("workflow")
|
||||||
|
var workflows []Workflow
|
||||||
|
_, err = dbclient.GetAll(ctx, q, &workflows)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error getting workflows in runinit: %s", err)
|
||||||
|
} else {
|
||||||
|
updated := 0
|
||||||
|
for _, workflow := range workflows {
|
||||||
|
if workflow.ExecutingOrg.Id == "" {
|
||||||
|
workflow.ExecutingOrg = activeOrgs[0]
|
||||||
|
|
||||||
|
err = setWorkflow(ctx, workflow, workflow.ID)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed setting workflow in init: %s", err)
|
||||||
|
} else {
|
||||||
|
log.Printf("Fixed workflow %s to have the right org.", workflow.ID)
|
||||||
|
updated += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if updated > 0 {
|
||||||
|
log.Printf("Set workflow orgs for %d workflows", updated)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Gets schedules and starts them
|
// Gets schedules and starts them
|
||||||
log.Printf("Relaunching schedules")
|
log.Printf("Relaunching schedules")
|
||||||
schedules, err := getAllSchedules(ctx)
|
schedules, err := getAllSchedules(ctx)
|
||||||
@@ -6746,7 +6790,136 @@ func runInit(ctx context.Context) {
|
|||||||
log.Printf("Finished INIT")
|
log.Printf("Finished INIT")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleVerifyCloudsync(orgId string) (SyncFeatures, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
org, err := getOrg(ctx, orgId)
|
||||||
|
if err != nil {
|
||||||
|
return SyncFeatures{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//r.HandleFunc("/api/v1/getorgs", handleGetOrgs).Methods("GET", "OPTIONS")
|
||||||
|
|
||||||
|
syncURL := fmt.Sprintf("%s/api/v1/cloud/sync/get_access", syncUrl)
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest(
|
||||||
|
"GET",
|
||||||
|
syncURL,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, org.SyncConfig.Apikey))
|
||||||
|
newresp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return SyncFeatures{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
respBody, err := ioutil.ReadAll(newresp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return SyncFeatures{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData := retStruct{}
|
||||||
|
err = json.Unmarshal(respBody, &responseData)
|
||||||
|
if err != nil {
|
||||||
|
return SyncFeatures{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if newresp.StatusCode != 200 {
|
||||||
|
return SyncFeatures{}, errors.New(fmt.Sprintf("Got status code %d when getting org remotely. Expected 200. Contact support.", newresp.StatusCode))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !responseData.Success {
|
||||||
|
return SyncFeatures{}, errors.New(responseData.Reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
return responseData.SyncFeatures, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actually stops syncing with cloud for an org.
|
||||||
|
// Disables potential schedules, removes environments, breaks workflows etc.
|
||||||
|
func handleStopCloudSync(syncUrl string, org Org) error {
|
||||||
|
if len(org.SyncConfig.Apikey) == 0 {
|
||||||
|
return errors.New(fmt.Sprintf("Couldn't find any sync key to disable org %s", org.Id))
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Should run cloud sync disable for org %s with URL %s and sync key %s", org.Id, syncUrl, org.SyncConfig.Apikey)
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest(
|
||||||
|
"DELETE",
|
||||||
|
syncUrl,
|
||||||
|
nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, org.SyncConfig.Apikey))
|
||||||
|
newresp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
respBody, err := ioutil.ReadAll(newresp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Printf("Remote disable ret: %s", string(respBody))
|
||||||
|
|
||||||
|
responseData := retStruct{}
|
||||||
|
err = json.Unmarshal(respBody, &responseData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if newresp.StatusCode != 200 {
|
||||||
|
return errors.New(fmt.Sprintf("Got status code %d when disabling org remotely. Expected 200. Contact support.", newresp.StatusCode))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !responseData.Success {
|
||||||
|
//log.Printf("Success reason: %s", responseData.Reason)
|
||||||
|
return errors.New(responseData.Reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Everything is success. Should disable org sync for %s", org.Id)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
org.CloudSync = false
|
||||||
|
org.SyncFeatures = SyncFeatures{}
|
||||||
|
org.SyncConfig = SyncConfig{}
|
||||||
|
|
||||||
|
err = setOrg(ctx, org, org.Id)
|
||||||
|
if err != nil {
|
||||||
|
newerror := fmt.Sprintf("ERROR: Failed updating even though there was success: %s", err)
|
||||||
|
log.Printf(newerror)
|
||||||
|
return errors.New(newerror)
|
||||||
|
}
|
||||||
|
|
||||||
|
var environments []Environment
|
||||||
|
q := datastore.NewQuery("Environments").Filter("org_id =", org.Id)
|
||||||
|
_, err = dbclient.GetAll(ctx, q, &environments)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't disable, this will be deleted entirely
|
||||||
|
for _, environment := range environments {
|
||||||
|
if environment.Type == "cloud" {
|
||||||
|
environment.Name = "Cloud"
|
||||||
|
environment.Archived = true
|
||||||
|
err = setEnvironment(ctx, &environment)
|
||||||
|
if err == nil {
|
||||||
|
log.Printf("Updated cloud environment %s", environment.Name)
|
||||||
|
} else {
|
||||||
|
log.Printf("Failed to update cloud environment %s", environment.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// INFO: https://docs.google.com/drawings/d/1JJebpPeEVEbmH_qsAC6zf9Noygp7PytvesrkhE19QrY/edit
|
// INFO: https://docs.google.com/drawings/d/1JJebpPeEVEbmH_qsAC6zf9Noygp7PytvesrkhE19QrY/edit
|
||||||
|
/*
|
||||||
|
This is here to both enable and disable cloud sync features for an organization
|
||||||
|
*/
|
||||||
func handleCloudSetup(resp http.ResponseWriter, request *http.Request) {
|
func handleCloudSetup(resp http.ResponseWriter, request *http.Request) {
|
||||||
cors := handleCors(resp, request)
|
cors := handleCors(resp, request)
|
||||||
if cors {
|
if cors {
|
||||||
@@ -6778,6 +6951,7 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) {
|
|||||||
type ReturnData struct {
|
type ReturnData struct {
|
||||||
Apikey string `datastore:"apikey"`
|
Apikey string `datastore:"apikey"`
|
||||||
Organization Org `datastore:"organization"`
|
Organization Org `datastore:"organization"`
|
||||||
|
Disable bool `datastore:"disable"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var tmpData ReturnData
|
var tmpData ReturnData
|
||||||
@@ -6833,7 +7007,42 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) {
|
|||||||
|
|
||||||
// FIXME: Path
|
// FIXME: Path
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
syncPath := "http://192.168.3.6:5002/api/v1/cloud/sync"
|
apiPath := "/api/v1/cloud/sync"
|
||||||
|
if tmpData.Disable {
|
||||||
|
if !org.CloudSync {
|
||||||
|
log.Printf("Org %s isn't syncing. Can't stop.", org.Id)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Skipped cloud sync setup. Already syncing."}`)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Should disable sync for org %s", org.Id)
|
||||||
|
apiPath := "/api/v1/cloud/sync/stop"
|
||||||
|
syncUrl = fmt.Sprintf("%s%s", syncUrl, apiPath)
|
||||||
|
|
||||||
|
err = handleStopCloudSync(syncUrl, *org)
|
||||||
|
if err != nil {
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err)))
|
||||||
|
} else {
|
||||||
|
resp.WriteHeader(200)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "Successfully disabled cloud sync for org."}`)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything below here is to SET UP CLOUD SYNC.
|
||||||
|
// If you want to disable cloud sync, see previous section.
|
||||||
|
if org.CloudSync {
|
||||||
|
log.Printf("Org %s is already syncing. Skip", org.Id)
|
||||||
|
resp.WriteHeader(401)
|
||||||
|
resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Org is already syncing. Nothing to set up."}`)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
syncPath := fmt.Sprintf("%s%s", syncUrl, apiPath)
|
||||||
|
|
||||||
type requestStruct struct {
|
type requestStruct struct {
|
||||||
ApiKey string `json:"api_key"`
|
ApiKey string `json:"api_key"`
|
||||||
}
|
}
|
||||||
@@ -6871,14 +7080,6 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type retStruct struct {
|
|
||||||
Success bool `json:"success"`
|
|
||||||
SyncFeatures SyncFeatures `json:"sync_features"`
|
|
||||||
SessionKey string `json:"session_key"`
|
|
||||||
IntervalSeconds int64 `json:"interval_seconds"`
|
|
||||||
Reason string `json:"reason"`
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Respbody: %s", string(respBody))
|
log.Printf("Respbody: %s", string(respBody))
|
||||||
responseData := retStruct{}
|
responseData := retStruct{}
|
||||||
err = json.Unmarshal(respBody, &responseData)
|
err = json.Unmarshal(respBody, &responseData)
|
||||||
@@ -6912,6 +7113,59 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) {
|
|||||||
Interval: responseData.IntervalSeconds,
|
Interval: responseData.IntervalSeconds,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Add this for every feature
|
||||||
|
if org.SyncFeatures.Workflows.Active {
|
||||||
|
log.Printf("Should activate cloud workflows for org %s!", org.Id)
|
||||||
|
|
||||||
|
// 1. Find environment
|
||||||
|
// 2. If cloud env found, enable it (un-archive)
|
||||||
|
// 3. If it doesn't create it
|
||||||
|
var environments []Environment
|
||||||
|
q := datastore.NewQuery("Environments").Filter("org_id =", org.Id)
|
||||||
|
_, err = dbclient.GetAll(ctx, q, &environments)
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
// Don't disable, this will be deleted entirely
|
||||||
|
found := false
|
||||||
|
for _, environment := range environments {
|
||||||
|
if environment.Type == "cloud" {
|
||||||
|
environment.Name = "Cloud"
|
||||||
|
environment.Archived = false
|
||||||
|
err = setEnvironment(ctx, &environment)
|
||||||
|
if err == nil {
|
||||||
|
log.Printf("Re-added cloud environment %s", environment.Name)
|
||||||
|
} else {
|
||||||
|
log.Printf("Failed to re-enable cloud environment %s", environment.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
log.Printf("Env for cloud not found. Should add it!")
|
||||||
|
newEnv := Environment{
|
||||||
|
Name: "Cloud",
|
||||||
|
Type: "cloud",
|
||||||
|
Archived: false,
|
||||||
|
Registered: true,
|
||||||
|
Default: false,
|
||||||
|
OrgId: org.Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = setEnvironment(ctx, &newEnv)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed setting up NEW org environment for org %s: %s", org.Id, err)
|
||||||
|
} else {
|
||||||
|
log.Printf("Successfully added new environment for org %s", org.Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("Failed setting org environment, because none were found: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = setOrg(ctx, *org, org.Id)
|
err = setOrg(ctx, *org, org.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("ERROR: Failed updating org even though there was success: %s", err)
|
log.Printf("ERROR: Failed updating org even though there was success: %s", err)
|
||||||
|
|||||||
+156
-14
@@ -204,6 +204,7 @@ type WorkflowExecution struct {
|
|||||||
ExecutionArgument string `json:"execution_argument" datastore:"execution_argument,noindex"`
|
ExecutionArgument string `json:"execution_argument" datastore:"execution_argument,noindex"`
|
||||||
ExecutionId string `json:"execution_id" datastore:"execution_id"`
|
ExecutionId string `json:"execution_id" datastore:"execution_id"`
|
||||||
ExecutionSource string `json:"execution_source" datastore:"execution_source"`
|
ExecutionSource string `json:"execution_source" datastore:"execution_source"`
|
||||||
|
ExecutionOrg string `json:"execution_org" datastore:"execution_org"`
|
||||||
WorkflowId string `json:"workflow_id" datastore:"workflow_id"`
|
WorkflowId string `json:"workflow_id" datastore:"workflow_id"`
|
||||||
LastNode string `json:"last_node" datastore:"last_node"`
|
LastNode string `json:"last_node" datastore:"last_node"`
|
||||||
Authorization string `json:"authorization" datastore:"authorization"`
|
Authorization string `json:"authorization" datastore:"authorization"`
|
||||||
@@ -481,7 +482,7 @@ func getWorkflowQueue(ctx context.Context, id string) (ExecutionRequestWrapper,
|
|||||||
//}
|
//}
|
||||||
|
|
||||||
// Frequency = cronjob OR minutes between execution
|
// Frequency = cronjob OR minutes between execution
|
||||||
func createSchedule(ctx context.Context, scheduleId, workflowId, name, startNode, frequency string, body []byte) error {
|
func createSchedule(ctx context.Context, scheduleId, workflowId, name, startNode, frequency, orgId string, body []byte) error {
|
||||||
var err error
|
var err error
|
||||||
testSplit := strings.Split(frequency, "*")
|
testSplit := strings.Split(frequency, "*")
|
||||||
cronJob := ""
|
cronJob := ""
|
||||||
@@ -525,7 +526,7 @@ func createSchedule(ctx context.Context, scheduleId, workflowId, name, startNode
|
|||||||
Body: ioutil.NopCloser(strings.NewReader(bodyWrapper)),
|
Body: ioutil.NopCloser(strings.NewReader(bodyWrapper)),
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, err := handleExecution(workflowId, Workflow{}, request)
|
_, _, err := handleExecution(workflowId, Workflow{ExecutingOrg: Org{Id: orgId}}, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to execute %s: %s", workflowId, err)
|
log.Printf("Failed to execute %s: %s", workflowId, err)
|
||||||
}
|
}
|
||||||
@@ -1355,6 +1356,7 @@ func setNewWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
workflow.ID = uuid.NewV4().String()
|
workflow.ID = uuid.NewV4().String()
|
||||||
workflow.Owner = user.Id
|
workflow.Owner = user.Id
|
||||||
workflow.Sharing = "private"
|
workflow.Sharing = "private"
|
||||||
|
workflow.ExecutingOrg = user.ActiveOrg
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
log.Printf("Saved new workflow %s with name %s", workflow.ID, workflow.Name)
|
log.Printf("Saved new workflow %s with name %s", workflow.ID, workflow.Name)
|
||||||
@@ -1395,7 +1397,7 @@ func setNewWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
// FIXME: Add real env
|
// FIXME: Add real env
|
||||||
envName := "Shuffle"
|
envName := "Shuffle"
|
||||||
environments, err := getEnvironments(ctx)
|
environments, err := getEnvironments(ctx, user.ActiveOrg.Id)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
for _, env := range environments {
|
for _, env := range environments {
|
||||||
if env.Default {
|
if env.Default {
|
||||||
@@ -1679,8 +1681,6 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//Actions []Action `json:"actions" datastore:"actions,noindex"`
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(request.Body)
|
body, err := ioutil.ReadAll(request.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed hook unmarshaling: %s", err)
|
log.Printf("Failed hook unmarshaling: %s", err)
|
||||||
@@ -1712,6 +1712,10 @@ func saveWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
workflow.Owner = user.Id
|
workflow.Owner = user.Id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(workflow.ExecutingOrg.Id) == 0 {
|
||||||
|
workflow.ExecutingOrg = user.ActiveOrg
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME - this shouldn't be necessary with proper API checks
|
// FIXME - this shouldn't be necessary with proper API checks
|
||||||
newActions := []Action{}
|
newActions := []Action{}
|
||||||
allNodes := []string{}
|
allNodes := []string{}
|
||||||
@@ -2305,6 +2309,11 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
|||||||
workflow = *tmpworkflow
|
workflow = *tmpworkflow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(workflow.ExecutingOrg.Id) == 0 {
|
||||||
|
log.Printf("Stopped execution because there is no executing org for workflow %s", workflow.ID)
|
||||||
|
return WorkflowExecution{}, fmt.Sprintf("Workflow has no executing org defined"), errors.New("Workflow has no executing org defined")
|
||||||
|
}
|
||||||
|
|
||||||
if len(workflow.Actions) == 0 {
|
if len(workflow.Actions) == 0 {
|
||||||
workflow.Actions = []Action{}
|
workflow.Actions = []Action{}
|
||||||
}
|
}
|
||||||
@@ -2397,6 +2406,8 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Check for parameters of start and ExecutionId
|
// Check for parameters of start and ExecutionId
|
||||||
|
// This is mostly used for user input trigger
|
||||||
|
|
||||||
start, startok := request.URL.Query()["start"]
|
start, startok := request.URL.Query()["start"]
|
||||||
answer, answerok := request.URL.Query()["answer"]
|
answer, answerok := request.URL.Query()["answer"]
|
||||||
referenceId, referenceok := request.URL.Query()["reference_execution"]
|
referenceId, referenceok := request.URL.Query()["reference_execution"]
|
||||||
@@ -2642,16 +2653,71 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
|||||||
// Verification for execution environments
|
// Verification for execution environments
|
||||||
workflowExecution.Results = defaultResults
|
workflowExecution.Results = defaultResults
|
||||||
workflowExecution.Workflow.Actions = newActions
|
workflowExecution.Workflow.Actions = newActions
|
||||||
onpremExecution := false
|
onpremExecution := true
|
||||||
environments := []string{}
|
environments := []string{}
|
||||||
|
|
||||||
|
if len(workflowExecution.ExecutionOrg) == 0 && len(workflow.ExecutingOrg.Id) > 0 {
|
||||||
|
workflowExecution.ExecutionOrg = workflow.ExecutingOrg.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
var allEnvs []Environment
|
||||||
|
if len(workflowExecution.ExecutionOrg) > 0 {
|
||||||
|
log.Printf("Executing ORG: %s", workflowExecution.ExecutionOrg)
|
||||||
|
|
||||||
|
allEnvironments, err := getEnvironments(ctx, workflowExecution.ExecutionOrg)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed finding environments: %s", err)
|
||||||
|
return WorkflowExecution{}, fmt.Sprintf("Workflow environments not found for this org"), errors.New(fmt.Sprintf("Workflow environments not found for this org"))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, curenv := range allEnvironments {
|
||||||
|
if curenv.Archived {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
allEnvs = append(allEnvs, curenv)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("[ERROR] No org identified for execution of %s. Returning", workflowExecution.Workflow.ID)
|
||||||
|
return WorkflowExecution{}, "No org identified for execution", errors.New("No org identified for execution")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(allEnvs) == 0 {
|
||||||
|
log.Printf("[ERROR] No active environments found for org", workflowExecution.ExecutionOrg)
|
||||||
|
return WorkflowExecution{}, "No active environments found", errors.New(fmt.Sprintf("No active env found for org %s", workflowExecution.ExecutionOrg))
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the actions are children of the startnode?
|
// Check if the actions are children of the startnode?
|
||||||
imageNames := []string{}
|
imageNames := []string{}
|
||||||
|
cloudExec := false
|
||||||
for _, action := range workflowExecution.Workflow.Actions {
|
for _, action := range workflowExecution.Workflow.Actions {
|
||||||
if action.Environment != cloudname {
|
// Verify if the action environment exists and append
|
||||||
found := false
|
found := false
|
||||||
|
for _, env := range allEnvs {
|
||||||
|
if env.Name == action.Environment {
|
||||||
|
found = true
|
||||||
|
|
||||||
|
if env.Type == "cloud" {
|
||||||
|
cloudExec = true
|
||||||
|
} else if env.Type == "onprem" {
|
||||||
|
onpremExecution = true
|
||||||
|
} else {
|
||||||
|
log.Printf("[ERROR] No handler for environment type %s", env.Type)
|
||||||
|
return WorkflowExecution{}, "No active environments found", errors.New(fmt.Sprintf("No handler for environment type %s", env.Type))
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
log.Printf("[ERROR] Couldn't find environment %s. Maybe it's inactive?", action.Environment)
|
||||||
|
return WorkflowExecution{}, "Couldn't find the environment", errors.New(fmt.Sprintf("Couldn't find env %s in org %s", action.Environment, workflowExecution.ExecutionOrg))
|
||||||
|
}
|
||||||
|
|
||||||
|
found = false
|
||||||
for _, env := range environments {
|
for _, env := range environments {
|
||||||
if env == action.Environment {
|
if env == action.Environment {
|
||||||
|
|
||||||
found = true
|
found = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -2665,9 +2731,6 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
|||||||
if !found {
|
if !found {
|
||||||
environments = append(environments, action.Environment)
|
environments = append(environments, action.Environment)
|
||||||
}
|
}
|
||||||
|
|
||||||
onpremExecution = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = imageCheckBuilder(imageNames)
|
err = imageCheckBuilder(imageNames)
|
||||||
@@ -2711,8 +2774,22 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
|||||||
log.Printf("Failed adding to db: %s", err)
|
log.Printf("Failed adding to db: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
log.Printf("[ERROR] Cloud not implemented yet")
|
|
||||||
|
// Verifies and runs cloud executions
|
||||||
|
if cloudExec {
|
||||||
|
featuresList, err := handleVerifyCloudsync(workflowExecution.ExecutionOrg)
|
||||||
|
if !featuresList.Workflows.Active || err != nil {
|
||||||
|
log.Printf("Error: %s", err)
|
||||||
|
log.Printf("[ERROR] Cloud not implemented yet. May need to work on app checking and such")
|
||||||
|
return WorkflowExecution{}, "Cloud not implemented yet", errors.New("Cloud not implemented yet")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(workflowExecution.Workflow.Actions) == 1 {
|
||||||
|
log.Printf("Should execute directly with cloud instead of worker because only one action")
|
||||||
|
cloudExecuteAction(workflowExecution.ExecutionId, workflowExecution.Workflow.Actions[0], workflowExecution.ExecutionOrg)
|
||||||
|
return WorkflowExecution{}, "Cloud not implemented yet", errors.New("Cloud not implemented yet")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = increaseStatisticsField(ctx, "workflow_executions", workflow.ID, 1)
|
err = increaseStatisticsField(ctx, "workflow_executions", workflow.ID, 1)
|
||||||
@@ -2723,6 +2800,69 @@ func handleExecution(id string, workflow Workflow, request *http.Request) (Workf
|
|||||||
return workflowExecution, "", nil
|
return workflowExecution, "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This updates stuff locally from remote executions
|
||||||
|
func cloudExecuteAction(workflowExecutionId string, action Action, orgId string) error {
|
||||||
|
log.Printf("Executing action: %#v in execution ID %s", action, workflowExecutionId)
|
||||||
|
ctx := context.Background()
|
||||||
|
org, err := getOrg(ctx, orgId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExecutionStruct struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Action Action `json:"action"`
|
||||||
|
}
|
||||||
|
data := ExecutionStruct{
|
||||||
|
ID: workflowExecutionId,
|
||||||
|
Action: action,
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed marshaling api key data: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
syncURL := fmt.Sprintf("%s/api/v1/cloud/sync/execute_node", syncUrl)
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequest(
|
||||||
|
"POST",
|
||||||
|
syncURL,
|
||||||
|
bytes.NewBuffer(b),
|
||||||
|
)
|
||||||
|
|
||||||
|
req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, org.SyncConfig.Apikey))
|
||||||
|
newresp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
respBody, err := ioutil.ReadAll(newresp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Finished request. Data: %s", string(respBody))
|
||||||
|
log.Printf("Status code: %d", newresp.StatusCode)
|
||||||
|
|
||||||
|
responseData := retStruct{}
|
||||||
|
err = json.Unmarshal(respBody, &responseData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if newresp.StatusCode != 200 {
|
||||||
|
return errors.New(fmt.Sprintf("Got status code %d when executing remotely. Expected 200. Contact support.", newresp.StatusCode))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !responseData.Success {
|
||||||
|
return errors.New(responseData.Reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func executeWorkflow(resp http.ResponseWriter, request *http.Request) {
|
func executeWorkflow(resp http.ResponseWriter, request *http.Request) {
|
||||||
cors := handleCors(resp, request)
|
cors := handleCors(resp, request)
|
||||||
if cors {
|
if cors {
|
||||||
@@ -2776,6 +2916,7 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("[INFO] Starting execution of %s!", fileId)
|
log.Printf("[INFO] Starting execution of %s!", fileId)
|
||||||
|
workflow.ExecutingOrg = user.ActiveOrg
|
||||||
workflowExecution, executionResp, err := handleExecution(fileId, *workflow, request)
|
workflowExecution, executionResp, err := handleExecution(fileId, *workflow, request)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -3137,6 +3278,7 @@ func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) {
|
|||||||
schedule.Name,
|
schedule.Name,
|
||||||
startNode,
|
startNode,
|
||||||
schedule.Frequency,
|
schedule.Frequency,
|
||||||
|
user.ActiveOrg.Id,
|
||||||
[]byte(parsedBody),
|
[]byte(parsedBody),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -3336,9 +3478,9 @@ func getWorkflow(ctx context.Context, id string) (*Workflow, error) {
|
|||||||
return workflow, nil
|
return workflow, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getEnvironments(ctx context.Context) ([]Environment, error) {
|
func getEnvironments(ctx context.Context, OrgId string) ([]Environment, error) {
|
||||||
var environments []Environment
|
var environments []Environment
|
||||||
q := datastore.NewQuery("Environments")
|
q := datastore.NewQuery("Environments").Filter("org_id =", OrgId)
|
||||||
|
|
||||||
_, err := dbclient.GetAll(ctx, q, &environments)
|
_, err := dbclient.GetAll(ctx, q, &environments)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -196,12 +196,13 @@ const Admin = (props) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const enableCloudSync = (apikey, organization) => {
|
const enableCloudSync = (apikey, organization, disableSync) => {
|
||||||
setOrgSyncResponse("")
|
setOrgSyncResponse("")
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
apikey: apikey,
|
apikey: apikey,
|
||||||
organization: organization,
|
organization: organization,
|
||||||
|
disable: disableSync,
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = globalUrl + '/api/v1/cloud/setup';
|
const url = globalUrl + '/api/v1/cloud/setup';
|
||||||
@@ -229,13 +230,20 @@ const Admin = (props) => {
|
|||||||
.then((responseJson) => {
|
.then((responseJson) => {
|
||||||
if (!responseJson.success && responseJson.reason !== undefined) {
|
if (!responseJson.success && responseJson.reason !== undefined) {
|
||||||
setOrgSyncResponse(responseJson.reason)
|
setOrgSyncResponse(responseJson.reason)
|
||||||
alert.error("Failed to sync: "+responseJson.reason)
|
alert.error("Failed to handle sync: "+responseJson.reason)
|
||||||
} else if (!responseJson.success) {
|
} else if (!responseJson.success) {
|
||||||
alert.error("Failed to sync.")
|
alert.error("Failed to handle sync.")
|
||||||
} else {
|
} else {
|
||||||
alert.success("Sync set up!")
|
|
||||||
getOrgs()
|
getOrgs()
|
||||||
//setCloudSyncModalOpen(false)
|
if (disableSync) {
|
||||||
|
alert.success("Successfully disabled sync!")
|
||||||
|
} else {
|
||||||
|
alert.success("Sync successfully set up!")
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedOrganization.cloud_sync = !selectedOrganization.cloud_sync
|
||||||
|
setSelectedOrganization(selectedOrganization)
|
||||||
|
setCloudSyncApikey("")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@@ -389,9 +397,15 @@ const Admin = (props) => {
|
|||||||
for (var key in environments) {
|
for (var key in environments) {
|
||||||
if (environments[key].Name == name) {
|
if (environments[key].Name == name) {
|
||||||
if (environments[key].default) {
|
if (environments[key].default) {
|
||||||
alert.info("Can't delete the default environment")
|
alert.error("Can't delete the default environment")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (environments[key].type === "cloud") {
|
||||||
|
alert.error("Can't delete the cloud environments")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
environments[key].archived = true
|
environments[key].archived = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -881,6 +895,7 @@ const Admin = (props) => {
|
|||||||
}}
|
}}
|
||||||
required
|
required
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
|
disabled={selectedOrganization.cloud_sync}
|
||||||
autoComplete="cloud apikey"
|
autoComplete="cloud apikey"
|
||||||
id="apikey_field"
|
id="apikey_field"
|
||||||
margin="normal"
|
margin="normal"
|
||||||
@@ -890,14 +905,19 @@ const Admin = (props) => {
|
|||||||
setCloudSyncApikey(event.target.value)
|
setCloudSyncApikey(event.target.value)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Button disabled={cloudSyncApikey.length === 0 || loading} variant="contained" style={{ marginLeft: 15, height: 60, margin: "auto", borderRadius: "0px" }} onClick={() => {
|
<Button disabled={(!selectedOrganization.cloud_sync && cloudSyncApikey.length === 0) || loading} variant="contained" style={{ marginLeft: 15, height: 60, margin: "auto", borderRadius: "0px" }} onClick={() => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
enableCloudSync(
|
enableCloudSync(
|
||||||
cloudSyncApikey,
|
cloudSyncApikey,
|
||||||
selectedOrganization,
|
selectedOrganization,
|
||||||
|
selectedOrganization.cloud_sync,
|
||||||
)
|
)
|
||||||
}} color="primary">
|
}} color="primary">
|
||||||
Test sync
|
{selectedOrganization.cloud_sync ?
|
||||||
|
"Stop sync"
|
||||||
|
:
|
||||||
|
"Start sync"
|
||||||
|
}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{orgSyncResponse.length > 0 ?
|
{orgSyncResponse.length > 0 ?
|
||||||
@@ -910,7 +930,7 @@ const Admin = (props) => {
|
|||||||
<Grid container style={{width: "100%", marginBottom: 15, }}>
|
<Grid container style={{width: "100%", marginBottom: 15, }}>
|
||||||
{syncList.map((data, index) => {
|
{syncList.map((data, index) => {
|
||||||
return (
|
return (
|
||||||
<GridItem data={data} />
|
<GridItem key={index} data={data} />
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -1369,6 +1389,10 @@ const Admin = (props) => {
|
|||||||
primary="Orborus running (TBD)"
|
primary="Orborus running (TBD)"
|
||||||
style={{minWidth: 200, maxWidth: 200}}
|
style={{minWidth: 200, maxWidth: 200}}
|
||||||
/>
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary="Type"
|
||||||
|
style={{minWidth: 150, maxWidth: 150}}
|
||||||
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="Default"
|
primary="Default"
|
||||||
style={{minWidth: 150, maxWidth: 150}}
|
style={{minWidth: 150, maxWidth: 150}}
|
||||||
@@ -1402,6 +1426,10 @@ const Admin = (props) => {
|
|||||||
primary={"TBD"}
|
primary={"TBD"}
|
||||||
style={{minWidth: 200, maxWidth: 200, overflow: "hidden"}}
|
style={{minWidth: 200, maxWidth: 200, overflow: "hidden"}}
|
||||||
/>
|
/>
|
||||||
|
<ListItemText
|
||||||
|
primary={environment.Type}
|
||||||
|
style={{minWidth: 150, maxWidth: 150}}
|
||||||
|
/>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
style={{minWidth: 150, maxWidth: 150, overflow: "hidden"}}
|
style={{minWidth: 150, maxWidth: 150, overflow: "hidden"}}
|
||||||
primary={environment.default ? "true" : null}
|
primary={environment.default ? "true" : null}
|
||||||
|
|||||||
@@ -5554,7 +5554,7 @@ const AngularWorkflow = (props) => {
|
|||||||
jsonvalid = false
|
jsonvalid = false
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("Error: ", e)
|
//console.log("Error: ", e)
|
||||||
jsonvalid = false
|
jsonvalid = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -337,6 +337,9 @@ const Workflows = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data.execution_org = {"id": ""}
|
||||||
|
console.log(data)
|
||||||
|
|
||||||
let linkElement = document.createElement('a');
|
let linkElement = document.createElement('a');
|
||||||
linkElement.setAttribute('href', dataUri);
|
linkElement.setAttribute('href', dataUri);
|
||||||
linkElement.setAttribute('download', exportFileDefaultName);
|
linkElement.setAttribute('download', exportFileDefaultName);
|
||||||
|
|||||||
Reference in New Issue
Block a user