4f3f07d4dd
- Vendor shuffle-shared v1.2.51 as backend/go-app/shuffle-shared - Add replace directive in go.mod to use the local moduled copy - In HandleCheckLicense, force org.Licensed=true and set every SyncFeatures limit to 1e9, skipping all license-key logic - Update Dockerfile to ADD the local shuffle-shared before go build - Verified: backend image builds successfully via docker
5415 lines
166 KiB
Go
5415 lines
166 KiB
Go
package shuffle
|
|
|
|
import (
|
|
"archive/tar"
|
|
"archive/zip"
|
|
"bufio"
|
|
"crypto/sha1"
|
|
"bytes"
|
|
"compress/gzip"
|
|
"context"
|
|
"crypto/md5"
|
|
"crypto/sha256"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"regexp"
|
|
"runtime"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"cloud.google.com/go/storage"
|
|
docker "github.com/docker/docker/client"
|
|
"gopkg.in/yaml.v2"
|
|
uuid "github.com/satori/go.uuid"
|
|
|
|
"github.com/frikky/kin-openapi/openapi3"
|
|
//iocParser "github.com/Shuffle/indicator-parser/go/ioc"
|
|
)
|
|
|
|
var downloadedImages = []string{}
|
|
var pythonAllowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
|
|
var pythonReplacements = map[string]string{
|
|
"[": "",
|
|
"]": "",
|
|
"{": "",
|
|
"}": "",
|
|
"(": "",
|
|
")": "",
|
|
"!": "",
|
|
"@": "",
|
|
"#": "",
|
|
"$": "",
|
|
"%": "",
|
|
"^": "",
|
|
"&": "",
|
|
":": "",
|
|
";": "",
|
|
"<": "",
|
|
">": "",
|
|
"'": "",
|
|
}
|
|
|
|
type countingWriter struct {
|
|
n *int64
|
|
}
|
|
|
|
func CopyFile(fromfile, tofile string) error {
|
|
from, err := os.Open(fromfile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer from.Close()
|
|
|
|
to, err := os.OpenFile(tofile, os.O_RDWR|os.O_CREATE, 0666)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer to.Close()
|
|
|
|
_, err = io.Copy(to, from)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func GetCorrectActionName(parsed string) string {
|
|
if strings.HasPrefix(parsed, "post ") || strings.HasPrefix(parsed, "post_") {
|
|
parsed = parsed[5:]
|
|
} else if strings.HasPrefix(parsed, "get list") || strings.HasPrefix(parsed, "get_list") {
|
|
parsed = parsed[4:]
|
|
} else if strings.HasPrefix(parsed, "head ") || strings.HasPrefix(parsed, "head_") {
|
|
parsed = parsed[5:]
|
|
} else if strings.HasPrefix(parsed, "put ") || strings.HasPrefix(parsed, "put_") {
|
|
parsed = parsed[4:]
|
|
} else if strings.HasPrefix(parsed, "patch ") || strings.HasPrefix(parsed, "patch_") {
|
|
parsed = parsed[6:]
|
|
}
|
|
|
|
if strings.HasPrefix(parsed, "\"") {
|
|
parsed = parsed[1:]
|
|
}
|
|
|
|
if strings.HasSuffix(parsed, "\"") {
|
|
parsed = parsed[:len(parsed)-1]
|
|
}
|
|
|
|
return parsed
|
|
}
|
|
|
|
func FormatAppfile(filedata string) (string, string) {
|
|
lines := strings.Split(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 {
|
|
// This could break something..
|
|
classname = "TMP"
|
|
}
|
|
}
|
|
|
|
if strings.Contains(line, "if __name__ ==") {
|
|
break
|
|
}
|
|
|
|
// asyncio.run(HelloWorld.run(), debug=True)
|
|
|
|
newfile = append(newfile, line)
|
|
}
|
|
|
|
filedata = strings.Join(newfile, "\n")
|
|
return classname, filedata
|
|
}
|
|
|
|
// Streams the data into a zip to be used for a cloud function
|
|
func StreamZipdata(ctx context.Context, identifier, pythoncode, requirements, bucketName string) (string, error) {
|
|
filename := fmt.Sprintf("generated_cloudfunctions/%s.zip", identifier)
|
|
|
|
buf := new(bytes.Buffer)
|
|
zipWriter := zip.NewWriter(buf)
|
|
|
|
if project.Environment == "cloud" {
|
|
client, err := storage.NewClient(ctx)
|
|
if err != nil {
|
|
log.Printf("Failed to create datastore client: %v", err)
|
|
return filename, err
|
|
}
|
|
|
|
bucket := client.Bucket(bucketName)
|
|
|
|
obj := bucket.Object(filename)
|
|
storageWriter := obj.NewWriter(ctx)
|
|
defer storageWriter.Close()
|
|
|
|
zipWriter = zip.NewWriter(storageWriter)
|
|
}
|
|
|
|
zipFile, err := zipWriter.Create("main.py")
|
|
if err != nil {
|
|
log.Printf("Packing failed to create zip file from bucket: %v", err)
|
|
return filename, err
|
|
}
|
|
|
|
// Have to use Fprintln otherwise it tries to parse all strings etc.
|
|
if _, err := fmt.Fprintln(zipFile, pythoncode); err != nil {
|
|
return filename, err
|
|
}
|
|
|
|
//log.Printf("Merging requirements: %s", requirements)
|
|
|
|
zipFile, err = zipWriter.Create("requirements.txt")
|
|
if err != nil {
|
|
log.Printf("Packing failed to create zip file from bucket: %v", err)
|
|
return filename, err
|
|
}
|
|
|
|
if _, err := fmt.Fprintln(zipFile, requirements); err != nil {
|
|
return filename, err
|
|
}
|
|
|
|
err = zipWriter.Close()
|
|
if err != nil {
|
|
log.Printf("Packing failed to close zip file writer from bucket: %v", err)
|
|
return filename, err
|
|
}
|
|
|
|
return filename, nil
|
|
}
|
|
|
|
// used to load SDK
|
|
func getGithubFile(githubUrl string) ([]byte, error) {
|
|
// md5 the github url and use it as cache key
|
|
ctx := context.Background()
|
|
cacheKey := fmt.Sprintf("%x", md5.Sum([]byte(githubUrl)))
|
|
if project.CacheDb {
|
|
cache, err := GetCache(ctx, cacheKey)
|
|
if err == nil {
|
|
cacheData := []byte(cache.([]uint8))
|
|
return cacheData, nil
|
|
}
|
|
}
|
|
|
|
// Load the data from this file and return.
|
|
resp, err := http.Get(githubUrl)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to get app base from github: %s", err)
|
|
return []byte{}, err
|
|
}
|
|
|
|
if resp.StatusCode >= 300 {
|
|
log.Printf("[ERROR] Failed to get app base from github: %s", resp.Status)
|
|
return []byte{}, errors.New(fmt.Sprintf("Failed to get app base from github: %s", resp.Status))
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
appbaseData, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to read app base from github: %s", err)
|
|
return []byte{}, err
|
|
}
|
|
|
|
log.Printf("[DEBUG] Loaded App SDK of length %d from github", len(appbaseData))
|
|
if project.CacheDb {
|
|
err = SetCache(ctx, cacheKey, appbaseData, 30)
|
|
if err != nil {
|
|
log.Printf("[WARNING] Failed setting cache for org stats '%s': %s", cacheKey, err)
|
|
}
|
|
}
|
|
|
|
return appbaseData, nil
|
|
}
|
|
|
|
func GetAppbase() ([]byte, []byte, error) {
|
|
// 1. Have baseline in bucket/generated_apps/baseline
|
|
// 2. Copy the baseline to a new folder with identifier name
|
|
appbase := "../app_sdk/app_base.py"
|
|
|
|
//static := "../app_sdk/static_baseline.py"
|
|
//staticData, err := ioutil.ReadFile(static)
|
|
//if err != nil {
|
|
// return []byte{}, []byte{}, err
|
|
//}
|
|
staticData := []byte{}
|
|
|
|
appbaseData, err := ioutil.ReadFile(appbase)
|
|
if err != nil {
|
|
// FIXME: Use an older commit of the file?
|
|
githubUrl := "https://raw.githubusercontent.com/Shuffle/app_sdk/refs/heads/main/shuffle_sdk/shuffle_sdk.py"
|
|
content, err := getGithubFile(githubUrl)
|
|
return content, []byte{}, err
|
|
|
|
return []byte{}, []byte{}, err
|
|
}
|
|
|
|
return appbaseData, staticData, nil
|
|
}
|
|
|
|
// Builds the structure for the new generated app in storage (copying baseline files)
|
|
func GetAppbaseGCP(ctx context.Context, client *storage.Client, bucketName string) ([]byte, []byte, error) {
|
|
// 1. Have baseline in bucket/generated_apps/baseline
|
|
// 2. Copy the baseline to a new folder with identifier name
|
|
|
|
loadFromGithub := true
|
|
|
|
basePath := "generated_apps/baseline"
|
|
reference := client.Bucket(bucketName).Object(fmt.Sprintf("%s/app_base.py", basePath))
|
|
|
|
// Check if it's more than 1 month old
|
|
if reference != nil {
|
|
attrs, err := reference.Attrs(ctx)
|
|
if err == nil {
|
|
if attrs.Updated.Before(time.Now().AddDate(0, -1, 0)) {
|
|
log.Printf("[WARNING] App base is older than 1 month on github. Offloading to direct github ref")
|
|
} else {
|
|
loadFromGithub = false
|
|
}
|
|
}
|
|
}
|
|
|
|
if loadFromGithub {
|
|
// FIXME: Use an older commit of the file
|
|
githubUrl := "https://raw.githubusercontent.com/Shuffle/app_sdk/refs/heads/main/shuffle_sdk/shuffle_sdk.py"
|
|
content, err := getGithubFile(githubUrl)
|
|
return content, []byte{}, err
|
|
}
|
|
|
|
appbase, err := reference.NewReader(ctx)
|
|
if err != nil {
|
|
log.Printf("[WARNING] Failed to get app base from GCP BUCKET %#v. Offloading to direct github ref", bucketName)
|
|
|
|
// FIXME: Use an older commit of the file
|
|
githubUrl := "https://raw.githubusercontent.com/Shuffle/app_sdk/refs/heads/main/shuffle_sdk/shuffle_sdk.py"
|
|
content, err := getGithubFile(githubUrl)
|
|
return content, []byte{}, err
|
|
}
|
|
|
|
defer appbase.Close()
|
|
appbaseData, err := ioutil.ReadAll(appbase)
|
|
if err != nil {
|
|
return []byte{}, []byte{}, err
|
|
}
|
|
|
|
return appbaseData, []byte{}, nil
|
|
}
|
|
|
|
func FixAppbase(appbase []byte) []string {
|
|
record := true
|
|
validLines := []string{}
|
|
// Used to use static_baseline + app_base. Now it's only appbase :O
|
|
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
|
|
}
|
|
|
|
// Builds the structure for the new generated app in storage (copying baseline files)
|
|
func BuildStructureGCP(ctx context.Context, client *storage.Client, identifier, bucketName string) (string, error) {
|
|
// 1. Have baseline in bucket/generated_apps/baseline
|
|
// 2. Copy the baseline to a new folder with identifier name
|
|
|
|
basePath := "generated_apps"
|
|
//identifier := fmt.Sprintf("%s-%s", swagger.Info.Title, curHash)
|
|
appPath := fmt.Sprintf("%s/%s", basePath, identifier)
|
|
//fileNames := []string{"Dockerfile", "requirements.txt"}
|
|
//fileNames := []string{"Dockerfile", "requirements.txt"}
|
|
fileNames := []string{"Dockerfile"}
|
|
requirements := GetAppRequirements()
|
|
if len(requirements) > 0 {
|
|
|
|
// Write the requirements text to the file just so that it's ready
|
|
dst := client.Bucket(bucketName).Object(fmt.Sprintf("%s/%s", appPath, "requirements.txt"))
|
|
if _, err := dst.NewWriter(ctx).Write([]byte(requirements)); err != nil {
|
|
log.Printf("[ERROR] Failed to write requirements.txt during app build: %s", err)
|
|
fileNames = append(fileNames, "requirements.txt")
|
|
}
|
|
}
|
|
|
|
for _, file := range fileNames {
|
|
src := client.Bucket(bucketName).Object(fmt.Sprintf("%s/baseline/%s", basePath, file))
|
|
dst := client.Bucket(bucketName).Object(fmt.Sprintf("%s/%s", appPath, file))
|
|
if _, err := dst.CopierFrom(src).Run(ctx); err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
|
|
return appPath, nil
|
|
}
|
|
|
|
// Builds the base structure for the app that we're making
|
|
// Returns error if anything goes wrong. This has to work if
|
|
// the python code is supposed to be generated
|
|
func BuildStructure(swagger *openapi3.Swagger, curHash string) (string, error) {
|
|
//log.Printf("%#v", swagger)
|
|
|
|
// adding md5 based on input data to not overwrite earlier data.
|
|
generatedPath := "generated"
|
|
subpath := "../app_gen/python-lib/"
|
|
identifier := fmt.Sprintf("%s-%s", swagger.Info.Title, curHash)
|
|
appPath := fmt.Sprintf("%s/%s", generatedPath, identifier)
|
|
|
|
os.MkdirAll(appPath, os.ModePerm)
|
|
os.Mkdir(fmt.Sprintf("%s/src", appPath), os.ModePerm)
|
|
|
|
// File path no longer required
|
|
sourceDockerPath := fmt.Sprintf("%sbaseline/Dockerfile", subpath)
|
|
destDockerPath := fmt.Sprintf("%s/%s", appPath, "Dockerfile")
|
|
|
|
// Check if the full dest folders exists - otherwise make them
|
|
parsedPathSplit := strings.Split(destDockerPath, "/")
|
|
parsedPath := strings.Join(parsedPathSplit[0:len(parsedPathSplit)-1], "/")
|
|
if _, err := os.Stat(parsedPath); os.IsNotExist(err) {
|
|
os.MkdirAll(parsedPath, 0644)
|
|
log.Printf("[INFO] Created folder path for Dockerfile to ensure it exists: %s", parsedPath)
|
|
}
|
|
|
|
err := CopyFile(sourceDockerPath, destDockerPath)
|
|
if err != nil {
|
|
|
|
// Keep it in blobs just in case?
|
|
foundDockerfile := GetBaseDockerfile()
|
|
if len(foundDockerfile) > 0 {
|
|
|
|
// Writing it to both
|
|
ioutil.WriteFile(sourceDockerPath, []byte(foundDockerfile), 0644)
|
|
err = ioutil.WriteFile(destDockerPath, []byte(foundDockerfile), 0644)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to write Dockerfile from BaseDockerfile during app build: %s", err)
|
|
return appPath, err
|
|
} else {
|
|
log.Printf("[INFO] Successfully wrote Dockerfile from BaseDockerfile to path %s", destDockerPath)
|
|
}
|
|
|
|
} else {
|
|
log.Printf("[ERROR] Failed to move Dockerfile from location %s to location %s. Found Dockerfile len: %d", sourceDockerPath, destDockerPath, len(foundDockerfile))
|
|
}
|
|
}
|
|
|
|
parsedAppPath := fmt.Sprintf("%s/%s", appPath, "requirements.txt")
|
|
requirements := GetAppRequirements()
|
|
if len(requirements) > 0 {
|
|
// Write it to the file just so that it's ready
|
|
err = ioutil.WriteFile(parsedAppPath, []byte(requirements), 0644)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to write requirements.txt during app build: %s", err)
|
|
} else {
|
|
return appPath, nil
|
|
}
|
|
}
|
|
|
|
err = CopyFile(fmt.Sprintf("%sbaseline/requirements.txt", subpath), fmt.Sprintf("%s/%s", appPath, "requirements.txt"))
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to move requrements.txt")
|
|
return appPath, err
|
|
}
|
|
|
|
return appPath, nil
|
|
}
|
|
|
|
func TrimToNum(r int) bool {
|
|
if n := r - '0'; n >= 0 && n <= 9 {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Returns fixed function names based on a list of strings
|
|
func GetValidParameters(parameters []string) []string {
|
|
numbers := "0123456789"
|
|
newParams := []string{}
|
|
for _, param := range parameters {
|
|
if param == "headers=\"\"" || param == "queries=\"\"" {
|
|
newParams = append(newParams, param)
|
|
continue
|
|
}
|
|
|
|
originalParam := param
|
|
|
|
// Something with dashes not working?
|
|
|
|
for key, val := range pythonReplacements {
|
|
param = strings.Replace(param, key, val, -1)
|
|
}
|
|
|
|
for _, char := range param {
|
|
if !strings.Contains(pythonAllowed, string(char)) {
|
|
param = strings.Replace(param, string(char), "", -1)
|
|
}
|
|
}
|
|
|
|
if len(param) > 0 && !ArrayContains(newParams, param) {
|
|
newParams = append(newParams, param)
|
|
} else {
|
|
// Find some name for it just for the code
|
|
h := md5.New()
|
|
io.WriteString(h, originalParam)
|
|
newName := strings.ToLower(fmt.Sprintf("%X", h.Sum(nil)))
|
|
|
|
// Fix leading numbers
|
|
newString := ""
|
|
shouldAdd := false
|
|
for _, char := range newName {
|
|
if !strings.Contains(numbers, string(char)) {
|
|
shouldAdd = true
|
|
}
|
|
|
|
if shouldAdd {
|
|
newString += string(char)
|
|
}
|
|
}
|
|
|
|
// Leading 0 not allowed
|
|
newParams = append(newParams, newString)
|
|
}
|
|
}
|
|
|
|
return newParams
|
|
}
|
|
|
|
// This function generates the python code that's being used.
|
|
// This is really meta when you program it. Handling parameters is hard here.
|
|
func MakePythoncode(swagger *openapi3.Swagger, name, url, method string, parameters, optionalQueries, headers []string, fileField string, api WorkflowApp, handleFile bool) (string, string) {
|
|
|
|
method = strings.ToLower(method)
|
|
queryString := ""
|
|
queryData := ""
|
|
|
|
extraHeaders := ""
|
|
extraQueries := ""
|
|
reservedKeys := []string{"BearerAuth", "ApiKeyAuth", "Oauth2", "BasicAuth", "JWT"}
|
|
|
|
// Predefined for auth
|
|
//invalidQueries := []string{"access_token", "username_basic", "password_basic", "apikey", "api_key"}
|
|
|
|
// FIXME - this might break - need to check if ? or & should be set as query
|
|
parameterData := ""
|
|
if len(optionalQueries) > 0 {
|
|
//if len(queryString
|
|
queryString += ", "
|
|
for index, query := range optionalQueries {
|
|
// Check if it's a part of the URL already
|
|
|
|
parsedQuery := FixFunctionName(query, "", true)
|
|
newParams := GetValidParameters([]string{parsedQuery})
|
|
if len(newParams) > 0 {
|
|
parsedQuery = newParams[0]
|
|
}
|
|
|
|
if strings.Contains(queryString, parsedQuery) {
|
|
continue
|
|
}
|
|
|
|
queryString += fmt.Sprintf("%s=\"\"", parsedQuery)
|
|
|
|
if index != len(optionalQueries)-1 {
|
|
queryString += ", "
|
|
}
|
|
|
|
/*
|
|
queryData += fmt.Sprintf(`
|
|
if %s:
|
|
url += f"&%s={%s}"`, query, query, query)
|
|
*/
|
|
queryData += fmt.Sprintf(`
|
|
if %s:
|
|
if isinstance(%s, list) or isinstance(%s, dict):
|
|
try:
|
|
%s = json.dumps(%s)
|
|
except:
|
|
pass
|
|
|
|
params[requests.utils.quote("%s")] = requests.utils.quote(%s)`, parsedQuery, parsedQuery, parsedQuery, parsedQuery, parsedQuery, query, parsedQuery)
|
|
}
|
|
} else {
|
|
//log.Printf("No optional queries?")
|
|
}
|
|
|
|
// api.Authentication.Parameters[0].Value = "BearerAuth"
|
|
authenticationParameter := ""
|
|
authenticationSetup := ""
|
|
authenticationAddin := ""
|
|
// Python configuration code that should work :)
|
|
if swagger.Components.SecuritySchemes != nil {
|
|
if swagger.Components.SecuritySchemes["BearerAuth"] != nil {
|
|
authenticationParameter = ", apikey"
|
|
authenticationSetup = "if apikey != \" \" and not apikey.startswith(\"Bearer\"): request_headers[\"Authorization\"] = f\"Bearer {apikey}\""
|
|
|
|
} else if swagger.Components.SecuritySchemes["BasicAuth"] != nil {
|
|
authenticationParameter = ", username_basic, password_basic"
|
|
authenticationSetup = "auth=None\n if username_basic or password_basic:\n if \"Authorization\" not in headers and \"Basic\" not in headers and not \"Bearer\" in headers:\n auth = requests.auth.HTTPBasicAuth(username_basic, password_basic)"
|
|
//authenticationAddin = ", auth=(username_basic, password_basic)"
|
|
authenticationAddin = ", auth=auth"
|
|
|
|
} else if swagger.Components.SecuritySchemes["ApiKeyAuth"] != nil {
|
|
authenticationParameter = ", apikey"
|
|
|
|
//if len(securitySchemes["ApiKeyAuth"].Value.Description) > 0 {
|
|
// //log.Printf("UPDATING AUTH!")
|
|
// extraParam.Description = fmt.Sprintf("Start with %s", securitySchemes["ApiKeyAuth"].Value.Description)
|
|
|
|
if swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.In == "header" {
|
|
// This is a way to bypass apikeys by passing " "
|
|
authenticationSetup = fmt.Sprintf(`if apikey != " ": request_headers["%s"] = apikey`, swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Name)
|
|
|
|
// Fixes token prefixes (e.g. Token.. or SSWS..)
|
|
if len(swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Description) > 0 {
|
|
trimmedDescription := strings.Trim(swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Description, " ")
|
|
|
|
authenticationSetup = fmt.Sprintf("if apikey != \" \":\n if apikey.startswith(\"%s\"):\n request_headers[\"%s\"] = apikey\n else:\n apikey = apikey.replace(\"%s\", \"\", -1).strip()\n request_headers[\"%s\"] = f\"%s{apikey}\"", trimmedDescription, swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Name, swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Description, swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Name, swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Description)
|
|
}
|
|
|
|
} else if swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.In == "query" {
|
|
// This might suck lol
|
|
//authenticationSetup = fmt.Sprintf("if apikey != \" \": params[\"%s\"] = requests.utils.quote(apikey)", swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Name)
|
|
|
|
trimmedDescription := strings.Trim(swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Description, " ")
|
|
|
|
authenticationSetup = fmt.Sprintf("if apikey != \" \":\n if apikey.startswith(\"%s\"):\n params[\"%s\"] = requests.utils.quote(apikey)\n else:\n apikey = apikey.replace(\"%s\", \"\", -1).strip()\n params[\"%s\"] = requests.utils.quote(f\"%s{apikey}\")", trimmedDescription, swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Name, swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Description, swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Name, swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Description)
|
|
}
|
|
|
|
} else if swagger.Components.SecuritySchemes["Oauth2"] != nil {
|
|
//log.Printf("[DEBUG] Appending Oauth2 code")
|
|
authenticationParameter = ", access_token"
|
|
authenticationSetup = fmt.Sprintf("if access_token != \" \": request_headers[\"Authorization\"] = f\"Bearer {access_token}\"\n #request_headers[\"Content-Type\"] = \"application/json\"")
|
|
|
|
} else if swagger.Components.SecuritySchemes["jwt"] != nil {
|
|
//log.Printf("[DEBUG] Appending Oauth2 code")
|
|
authenticationParameter = ", username_basic, password_basic"
|
|
//api.Authentication.TokenUri = securitySchemes["jwt"].Value.In
|
|
//authenticationSetup = fmt.Sprintf("authret = requests.get(f\"{url}%s\", headers=request_headers, auth=(username_basic, password_basic), verify=False)\n request_headers[\"Authorization\"] = f\"Bearer {authret.text}\"\n print(f\"{authret.text}\")", api.Authentication.TokenUri)
|
|
|
|
// Add: client_id and client_secret in body as JSON?
|
|
|
|
authType := "basic"
|
|
|
|
// check x-jwt-auth-type
|
|
if swagger.Components.SecuritySchemes["x-jwt-auth-type-json"] != nil {
|
|
authType = "json"
|
|
}
|
|
|
|
if authType == "basic" {
|
|
// ADD: accessToken = field
|
|
authenticationSetup = fmt.Sprintf("authret = requests.get(f\"{url}%s\", headers=request_headers, auth=(username_basic, password_basic), verify=False)\n if 'access_token' in authret.text:\n request_headers[\"Authorization\"] = f\"Bearer {authret.json()['access_token']}\"\n elif 'jwt' in authret.text:\n request_headers[\"Authorization\"] = f\"Bearer {authret.json()['jwt']}\"\n elif 'accessToken' in authret.text:\n request_headers[\"Authorization\"] = f\"Bearer {authret.json()['accessToken']}\"\n else:\n request_headers[\"Authorization\"] = f\"Bearer {authret.text}\"\n print(f\"Found Bearer auth: {authret.text}\")", api.Authentication.TokenUri)
|
|
} else {
|
|
authenticationSetup = fmt.Sprintf("authret = requests.post(f\"{url}%s\", headers=request_headers, json={\"username\": username_basic, \"password\": password_basic}, verify=False)\n if 'access_token' in authret.text:\n request_headers[\"Authorization\"] = f\"Bearer {authret.json()['access_token']}\"\n elif 'jwt' in authret.text:\n request_headers[\"Authorization\"] = f\"Bearer {authret.json()['jwt']}\"\n elif 'accessToken' in authret.text:\n request_headers[\"Authorization\"] = f\"Bearer {authret.json()['accessToken']}\"\n else:\n request_headers[\"Authorization\"] = f\"Bearer {authret.text}\"\n print(f\"Found Bearer auth: {authret.text}\")", api.Authentication.TokenUri)
|
|
}
|
|
|
|
//log.Printf("[DEBUG] Appending jwt code for authenticationSetup:\n %s", authenticationSetup)
|
|
}
|
|
}
|
|
|
|
urlSplit := strings.Split(url, "/")
|
|
if strings.HasPrefix(url, "http") && len(urlSplit) > 2 {
|
|
tmpUrl := strings.Join(urlSplit[3:len(urlSplit)], "/")
|
|
if len(tmpUrl) > 0 {
|
|
url = "/" + tmpUrl
|
|
} else {
|
|
if strings.HasSuffix(url, "/") {
|
|
url = "/"
|
|
} else {
|
|
url = ""
|
|
}
|
|
}
|
|
} else {
|
|
tmpUrl := ""
|
|
if len(urlSplit) > 2 {
|
|
tmpUrl = "/" + strings.Join(urlSplit[3:len(urlSplit)], "/")
|
|
}
|
|
|
|
if !strings.HasPrefix(url, "/") {
|
|
url = tmpUrl
|
|
}
|
|
}
|
|
|
|
functionname := strings.ToLower(fmt.Sprintf("%s_%s", method, name))
|
|
if strings.Contains(strings.ToLower(name), strings.ToLower(method)) {
|
|
functionname = strings.ToLower(name)
|
|
}
|
|
|
|
// Check for bad {} for python printf.
|
|
// If it's NOT closed before the next /
|
|
openBrackets := strings.Count(url, "{")
|
|
closeBrackets := strings.Count(url, "}")
|
|
if openBrackets != closeBrackets {
|
|
|
|
removedChars := 0
|
|
for charPos, char := range url {
|
|
if char != '{' {
|
|
continue
|
|
}
|
|
|
|
// Find the next / or end of string
|
|
nextClose := strings.Index(url[charPos:], "}")
|
|
nextSlash := strings.Index(url[charPos:], "/")
|
|
if nextClose == -1 || (nextSlash != -1 && nextSlash < nextClose) {
|
|
// We have a problem
|
|
log.Printf("[ERROR] Unbalanced bracket at position %d in URL %s", charPos, url)
|
|
|
|
// Remove the bracket from the specific spot.
|
|
url = url[0:charPos-removedChars] + url[charPos-removedChars+1:len(url)]
|
|
removedChars += 1
|
|
}
|
|
}
|
|
|
|
openBrackets := strings.Count(url, "{")
|
|
closeBrackets := strings.Count(url, "}")
|
|
if openBrackets != closeBrackets {
|
|
log.Printf("[ERROR] Unbalanced brackets in generated URL %s - this might cause issues, so we're skipping the function. App: %s. Autofixing.", url, name)
|
|
|
|
// Makes the function not generate
|
|
return functionname, ""
|
|
}
|
|
}
|
|
|
|
urlParameter := ", url"
|
|
urlInline := "{url}"
|
|
|
|
// Specific check for SSL verification
|
|
// This is critical for onprem stuff.
|
|
// Added to_file as of July 2022
|
|
verifyParam := ", ssl_verify=False, to_file=False"
|
|
verifyWrapper := `ssl_verify = True if str(ssl_verify).lower() == "true" or ssl_verify == "1" else False`
|
|
verifyAddin := ", verify=ssl_verify"
|
|
|
|
// Codegen for headers
|
|
headerParserCode := ""
|
|
queryParserCode := ""
|
|
if len(parameters) > 0 {
|
|
parameters = GetValidParameters(parameters)
|
|
parameterData = fmt.Sprintf(", %s", strings.Join(parameters, ", "))
|
|
|
|
// This is gibberish :)
|
|
for _, param := range parameters {
|
|
if strings.Contains(param, "headers=") {
|
|
headerParserCode = "if isinstance(headers, dict):\n request_headers = headers\n elif len(headers) > 0:\n for header in str(headers).split(\"\\n\"):\n if ':' in header:\n headersplit=header.split(':')\n request_headers[headersplit[0].strip()] = ':'.join(headersplit[1:]).strip()\n elif '=' in header:\n headersplit=header.split('=')\n request_headers[headersplit[0].strip()] = '='.join(headersplit[1:]).strip()"
|
|
|
|
} else if strings.Contains(param, "queries=") {
|
|
queryParserCode = "\n if len(queries) > 0:\n if isinstance(queries, dict):\n params=queries\n else:\n if queries[0] == \"?\" or queries[0] == \"&\":\n queries = queries[1:len(queries)]\n if queries[len(queries)-1] == \"?\" or queries[len(queries)-1] == \"&\":\n queries = queries[0:-1]\n for query in queries.split(\"&\"):\n if isinstance(query, list) or isinstance(query, dict):\n try:\n query = json.dumps(query)\n except:\n pass\n if '=' in query:\n headersplit=query.split('=')\n params[requests.utils.quote(headersplit[0].strip())] = requests.utils.quote(headersplit[1].strip())\n else:\n params[requests.utils.quote(query.strip())] = None\n params = '&'.join([k if v is None else f\"{k}={v}\" for k, v in params.items()])"
|
|
|
|
} else {
|
|
if !strings.Contains(url, fmt.Sprintf("{%s}", param)) {
|
|
queryData += fmt.Sprintf(`
|
|
if %s:
|
|
if isinstance(%s, list) or isinstance(%s, dict):
|
|
try:
|
|
%s = json.dumps(%s)
|
|
except:
|
|
pass
|
|
|
|
params[requests.utils.quote("%s")] = requests.utils.quote(%s)`, param, param, param, param, param, param, param)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bodyParameter := ""
|
|
bodyAddin := ""
|
|
bodyFormatter := ""
|
|
postParameters := []string{"post", "patch", "put", "delete"}
|
|
for _, item := range postParameters {
|
|
if method == item {
|
|
bodyParameter = ", body=\"\""
|
|
bodyAddin = ", data=body"
|
|
|
|
// FIXME: Does JSON data work?
|
|
bodyFormatter = "try:\n body = \" \".join(body.strip().split()).encode(\"utf-8\")\n except:\n pass"
|
|
}
|
|
}
|
|
|
|
preparedHeaders := "request_headers={}"
|
|
if len(headers) > 0 {
|
|
if method == "post" && len(fileField) > 0 {
|
|
} else {
|
|
preparedHeaders = "request_headers={"
|
|
for count, header := range headers {
|
|
headerSplit := strings.Split(header, "=")
|
|
|
|
added := false
|
|
if len(headerSplit) == 2 {
|
|
if strings.Contains(preparedHeaders, headerSplit[0]) {
|
|
continue
|
|
}
|
|
|
|
headerSplit[0] = strings.Replace(headerSplit[0], "\"", "", -1)
|
|
headerSplit[0] = strings.Replace(headerSplit[0], "'", "", -1)
|
|
headerSplit[1] = strings.Replace(headerSplit[1], "\"", "", -1)
|
|
headerSplit[1] = strings.Replace(headerSplit[1], "'", "", -1)
|
|
|
|
preparedHeaders += fmt.Sprintf(`"%s": "%s"`, headerSplit[0], headerSplit[1])
|
|
added = true
|
|
}
|
|
|
|
if count != len(headers)-1 && added {
|
|
preparedHeaders += ","
|
|
}
|
|
}
|
|
|
|
preparedHeaders += "}"
|
|
}
|
|
}
|
|
|
|
fileBalance := ""
|
|
fileAdder := ``
|
|
fileGrabber := ``
|
|
fileParameter := ``
|
|
contentTypeRemoval := "pass"
|
|
bodyParsing := "try:\n body = json.dumps(body)\n except:\n pass"
|
|
if method == "post" && len(fileField) > 0 {
|
|
fileParameter = ", file_id"
|
|
//fileGrabber = "filedata = self.get_file(file_id)\n print(f\"FILEDATA: {filedata}\")"
|
|
fileGrabber = "filedata = self.get_file(file_id)"
|
|
contentTypeRemoval = "del request_headers[contentType]"
|
|
|
|
// This indentation is confusing (but correct) ROFL
|
|
fileAdder = fmt.Sprintf(`if not filedata["success"]:
|
|
return {"success": False, "reason": f"{file_id} is not a valid File ID"}
|
|
|
|
files = {"%s": (filedata["filename"], filedata["data"])}`, fileField)
|
|
|
|
fileBalance = ", files=files"
|
|
|
|
bodyParsing = ""
|
|
}
|
|
|
|
// Removes duplicate file IDs
|
|
if strings.Contains(parameterData, `, file_id=""`) && strings.Contains(fileParameter, ", file_id") {
|
|
parameterData = strings.Replace(parameterData, `, file_id=""`, "", -1)
|
|
} else if strings.Contains(parameterData, `, file_id`) && strings.Contains(fileParameter, ", file_id") {
|
|
parameterData = strings.Replace(parameterData, ", file_id", "", -1)
|
|
}
|
|
|
|
if swagger.Components.SecuritySchemes != nil {
|
|
for key, value := range swagger.Components.SecuritySchemes {
|
|
if ArrayContains(reservedKeys, key) {
|
|
continue
|
|
}
|
|
|
|
//parsedKey := strings.Replace(key, "-", "_", -1)
|
|
parsedKey := FixFunctionName(key, "", true)
|
|
|
|
if value.Value.In == "header" {
|
|
queryString += fmt.Sprintf(", %s=\"\"", parsedKey)
|
|
if len(extraHeaders) > 0 {
|
|
extraHeaders += "\n "
|
|
}
|
|
|
|
extraHeaders += fmt.Sprintf(`if %s != " ": request_headers["%s"] = %s`, parsedKey, key, parsedKey)
|
|
} else if value.Value.In == "query" {
|
|
//log.Printf("Handling extra queries for %#v", parsedKey)
|
|
if strings.Contains(parsedKey, "=") {
|
|
parsedKey = strings.Split(parsedKey, "=")[0]
|
|
}
|
|
|
|
queryString += fmt.Sprintf(", %s=\"\"", parsedKey)
|
|
if len(extraQueries) > 0 {
|
|
extraQueries += "\n "
|
|
}
|
|
extraQueries += fmt.Sprintf(`if %s != " ": params["%s"] = %s`, parsedKey, key, parsedKey)
|
|
} else {
|
|
//log.Printf("[WARNING] Can't handle type %s", value.Value.In)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Extra param for url if it's changeable
|
|
// Extra param for authentication scheme(s)
|
|
// The last weird one is the body.. Tabs & spaces sucks.
|
|
parsedParameters := fmt.Sprintf("%s%s%s%s%s%s%s",
|
|
authenticationParameter,
|
|
urlParameter,
|
|
fileParameter,
|
|
parameterData,
|
|
queryString,
|
|
bodyParameter,
|
|
verifyParam,
|
|
)
|
|
|
|
// Dedup parameters
|
|
parsedParametersSplit := strings.Split(parsedParameters, ",")
|
|
newParameters := []string{}
|
|
usedParams := []string{}
|
|
for _, param := range parsedParametersSplit {
|
|
param = strings.Trim(param, " ")
|
|
if param == "" {
|
|
continue
|
|
}
|
|
|
|
paramsplit := strings.Split(param, "=")
|
|
if len(paramsplit) > 1 {
|
|
param = paramsplit[0]
|
|
}
|
|
|
|
if !ArrayContains(usedParams, param) {
|
|
usedParams = append(usedParams, param)
|
|
if len(paramsplit) > 1 {
|
|
param = strings.Join(paramsplit, "=")
|
|
}
|
|
|
|
newParameters = append(newParameters, param)
|
|
}
|
|
}
|
|
|
|
parsedParameters = strings.Join(newParameters, ", ")
|
|
|
|
// Handles default return value
|
|
handleFileString := "if not to_file:\n return self.prepare_response(ret)\n\n return ret.text"
|
|
|
|
parsedDataCurlParser := ""
|
|
if method == "post" || method == "patch" || method == "put" || method == "delete" {
|
|
parsedDataCurlParser = `parsed_curl_command += f""" -d '{body}'""" if isinstance(body, str) else f""" -d '{body.decode("utf-8")}'"""`
|
|
}
|
|
|
|
// Makes sure to reformat references
|
|
if !strings.HasPrefix(parsedParameters, ",") {
|
|
parsedParameters = fmt.Sprintf(", %s", parsedParameters)
|
|
}
|
|
|
|
data := fmt.Sprintf(` def %s(self%s):
|
|
print(f"Started function %s")
|
|
params={}
|
|
%s
|
|
url=f"%s%s"
|
|
%s
|
|
%s
|
|
%s
|
|
%s
|
|
%s
|
|
%s
|
|
%s
|
|
%s
|
|
%s
|
|
%s
|
|
if str(to_file).lower() == "true":
|
|
to_file = True
|
|
else:
|
|
to_file = False
|
|
|
|
if "http:/" in url and not "http://" in url:
|
|
url = url.replace("http:/", "http://", -1)
|
|
if "https:/" in url and not "https://" in url:
|
|
url = url.replace("https:/", "https://", -1)
|
|
if "http:///" in url:
|
|
url = url.replace("http:///", "http://", -1)
|
|
if "https:///" in url:
|
|
url = url.replace("https:///", "https://", -1)
|
|
if not "http://" in url and not "http" in url:
|
|
url = f"http://{url}"
|
|
|
|
%s
|
|
|
|
found = False
|
|
contentType = ""
|
|
for key, value in request_headers.items():
|
|
if key.lower() == "user-agent":
|
|
found = True
|
|
if key.lower() == "content-type":
|
|
contentType = key
|
|
|
|
if len(contentType) > 0:
|
|
%s
|
|
|
|
if not found:
|
|
request_headers["User-Agent"] = "Shuffle Automation"
|
|
|
|
try:
|
|
#parsed_headers = [sys.stdout.write(f" -H \"{key}: {value}\"") for key, value in request_headers.items()]
|
|
parsed_headers = ""
|
|
parsed_curl_command = f"curl -X%s {url} {parsed_headers}"
|
|
%s
|
|
|
|
self.action["parameters"].append({
|
|
"name": "shuffle_request_url",
|
|
"value": f"{url}",
|
|
})
|
|
self.action["parameters"].append({
|
|
"name": "shuffle_request_curl",
|
|
"value": f"{parsed_curl_command}",
|
|
})
|
|
self.action["parameters"].append({
|
|
"name": "shuffle_request_headers",
|
|
"value": f"{json.dumps(parsed_headers)}",
|
|
})
|
|
|
|
self.action_result["action"] = self.action
|
|
print("[DEBUG] Updated values in self.action_result from OpenAPI app! (1)")
|
|
except Exception as e:
|
|
print(f"[WARNING] Something went wrong when adding extra returns (1). {e}")
|
|
|
|
session = requests.Session()
|
|
ret = session.%s(url, headers=request_headers, params=params%s%s%s%s)
|
|
try:
|
|
found = False
|
|
for item in self.action["parameters"]:
|
|
if item["name"] == "shuffle_response_status":
|
|
found = True
|
|
break
|
|
|
|
if not found:
|
|
self.action["parameters"].append({
|
|
"name": "shuffle_response_status",
|
|
"value": f"{ret.status_code}",
|
|
})
|
|
self.action["parameters"].append({
|
|
"name": "shuffle_response_length",
|
|
"value": f"{len(ret.text)}",
|
|
})
|
|
self.action["parameters"].append({
|
|
"name": "shuffle_request_cookies",
|
|
"value": f"{json.dumps(session.cookies.get_dict())}",
|
|
})
|
|
print("[DEBUG] Updated values in self.action_result from OpenAPI app! (2)")
|
|
|
|
except Exception as e:
|
|
print(f"[WARNING] Something went wrong when adding extra returns (2). {e}")
|
|
|
|
if to_file:
|
|
# If content encoding or transfer encoding is base64, decode it
|
|
if ("content-encoding" in ret.headers.keys() and "base64" in ret.headers["content-encoding"].lower()) or ("transfer-encoding" in ret.headers.keys() and "base64" in ret.headers["transfer-encoding"].lower()) or ("content-transfer-encoding" in ret.headers.keys() and "base64" in ret.headers["content-transfer-encoding"].lower()):
|
|
print("[DEBUG] Content encoding is base64, decoding it")
|
|
ret.content = base64.b64decode(ret.content)
|
|
|
|
|
|
filedata = {
|
|
"filename": "response",
|
|
"data": ret.content,
|
|
}
|
|
|
|
fileret = self.set_files([filedata])
|
|
if len(fileret) == 1:
|
|
return {"success": True, "file_id": fileret[0], "status": ret.status_code}
|
|
|
|
return fileret
|
|
|
|
%s
|
|
`,
|
|
functionname,
|
|
parsedParameters,
|
|
functionname,
|
|
preparedHeaders,
|
|
urlInline,
|
|
url,
|
|
verifyWrapper,
|
|
extraHeaders,
|
|
extraQueries,
|
|
authenticationSetup,
|
|
headerParserCode,
|
|
queryData,
|
|
queryParserCode,
|
|
bodyFormatter,
|
|
fileGrabber,
|
|
fileAdder,
|
|
bodyParsing,
|
|
contentTypeRemoval,
|
|
strings.ToUpper(method),
|
|
parsedDataCurlParser,
|
|
method,
|
|
authenticationAddin,
|
|
bodyAddin,
|
|
verifyAddin,
|
|
fileBalance,
|
|
handleFileString,
|
|
)
|
|
|
|
// Use lowercase when checking
|
|
if strings.Contains(strings.ToLower(functionname), "get_list_all_issues") {
|
|
log.Printf("\n%s", data)
|
|
}
|
|
|
|
return functionname, data
|
|
}
|
|
|
|
func GetCustomActionCode(swagger *openapi3.Swagger, api WorkflowApp) string {
|
|
|
|
authenticationParameter := ""
|
|
authenticationSetup := ""
|
|
authenticationAddin := ""
|
|
|
|
if swagger.Components.SecuritySchemes != nil {
|
|
if swagger.Components.SecuritySchemes["BearerAuth"] != nil {
|
|
authenticationParameter = ", apikey"
|
|
authenticationSetup = "if apikey != \" \" and not apikey.startswith(\"Bearer\"): parsed_headers[\"Authorization\"] = f\"Bearer {apikey}\""
|
|
|
|
} else if swagger.Components.SecuritySchemes["BasicAuth"] != nil {
|
|
authenticationParameter = ", username_basic, password_basic"
|
|
authenticationSetup = "auth=None\n if username_basic or password_basic:\n if \"Authorization\" not in headers and \"Basic\" not in headers and not \"Bearer\" in headers:\n auth = requests.auth.HTTPBasicAuth(username_basic, password_basic)"
|
|
authenticationAddin = ", auth=auth"
|
|
|
|
} else if swagger.Components.SecuritySchemes["ApiKeyAuth"] != nil {
|
|
authenticationParameter = ", apikey"
|
|
|
|
if swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.In == "header" {
|
|
|
|
authenticationSetup = fmt.Sprintf(`if apikey != " ": parsed_headers["%s"] = apikey`, swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Name)
|
|
|
|
if len(swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Description) > 0 {
|
|
trimmedDescription := strings.Trim(swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Description, " ")
|
|
|
|
authenticationSetup = fmt.Sprintf("if apikey != \" \":\n if apikey.startswith(\"%s\"):\n parsed_headers[\"%s\"] = apikey\n else:\n apikey = apikey.replace(\"%s\", \"\", -1).strip()\n parsed_headers[\"%s\"] = f\"%s{apikey}\"", trimmedDescription, swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Name, swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Description, swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Name, swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Description)
|
|
}
|
|
|
|
} else if swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.In == "query" {
|
|
|
|
//authenticationSetup = fmt.Sprintf("if apikey != \" \": parsed_queries[\"%s\"] = requests.utils.quote(apikey)", swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Name)
|
|
trimmedDescription := strings.Trim(swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Description, " ")
|
|
|
|
authenticationSetup = fmt.Sprintf("if apikey != \" \":\n if apikey.startswith(\"%s\"):\n parsed_queries[\"%s\"] = requests.utils.quote(apikey)\n else:\n apikey = apikey.replace(\"%s\", \"\", -1).strip()\n parsed_queries[\"%s\"] = requests.utils.quote(f\"%s{apikey}\")", trimmedDescription, swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Name, swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Description, swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Name, swagger.Components.SecuritySchemes["ApiKeyAuth"].Value.Description)
|
|
}
|
|
|
|
} else if swagger.Components.SecuritySchemes["Oauth2"] != nil {
|
|
|
|
authenticationParameter = ", access_token"
|
|
authenticationSetup = fmt.Sprintf("if access_token != \" \": parsed_headers[\"Authorization\"] = f\"Bearer {access_token}\"\n #parsed_headers[\"Content-Type\"] = \"application/json\"")
|
|
|
|
} else if swagger.Components.SecuritySchemes["jwt"] != nil {
|
|
authenticationParameter = ", username_basic, password_basic"
|
|
authenticationSetup = fmt.Sprintf("authret = requests.get(f\"{url}%s\", headers=parsed_headers, auth=(username_basic, password_basic), verify=False)\n if 'access_token' in authret.text:\n parsed_headers[\"Authorization\"] = f\"Bearer {authret.json()['access_token']}\"\n elif 'jwt' in authret.text:\n parsed_headers[\"Authorization\"] = f\"Bearer {authret.json()['jwt']}\"\n elif 'accessToken' in authret.text:\n parsed_headers[\"Authorization\"] = f\"Bearer {authret.json()['accessToken']}\"\n else:\n parsed_headers[\"Authorization\"] = f\"Bearer {authret.text}\"\n print(f\"Found Bearer auth: {authret.text}\")", api.Authentication.TokenUri)
|
|
}
|
|
|
|
}
|
|
|
|
pythonCode := fmt.Sprintf(`
|
|
def fix_url(self, url, path=False):
|
|
if "hhttp" in url:
|
|
url = url.replace("hhttp", "http")
|
|
|
|
if url.startswith("http//"):
|
|
url = url.replace("http//", "http://")
|
|
if url.startswith("https//"):
|
|
url = url.replace("https//", "https://")
|
|
|
|
if "http:/" in url and not "http://" in url:
|
|
url = url.replace("http:/", "http://", -1)
|
|
if "https:/" in url and not "https://" in url:
|
|
url = url.replace("https:/", "https://", -1)
|
|
if "http:///" in url:
|
|
url = url.replace("http:///", "http://", -1)
|
|
if "https:///" in url:
|
|
url = url.replace("https:///", "https://", -1)
|
|
if not path and not "http://" in url and not "http" in url:
|
|
url = f"http://{url}"
|
|
|
|
return url
|
|
|
|
|
|
def checkverify(self, verify):
|
|
if str(verify).lower().strip() == "false":
|
|
return False
|
|
elif verify is None:
|
|
return False
|
|
elif verify:
|
|
return True
|
|
elif not verify:
|
|
return False
|
|
else:
|
|
return True
|
|
|
|
|
|
def is_valid_method(self, method):
|
|
valid_methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"]
|
|
method = method.upper()
|
|
|
|
if method in valid_methods:
|
|
return method
|
|
else:
|
|
raise ValueError(f"Invalid HTTP method: {method}")
|
|
|
|
|
|
def parse_headers(self, headers):
|
|
parsed_headers = {}
|
|
if headers:
|
|
split_headers = headers.split("\n")
|
|
self.logger.info(split_headers)
|
|
for header in split_headers:
|
|
if ":" in header:
|
|
splititem = ":"
|
|
elif "=" in header:
|
|
splititem = "="
|
|
else:
|
|
continue
|
|
|
|
splitheader = header.split(splititem)
|
|
if len(splitheader) >= 2:
|
|
parsed_headers[splitheader[0].strip()] = splititem.join(
|
|
splitheader[1:]
|
|
).strip()
|
|
else:
|
|
continue
|
|
|
|
return parsed_headers
|
|
|
|
def parse_queries(self, queries):
|
|
parsed_queries = {}
|
|
if not queries:
|
|
return parsed_queries
|
|
|
|
cleaned_queries = queries.strip()
|
|
if not cleaned_queries:
|
|
return parsed_queries
|
|
|
|
cleaned_queries = " ".join(cleaned_queries.split())
|
|
splitted_queries = cleaned_queries.split("&")
|
|
for query in splitted_queries:
|
|
if not query:
|
|
continue
|
|
|
|
querysplit = query.split("=")
|
|
if len(querysplit) == 0:
|
|
parsed_queries[query.strip()] = ""
|
|
else:
|
|
queryvalue = "=".join(querysplit[1:])
|
|
parsed_queries[querysplit[0].strip()] = queryvalue.strip()
|
|
|
|
return parsed_queries
|
|
|
|
def prepare_response(self, request):
|
|
try:
|
|
parsedheaders = {}
|
|
for key, value in request.headers.items():
|
|
parsedheaders[key] = value
|
|
|
|
cookies = {}
|
|
if request.cookies:
|
|
for key, value in request.cookies.items():
|
|
cookies[key] = value
|
|
|
|
|
|
jsondata = request.text
|
|
try:
|
|
jsondata = json.loads(jsondata)
|
|
except:
|
|
pass
|
|
|
|
parseddata = {
|
|
"status": request.status_code,
|
|
"body": jsondata,
|
|
"url": request.url,
|
|
"headers": parsedheaders,
|
|
"cookies":cookies,
|
|
"success": True,
|
|
}
|
|
|
|
return json.dumps(parseddata)
|
|
except Exception as e:
|
|
print(f"[WARNING] Failed in request: {e}")
|
|
return request.text
|
|
|
|
|
|
def custom_action(self%s, method="", url="", headers="", queries="", path="", ssl_verify=False, body=""):
|
|
url = self.fix_url(url)
|
|
|
|
try:
|
|
method = self.is_valid_method(method)
|
|
except ValueError as e:
|
|
self.logger.error(e)
|
|
return {"error": str(e)}
|
|
|
|
if not path:
|
|
path = "/"
|
|
|
|
path = self.fix_url(path, path=True)
|
|
if path and path.startswith(url):
|
|
path = path.replace(url, "", 1)
|
|
|
|
if path and not path.startswith('/'):
|
|
path = '/' + path
|
|
|
|
url += path
|
|
|
|
parsed_headers = {}
|
|
parsed_queries = {}
|
|
|
|
%s
|
|
|
|
# Allows overwriting of existing headers with custom input ones
|
|
additional_headers = self.parse_headers(headers)
|
|
try:
|
|
parsed_headers.update(additional_headers)
|
|
except Exception as e:
|
|
print(f"Header parse error: {e}")
|
|
|
|
additional_queries = self.parse_queries(queries)
|
|
try:
|
|
parsed_queries.update(additional_queries)
|
|
except Exception as e:
|
|
print(f"Query parse error: {e}")
|
|
|
|
ssl_verify = self.checkverify(ssl_verify)
|
|
|
|
if isinstance(body, dict):
|
|
try:
|
|
body = json.dumps(body)
|
|
except json.JSONDecodeError as e:
|
|
self.logger.error(f"error : {e}")
|
|
return {"error: Invalid JSON format for request body"}
|
|
|
|
try:
|
|
response = requests.request(method, url, headers=parsed_headers, params=parsed_queries, data=body, verify=ssl_verify%s) #response.raise_for_status()
|
|
|
|
return self.prepare_response(response)
|
|
|
|
except requests.RequestException as e:
|
|
self.logger.error(f"Request failed: {e}")
|
|
return {"error": f"Request failed: {e}"}
|
|
`, authenticationParameter, authenticationSetup, authenticationAddin)
|
|
|
|
return pythonCode
|
|
}
|
|
|
|
func AddCustomAction(swagger *openapi3.Swagger, api WorkflowApp) (WorkflowAppAction, string) {
|
|
|
|
parameters := []WorkflowAppActionParameter{}
|
|
pyCode := GetCustomActionCode(swagger, api)
|
|
|
|
securitySchemes := swagger.Components.SecuritySchemes
|
|
if securitySchemes != nil {
|
|
|
|
if securitySchemes["BearerAuth"] != nil {
|
|
|
|
parameters = append(parameters, WorkflowAppActionParameter{
|
|
Name: "apikey",
|
|
Description: "The apikey to use",
|
|
Multiline: false,
|
|
Required: true,
|
|
Example: "The API key to use. Space = skip",
|
|
Configuration: true,
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
} else if securitySchemes["ApiKeyAuth"] != nil {
|
|
|
|
extraParam := WorkflowAppActionParameter{
|
|
Name: "apikey",
|
|
Description: "The apikey to use",
|
|
Multiline: false,
|
|
Required: true,
|
|
Example: "**********",
|
|
Configuration: true,
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
}
|
|
|
|
if len(securitySchemes["ApiKeyAuth"].Value.Description) > 0 {
|
|
extraParam.Description = fmt.Sprintf("Start with %s", securitySchemes["ApiKeyAuth"].Value.Description)
|
|
}
|
|
|
|
parameters = append(parameters, extraParam)
|
|
|
|
} else if securitySchemes["jwt"] != nil {
|
|
|
|
parameters = append(parameters, WorkflowAppActionParameter{
|
|
Name: "username_basic",
|
|
Description: "The username to use",
|
|
Multiline: false,
|
|
Required: true,
|
|
Example: "The username to use",
|
|
Configuration: true,
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
parameters = append(parameters, WorkflowAppActionParameter{
|
|
Name: "password_basic",
|
|
Description: "The password to use",
|
|
Multiline: false,
|
|
Required: true,
|
|
Example: "***********",
|
|
Configuration: true,
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
} else if securitySchemes["BasicAuth"] != nil {
|
|
|
|
parameters = append(parameters, WorkflowAppActionParameter{
|
|
Name: "username_basic",
|
|
Description: "The username to use",
|
|
Multiline: false,
|
|
Required: true,
|
|
Example: "The username to use",
|
|
Configuration: true,
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
parameters = append(parameters, WorkflowAppActionParameter{
|
|
Name: "password_basic",
|
|
Description: "The password to use",
|
|
Multiline: false,
|
|
Required: true,
|
|
Example: "***********",
|
|
Configuration: true,
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
parameters = append(parameters, WorkflowAppActionParameter{
|
|
Name: "method",
|
|
Description: "The http method to use",
|
|
Multiline: false,
|
|
Required: true,
|
|
Options: []string{"GET", "POST", "PUT", "DELETE", "PATCH"},
|
|
Example: "GET",
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
|
|
parameters = append(parameters, WorkflowAppActionParameter{
|
|
Name: "url",
|
|
Description: "The URL of the API",
|
|
Multiline: false,
|
|
Required: true,
|
|
Example: "https://api.example.com",
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
|
|
parameters = append(parameters, WorkflowAppActionParameter{
|
|
Name: "path",
|
|
Description: "the path to add to the base url",
|
|
Multiline: false,
|
|
Required: false,
|
|
Example: "/users/profile",
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
|
|
parameters = append(parameters, WorkflowAppActionParameter{
|
|
Name: "headers",
|
|
Description: "Add or edit headers",
|
|
Multiline: true,
|
|
Required: false,
|
|
Example: "Content-Type:application/json\nAccept:application/json",
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
|
|
parameters = append(parameters, WorkflowAppActionParameter{
|
|
Name: "queries",
|
|
Description: "Add or edit queries",
|
|
Multiline: true,
|
|
Required: false,
|
|
Example: "view=basic&redirect=test",
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
|
|
parameters = append(parameters, WorkflowAppActionParameter{
|
|
Name: "ssl_verify",
|
|
Description: "Check if you want to verify request",
|
|
Multiline: false,
|
|
Options: []string{"False", "True"},
|
|
Required: false,
|
|
Example: "False",
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
|
|
parameters = append(parameters, WorkflowAppActionParameter{
|
|
Name: "body",
|
|
Description: "The body to use",
|
|
Multiline: true,
|
|
Required: false,
|
|
Example: `{"username": "example_user", "email": "user@example.com"}`,
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
|
|
action := WorkflowAppAction{
|
|
Description: "add a custom action for your app",
|
|
Name: "custom_action",
|
|
NodeType: "action",
|
|
Environment: "Shuffle",
|
|
Parameters: parameters,
|
|
}
|
|
|
|
action.Returns.Schema.Type = "string"
|
|
|
|
return action, pyCode
|
|
|
|
}
|
|
|
|
func GenerateYaml(swagger *openapi3.Swagger, newmd5 string) (*openapi3.Swagger, WorkflowApp, []string, error) {
|
|
api := WorkflowApp{}
|
|
//log.Printf("%#v", swagger.Info)
|
|
if swagger.Info == nil {
|
|
return swagger, WorkflowApp{}, []string{}, errors.New("Swagger.Info can't be empty.")
|
|
}
|
|
|
|
if len(swagger.Info.Title) == 0 {
|
|
return swagger, WorkflowApp{}, []string{}, errors.New("Swagger.Info.Title can't be empty.")
|
|
}
|
|
|
|
if len(swagger.Servers) == 0 {
|
|
//return swagger, WorkflowApp{}, []string{}, errors.New("Swagger.Servers can't be empty. Add 'servers':[{'url':'hostname.com'}'")
|
|
//return swagger, WorkflowApp{}, []string{}, errors.New("Swagger.Servers can't be empty. Add 'servers':[{'url':'hostname.com'}'")
|
|
swagger.Servers = openapi3.Servers{
|
|
&openapi3.Server{
|
|
URL: "https://hostname.com",
|
|
},
|
|
}
|
|
}
|
|
|
|
api.Name = swagger.Info.Title
|
|
api.Description = swagger.Info.Description
|
|
|
|
// FIXME: Versioning issue?
|
|
api.ID = newmd5
|
|
//uuid.NewV4().String()
|
|
|
|
api.IsValid = true
|
|
api.Link = swagger.Servers[0].URL // host does not exist lol
|
|
if strings.HasSuffix(api.Link, "/") {
|
|
api.Link = api.Link[:len(api.Link)-1]
|
|
}
|
|
|
|
example := "https://api-url"
|
|
if len(api.Link) > 0 {
|
|
example = api.Link
|
|
linkSplit := strings.Split(api.Link, "/")
|
|
if len(linkSplit) > 3 {
|
|
example = strings.Join(linkSplit[0:3], "/")
|
|
}
|
|
|
|
//log.Printf("EXAMPLE: %s", example)
|
|
}
|
|
|
|
api.AppVersion = "1.1.0"
|
|
api.Environment = "Shuffle"
|
|
api.SmallImage = ""
|
|
api.LargeImage = ""
|
|
api.Sharing = false
|
|
api.Verified = false
|
|
api.Tested = false
|
|
api.Invalid = false
|
|
api.PrivateID = newmd5
|
|
api.Generated = true
|
|
api.Activated = true
|
|
// Setting up security schemes
|
|
extraParameters := []WorkflowAppActionParameter{}
|
|
|
|
if val, ok := swagger.Info.ExtensionProps.Extensions["x-logo"]; ok {
|
|
j, err := json.Marshal(&val)
|
|
if err == nil {
|
|
if j[0] == 0x22 && j[len(j)-1] == 0x22 {
|
|
j = j[1 : len(j)-1]
|
|
}
|
|
|
|
//log.Printf("%s", j)
|
|
api.SmallImage = string(j)
|
|
api.LargeImage = string(j)
|
|
}
|
|
}
|
|
|
|
// Jesus what a clusterfuck.
|
|
// Handles parsing of categories from OpenApi3 custom field
|
|
if val, ok := swagger.Info.ExtensionProps.Extensions["x-categories"]; ok {
|
|
//log.Printf("Categories: %#v", val)
|
|
j, err := json.Marshal(&val)
|
|
if err == nil {
|
|
if j[0] == 0x22 && j[len(j)-1] == 0x22 {
|
|
j = j[1 : len(j)-1]
|
|
}
|
|
|
|
parsedCategories := fmt.Sprintf(`{"categories": %s}`, string(j))
|
|
type parsed struct {
|
|
Categories []string `json:"categories"`
|
|
}
|
|
|
|
var parse parsed
|
|
err := json.Unmarshal([]byte(parsedCategories), &parse)
|
|
if err != nil {
|
|
log.Printf("Failed unmarshaling categories: %s", err)
|
|
} else {
|
|
api.Categories = parse.Categories
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(swagger.Tags) > 0 {
|
|
newTags := []string{}
|
|
for _, tag := range swagger.Tags {
|
|
newTags = append(newTags, tag.Name)
|
|
}
|
|
|
|
api.Tags = newTags
|
|
}
|
|
|
|
securitySchemes := swagger.Components.SecuritySchemes
|
|
reservedKeys := []string{"BearerAuth", "ApiKeyAuth", "Oauth2", "BasicAuth", "jwt"}
|
|
|
|
if securitySchemes != nil {
|
|
//log.Printf("%#v", securitySchemes)
|
|
|
|
api.Authentication = Authentication{
|
|
Required: true,
|
|
Parameters: []AuthenticationParams{},
|
|
}
|
|
|
|
// Used for python code generation lol
|
|
// Not sure how this should work with oauth
|
|
if securitySchemes["BearerAuth"] != nil {
|
|
api.Authentication.Parameters = append(api.Authentication.Parameters, AuthenticationParams{
|
|
Name: "apikey",
|
|
Value: "",
|
|
Example: "******",
|
|
Description: securitySchemes["BearerAuth"].Value.Description,
|
|
In: securitySchemes["BearerAuth"].Value.In,
|
|
Scheme: securitySchemes["BearerAuth"].Value.Scheme,
|
|
Schema: SchemaDefinition{
|
|
Type: securitySchemes["BearerAuth"].Value.Scheme,
|
|
},
|
|
})
|
|
|
|
//log.Printf("HANDLE BEARER AUTH")
|
|
extraParameters = append(extraParameters, WorkflowAppActionParameter{
|
|
Name: "apikey",
|
|
Description: "The apikey to use",
|
|
Multiline: false,
|
|
Required: true,
|
|
Example: "The API key to use. Space = skip",
|
|
Configuration: true,
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
} else if securitySchemes["ApiKeyAuth"] != nil {
|
|
//log.Printf("AUTH:%#v", securitySchemes["ApiKeyAuth"].Value)
|
|
newAuthParam := AuthenticationParams{
|
|
Name: "apikey",
|
|
Value: "",
|
|
Example: "******",
|
|
Description: securitySchemes["ApiKeyAuth"].Value.Description,
|
|
In: securitySchemes["ApiKeyAuth"].Value.In,
|
|
Scheme: securitySchemes["ApiKeyAuth"].Value.Scheme,
|
|
Schema: SchemaDefinition{
|
|
Type: securitySchemes["ApiKeyAuth"].Value.Scheme,
|
|
},
|
|
}
|
|
|
|
//Example: securitySchemes["ApiKeyAuth"].Value.Example,
|
|
|
|
//log.Printf("HANDLE APIKEY AUTH")
|
|
extraParam := WorkflowAppActionParameter{
|
|
Name: "apikey",
|
|
Description: "The apikey to use",
|
|
Multiline: false,
|
|
Required: true,
|
|
Example: "**********",
|
|
Configuration: true,
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
}
|
|
|
|
if len(securitySchemes["ApiKeyAuth"].Value.Description) > 0 {
|
|
//log.Printf("UPDATING AUTH!")
|
|
extraParam.Description = fmt.Sprintf("Start with %s", securitySchemes["ApiKeyAuth"].Value.Description)
|
|
newAuthParam.Description = fmt.Sprintf("Start with %s", securitySchemes["ApiKeyAuth"].Value.Description)
|
|
}
|
|
|
|
api.Authentication.Parameters = append(api.Authentication.Parameters, newAuthParam)
|
|
extraParameters = append(extraParameters, extraParam)
|
|
} else if securitySchemes["Oauth2"] != nil {
|
|
api.Authentication.Type = "oauth2"
|
|
if val, ok := securitySchemes["Oauth2"].Value.ExtensionProps.Extensions["flow"]; ok {
|
|
newValue := string(fmt.Sprintf("%s", string(val.(json.RawMessage))))
|
|
//log.Printf("DATA: %s", newValue)
|
|
|
|
var parsed Oauth2Openapi
|
|
err := json.Unmarshal([]byte(newValue), &parsed)
|
|
if err != nil {
|
|
log.Printf("[WARNING] Failed to unmarshal Oauth2 data for app %s", api.Name)
|
|
} else {
|
|
log.Printf("[DEBUG] Set up Oauth2 config for app %s during generation", api.Name)
|
|
api.Authentication.Type = "oauth2"
|
|
|
|
api.Authentication.RedirectUri = parsed.AuthorizationCode.AuthorizationUrl
|
|
api.Authentication.TokenUri = parsed.AuthorizationCode.TokenUrl
|
|
api.Authentication.RefreshUri = parsed.AuthorizationCode.RefreshUrl
|
|
api.Authentication.Scope = parsed.AuthorizationCode.Scopes
|
|
}
|
|
} else {
|
|
log.Printf("[ERROR] No Oauth2 data to parse for app %s - bad parsing?", api.Name)
|
|
return swagger, WorkflowApp{}, []string{}, errors.New("Missing Oauth2 refreshUrl, scope, authorization URL or Token URL")
|
|
}
|
|
|
|
if val, ok := securitySchemes["Oauth2"].Value.ExtensionProps.Extensions["x-grant-type"]; ok {
|
|
|
|
// Make val from json.rawMessage into a string
|
|
newValue := string(fmt.Sprintf("%s", string(val.(json.RawMessage))))
|
|
// Check if quotes on it
|
|
if len(newValue) > 2 && newValue[0] == '"' && newValue[len(newValue)-1] == '"' {
|
|
newValue = newValue[1 : len(newValue)-1]
|
|
}
|
|
|
|
// November 2023: password & client_credentials
|
|
// Fix mar 2024: set type to oauth2-app
|
|
if len(newValue) > 0 {
|
|
api.Authentication.GrantType = newValue
|
|
api.Authentication.Type = "oauth2-app"
|
|
}
|
|
|
|
log.Printf("[DEBUG] Got special app build grant type: %s", newValue)
|
|
}
|
|
|
|
api.Authentication.Parameters = append(api.Authentication.Parameters, AuthenticationParams{
|
|
Name: "client_id",
|
|
Value: "",
|
|
Example: "client_id",
|
|
Description: securitySchemes["Oauth2"].Value.Description,
|
|
In: securitySchemes["Oauth2"].Value.In,
|
|
Scheme: securitySchemes["Oauth2"].Value.Scheme,
|
|
Schema: SchemaDefinition{
|
|
Type: securitySchemes["Oauth2"].Value.Scheme,
|
|
},
|
|
})
|
|
|
|
/*
|
|
api.Authentication.Parameters = append(api.Authentication.Parameters, AuthenticationParams{
|
|
Name: "client_id",
|
|
Value: "",
|
|
Example: "client_id",
|
|
Description: securitySchemes["Oauth2"].Value.Description,
|
|
In: securitySchemes["Oauth2"].Value.In,
|
|
Scheme: securitySchemes["Oauth2"].Value.Scheme,
|
|
Schema: SchemaDefinition{
|
|
Type: securitySchemes["Oauth2"].Value.Scheme,
|
|
},
|
|
})
|
|
*/
|
|
|
|
api.Authentication.Parameters = append(api.Authentication.Parameters, AuthenticationParams{
|
|
Name: "client_secret",
|
|
Value: "",
|
|
Example: "client_secret",
|
|
Description: securitySchemes["Oauth2"].Value.Description,
|
|
In: securitySchemes["Oauth2"].Value.In,
|
|
Scheme: securitySchemes["Oauth2"].Value.Scheme,
|
|
Schema: SchemaDefinition{
|
|
Type: securitySchemes["Oauth2"].Value.Scheme,
|
|
},
|
|
})
|
|
|
|
// Check for securitySchemes
|
|
//} else if securitySchemes["Oauth2"] != nil {
|
|
} else if securitySchemes["jwt"] != nil {
|
|
if len(securitySchemes["jwt"].Value.In) > 0 {
|
|
api.Authentication.TokenUri = securitySchemes["jwt"].Value.In
|
|
}
|
|
|
|
api.Authentication.Parameters = append(api.Authentication.Parameters, AuthenticationParams{
|
|
Name: "username_basic",
|
|
Value: "",
|
|
Example: "username",
|
|
Description: "",
|
|
In: "",
|
|
Scheme: "",
|
|
Schema: SchemaDefinition{
|
|
Type: securitySchemes["jwt"].Value.Scheme,
|
|
},
|
|
})
|
|
|
|
api.Authentication.Parameters = append(api.Authentication.Parameters, AuthenticationParams{
|
|
Name: "password_basic",
|
|
Value: "",
|
|
Example: "*****",
|
|
Description: "",
|
|
In: "",
|
|
Scheme: "",
|
|
Schema: SchemaDefinition{
|
|
Type: securitySchemes["jwt"].Value.Scheme,
|
|
},
|
|
})
|
|
|
|
extraParameters = append(extraParameters, WorkflowAppActionParameter{
|
|
Name: "username_basic",
|
|
Description: "The username to use",
|
|
Multiline: false,
|
|
Required: true,
|
|
Example: "The username to use",
|
|
Configuration: true,
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
extraParameters = append(extraParameters, WorkflowAppActionParameter{
|
|
Name: "password_basic",
|
|
Description: "The password to use",
|
|
Multiline: false,
|
|
Required: true,
|
|
Example: "***********",
|
|
Configuration: true,
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
} else if securitySchemes["BasicAuth"] != nil {
|
|
api.Authentication.Parameters = append(api.Authentication.Parameters, AuthenticationParams{
|
|
Name: "username_basic",
|
|
Value: "",
|
|
Example: "username",
|
|
Description: securitySchemes["BasicAuth"].Value.Description,
|
|
In: securitySchemes["BasicAuth"].Value.In,
|
|
Scheme: securitySchemes["BasicAuth"].Value.Scheme,
|
|
Schema: SchemaDefinition{
|
|
Type: securitySchemes["BasicAuth"].Value.Scheme,
|
|
},
|
|
})
|
|
|
|
api.Authentication.Parameters = append(api.Authentication.Parameters, AuthenticationParams{
|
|
Name: "password_basic",
|
|
Value: "",
|
|
Example: "*****",
|
|
Description: securitySchemes["BasicAuth"].Value.Description,
|
|
In: securitySchemes["BasicAuth"].Value.In,
|
|
Scheme: securitySchemes["BasicAuth"].Value.Scheme,
|
|
Schema: SchemaDefinition{
|
|
Type: securitySchemes["BasicAuth"].Value.Scheme,
|
|
},
|
|
})
|
|
|
|
extraParameters = append(extraParameters, WorkflowAppActionParameter{
|
|
Name: "username_basic",
|
|
Description: "The username to use",
|
|
Multiline: false,
|
|
Required: true,
|
|
Example: "The username to use",
|
|
Configuration: true,
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
extraParameters = append(extraParameters, WorkflowAppActionParameter{
|
|
Name: "password_basic",
|
|
Description: "The password to use",
|
|
Multiline: false,
|
|
Required: true,
|
|
Example: "***********",
|
|
Configuration: true,
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
for key, value := range securitySchemes {
|
|
if ArrayContains(reservedKeys, key) {
|
|
continue
|
|
}
|
|
|
|
//log.Printf("%s: %#v", key, value.Value)
|
|
exampleData := fmt.Sprintf("Extra auth field (%s)", value.Value.In)
|
|
api.Authentication.Parameters = append(api.Authentication.Parameters, AuthenticationParams{
|
|
Name: key,
|
|
Value: "",
|
|
Example: exampleData,
|
|
Description: exampleData,
|
|
In: value.Value.In,
|
|
Scheme: "",
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
|
|
extraParameters = append(extraParameters, WorkflowAppActionParameter{
|
|
Name: key,
|
|
Multiline: false,
|
|
Required: true,
|
|
Description: exampleData,
|
|
Example: exampleData,
|
|
Configuration: true,
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
}
|
|
|
|
// Adds a link parameter if it's not already defined
|
|
api.Authentication.Parameters = append(api.Authentication.Parameters, AuthenticationParams{
|
|
Name: "url",
|
|
Description: "The URL of the app",
|
|
Value: example,
|
|
Example: example,
|
|
Multiline: false,
|
|
Required: true,
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
|
|
extraParameters = append(extraParameters, WorkflowAppActionParameter{
|
|
Name: "url",
|
|
Description: "The URL of the API",
|
|
Value: example,
|
|
Example: example,
|
|
Multiline: false,
|
|
Required: true,
|
|
Configuration: true,
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
|
|
// This is the python code to be generated
|
|
// Could just as well be go at this point lol
|
|
pythonFunctions := []string{}
|
|
|
|
optionalParameters := []WorkflowAppActionParameter{}
|
|
headerParam := WorkflowAppActionParameter{
|
|
Name: "headers",
|
|
Description: "Add or edit headers",
|
|
Multiline: true,
|
|
Required: false,
|
|
Example: "Content-Type=application/json\nAccept=application/json\r\n",
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
}
|
|
|
|
optionalParameters = append(optionalParameters, headerParam)
|
|
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
|
Name: "queries",
|
|
Description: "Add or edit queries",
|
|
Multiline: true,
|
|
Required: false,
|
|
Example: "view=basic&redirect=test",
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
|
|
// Not validating by default, due to lots of people having issues with
|
|
// SSL things
|
|
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
|
Name: "ssl_verify",
|
|
Description: "Check if you want to verify request",
|
|
Multiline: false,
|
|
Required: false,
|
|
Example: "True",
|
|
Options: []string{
|
|
"False",
|
|
"True",
|
|
},
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
|
|
optionalParameters = append(optionalParameters, WorkflowAppActionParameter{
|
|
Name: "to_file",
|
|
Description: "Choose if we should write the result straight to a file or not",
|
|
Multiline: false,
|
|
Required: false,
|
|
Example: "False",
|
|
Options: []string{
|
|
"False",
|
|
"True",
|
|
},
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
})
|
|
|
|
// Fixing parameters with :
|
|
newExtraParams := []WorkflowAppActionParameter{}
|
|
newOptionalParams := []WorkflowAppActionParameter{}
|
|
for _, param := range extraParameters {
|
|
param.Name = FixParamname(param.Name)
|
|
newExtraParams = append(newExtraParams, param)
|
|
}
|
|
for _, param := range optionalParameters {
|
|
param.Name = FixParamname(param.Name)
|
|
newOptionalParams = append(newOptionalParams, param)
|
|
}
|
|
extraParameters = newExtraParams
|
|
optionalParameters = newOptionalParams
|
|
|
|
//Verified bool `json:"verified" yaml:"verified" required:false datastore:"verified"`
|
|
for actualPath, path := range swagger.Paths {
|
|
//actualPath = strings.Replace(actualPath, ".", "", -1)
|
|
actualPath = strings.Replace(actualPath, " ", "_", -1)
|
|
actualPath = strings.Replace(actualPath, "\\", "", -1)
|
|
if !api.Invalid && strings.HasPrefix(actualPath, "tmp") {
|
|
log.Printf("[WARNING] Set api %s to invalid because of path %s", swagger.Info.Title, actualPath)
|
|
api.Invalid = true
|
|
}
|
|
|
|
// FIXME: Handle everything behind questionmark (?) with dots as well.
|
|
// https://godoc.org/github.com/getkin/kin-openapi/openapi3#PathItem
|
|
if path.Get != nil {
|
|
action, curCode := HandleGet(swagger, api, extraParameters, path, actualPath, optionalParameters)
|
|
api.Actions = append(api.Actions, action)
|
|
pythonFunctions = append(pythonFunctions, curCode)
|
|
}
|
|
if path.Connect != nil {
|
|
action, curCode := HandleConnect(swagger, api, extraParameters, path, actualPath, optionalParameters)
|
|
api.Actions = append(api.Actions, action)
|
|
pythonFunctions = append(pythonFunctions, curCode)
|
|
}
|
|
if path.Head != nil {
|
|
action, curCode := HandleHead(swagger, api, extraParameters, path, actualPath, optionalParameters)
|
|
api.Actions = append(api.Actions, action)
|
|
pythonFunctions = append(pythonFunctions, curCode)
|
|
}
|
|
if path.Delete != nil {
|
|
action, curCode := HandleDelete(swagger, api, extraParameters, path, actualPath, optionalParameters)
|
|
api.Actions = append(api.Actions, action)
|
|
pythonFunctions = append(pythonFunctions, curCode)
|
|
}
|
|
if path.Post != nil {
|
|
action, curCode := HandlePost(swagger, api, extraParameters, path, actualPath, optionalParameters)
|
|
api.Actions = append(api.Actions, action)
|
|
pythonFunctions = append(pythonFunctions, curCode)
|
|
}
|
|
if path.Patch != nil {
|
|
action, curCode := HandlePatch(swagger, api, extraParameters, path, actualPath, optionalParameters)
|
|
api.Actions = append(api.Actions, action)
|
|
pythonFunctions = append(pythonFunctions, curCode)
|
|
}
|
|
if path.Put != nil {
|
|
action, curCode := HandlePut(swagger, api, extraParameters, path, actualPath, optionalParameters)
|
|
api.Actions = append(api.Actions, action)
|
|
pythonFunctions = append(pythonFunctions, curCode)
|
|
}
|
|
|
|
// Has to be here because its used differently above.
|
|
// FIXING this is done during export instead?
|
|
//log.Printf("OLDPATH: %s", actualPath)
|
|
//if strings.Contains(actualPath, "?") {
|
|
// actualPath = strings.Split(actualPath, "?")[0]
|
|
//}
|
|
|
|
//log.Printf("NEWPATH: %s", actualPath)
|
|
//newPaths[actualPath] = path
|
|
}
|
|
|
|
action, curCode := AddCustomAction(swagger, api)
|
|
api.Actions = append(api.Actions, action)
|
|
pythonFunctions = append(pythonFunctions, curCode)
|
|
|
|
return swagger, api, pythonFunctions, nil
|
|
}
|
|
|
|
// FIXME - have this give a real version?
|
|
func VerifyApi(api WorkflowApp) WorkflowApp {
|
|
if api.AppVersion == "" {
|
|
api.AppVersion = "1.0.0"
|
|
}
|
|
|
|
return api
|
|
}
|
|
|
|
func GetBasePython() string {
|
|
baseString := `import requests
|
|
import json
|
|
import urllib3
|
|
|
|
from shuffle_sdk import AppBase
|
|
|
|
class %s(AppBase):
|
|
"""
|
|
Autogenerated class by Shuffler
|
|
"""
|
|
|
|
__version__ = "%s"
|
|
app_name = "%s"
|
|
|
|
def __init__(self, redis, logger, console_logger=None):
|
|
self.verify = False
|
|
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
super().__init__(redis, logger, console_logger)
|
|
|
|
%s
|
|
|
|
if __name__ == "__main__":
|
|
%s.run()
|
|
`
|
|
|
|
// From old when we actually used asyncio (:
|
|
//#asyncio.run(%s.run(), debug=True)
|
|
return baseString
|
|
|
|
}
|
|
|
|
func DumpPythonGCP(ctx context.Context, client *storage.Client, basePath, name, version string, pythonFunctions []string, bucketName string) (string, error) {
|
|
parsedCode := fmt.Sprintf(GetBasePython(), name, version, name, strings.Join(pythonFunctions, "\n"), name)
|
|
|
|
// Create bucket handle
|
|
bucket := client.Bucket(bucketName)
|
|
obj := bucket.Object(fmt.Sprintf("%s/src/app.py", basePath))
|
|
w := obj.NewWriter(ctx)
|
|
if _, err := fmt.Fprintln(w, parsedCode); err != nil {
|
|
return "", err
|
|
}
|
|
// Close, just like writing a file.
|
|
if err := w.Close(); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return parsedCode, nil
|
|
}
|
|
|
|
func DumpPython(basePath, name, version string, pythonFunctions []string) (string, error) {
|
|
//log.Printf("%#v", api)
|
|
//log.Printf(strings.Join(pythonFunctions, "\n"))
|
|
|
|
parsedCode := fmt.Sprintf(GetBasePython(), name, version, name, strings.Join(pythonFunctions, "\n"), name)
|
|
|
|
err := ioutil.WriteFile(fmt.Sprintf("%s/src/app.py", basePath), []byte(parsedCode), os.ModePerm)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
//fmt.Println(parsedCode)
|
|
//log.Println(string(data))
|
|
return parsedCode, nil
|
|
}
|
|
|
|
func DumpApiGCP(ctx context.Context, client *storage.Client, swagger *openapi3.Swagger, basePath string, api WorkflowApp, bucketName string) error {
|
|
//log.Printf("%#v", api)
|
|
data, err := yaml.Marshal(api)
|
|
if err != nil {
|
|
log.Printf("Error with yaml marshal: %s", err)
|
|
return err
|
|
}
|
|
|
|
// Create bucket handle
|
|
bucket := client.Bucket(bucketName)
|
|
obj := bucket.Object(fmt.Sprintf("%s/app.yaml", basePath))
|
|
w := obj.NewWriter(ctx)
|
|
if _, err := fmt.Fprintln(w, string(data)); err != nil {
|
|
return err
|
|
}
|
|
// Close, just like writing a file.
|
|
if err := w.Close(); err != nil {
|
|
return err
|
|
}
|
|
|
|
openapidata, err := yaml.Marshal(swagger)
|
|
if err != nil {
|
|
log.Printf("Error with yaml marshal: %s", err)
|
|
return err
|
|
}
|
|
obj = bucket.Object(fmt.Sprintf("%s/openapi.yaml", basePath))
|
|
//log.Println(string(openapidata))
|
|
w = obj.NewWriter(ctx)
|
|
if _, err := fmt.Fprintln(w, string(openapidata)); err != nil {
|
|
return err
|
|
}
|
|
// Close, just like writing a file.
|
|
if err := w.Close(); err != nil {
|
|
return err
|
|
}
|
|
|
|
//log.Println(string(data))
|
|
return nil
|
|
}
|
|
|
|
func DumpApi(basePath string, api WorkflowApp) error {
|
|
//log.Printf("%#v", api)
|
|
data, err := yaml.Marshal(api)
|
|
if err != nil {
|
|
log.Printf("Error with yaml marshal: %s", err)
|
|
return err
|
|
}
|
|
|
|
err = ioutil.WriteFile(fmt.Sprintf("%s/api.yaml", basePath), []byte(data), os.ModePerm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
//log.Println(string(data))
|
|
return nil
|
|
}
|
|
|
|
func GetRunnerOnprem(classname string) string {
|
|
return fmt.Sprintf(`
|
|
# Run the actual thing after we've checked params
|
|
def run(request):
|
|
print("Started execution!")
|
|
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:
|
|
%s.run(action=action)
|
|
return f'Attempting to execute function {action["name"]} in app {action["app_name"]}'
|
|
else:
|
|
return f'Invalid action'
|
|
|
|
`, classname)
|
|
}
|
|
|
|
func GetRunnerGCP(classname string) string {
|
|
return fmt.Sprintf(`
|
|
# Run the actual thing after we've checked params
|
|
def run(request):
|
|
try:
|
|
action = request.get_json(force=True)
|
|
except:
|
|
return f'Error parsing JSON'
|
|
|
|
if action == None:
|
|
return f'No JSON detected'
|
|
|
|
#authorization_key = action.get("authorization")
|
|
#current_execution_id = action.get("execution_id")
|
|
|
|
if action and "name" in action and "app_name" in action:
|
|
%s.run(action=action)
|
|
return f'Attempting to execute function {action["name"]} in app {action["app_name"]}'
|
|
|
|
return f'Action ran!'
|
|
|
|
`, classname)
|
|
}
|
|
|
|
func DeployAppToDatastore(ctx context.Context, workflowapp WorkflowApp) error {
|
|
err := SetWorkflowAppDatastore(ctx, workflowapp, workflowapp.ID)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed setting workflowapp: %s", err)
|
|
return err
|
|
} else {
|
|
log.Printf("[INFO] Added %s:%s to the database", workflowapp.Name, workflowapp.AppVersion)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func FixParamname(paramname string) string {
|
|
paramname = strings.Replace(paramname, ".", "", -1)
|
|
paramname = strings.Replace(paramname, ":", "", -1)
|
|
paramname = strings.Replace(paramname, ",", "", -1)
|
|
paramname = strings.Replace(paramname, ".", "", -1)
|
|
paramname = strings.Replace(paramname, "&", "", -1)
|
|
paramname = strings.Replace(paramname, "/", "", -1)
|
|
paramname = strings.Replace(paramname, "\\", "", -1)
|
|
|
|
paramname = strings.Replace(paramname, "!", "", -1)
|
|
paramname = strings.Replace(paramname, "?", "", -1)
|
|
paramname = strings.Replace(paramname, "@", "", -1)
|
|
paramname = strings.Replace(paramname, "#", "", -1)
|
|
paramname = strings.Replace(paramname, "$", "", -1)
|
|
paramname = strings.Replace(paramname, "&", "", -1)
|
|
paramname = strings.Replace(paramname, "*", "", -1)
|
|
paramname = strings.Replace(paramname, "(", "", -1)
|
|
paramname = strings.Replace(paramname, ")", "", -1)
|
|
paramname = strings.Replace(paramname, "[", "", -1)
|
|
paramname = strings.Replace(paramname, "]", "", -1)
|
|
paramname = strings.Replace(paramname, "{", "", -1)
|
|
paramname = strings.Replace(paramname, "}", "", -1)
|
|
paramname = strings.Replace(paramname, `"`, "", -1)
|
|
paramname = strings.Replace(paramname, `'`, "", -1)
|
|
paramname = strings.Replace(paramname, `|`, "", -1)
|
|
paramname = strings.Replace(paramname, `~`, "", -1)
|
|
|
|
paramname = strings.Replace(paramname, " ", "_", -1)
|
|
paramname = strings.Replace(paramname, "-", "_", -1)
|
|
|
|
return paramname
|
|
}
|
|
|
|
// FIXME:
|
|
// https://docs.python.org/3.2/reference/lexical_analysis.html#identifiers
|
|
// This is used to build the python functions.
|
|
func FixFunctionName(functionName, actualPath string, lowercase bool) string {
|
|
if len(functionName) == 0 {
|
|
functionName = actualPath
|
|
}
|
|
|
|
validCharacters := []rune("_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
|
newname := ""
|
|
for _, char := range functionName {
|
|
if string(char) == " " {
|
|
newname += "_"
|
|
continue
|
|
}
|
|
|
|
for _, rune := range validCharacters {
|
|
if char == rune {
|
|
newname += string(char)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
functionName = newname
|
|
if lowercase == true {
|
|
functionName = strings.ToLower(functionName)
|
|
}
|
|
|
|
return functionName
|
|
}
|
|
|
|
// Returns a valid param name
|
|
func ValidateParameterName(name string) string {
|
|
invalid := []string{"False",
|
|
"await",
|
|
"else",
|
|
"import",
|
|
"pass",
|
|
"None",
|
|
"break",
|
|
"except",
|
|
"in",
|
|
"raise",
|
|
"True",
|
|
"class",
|
|
"finally",
|
|
"is",
|
|
"return",
|
|
"and",
|
|
"continue",
|
|
"for",
|
|
"lambda",
|
|
"try",
|
|
"as",
|
|
"def",
|
|
"from",
|
|
"nonlocal",
|
|
"while",
|
|
"assert",
|
|
"del",
|
|
"global",
|
|
"not",
|
|
"with",
|
|
"async",
|
|
"elif",
|
|
"if",
|
|
"or",
|
|
"yield",
|
|
}
|
|
|
|
newname := name
|
|
for _, item := range invalid {
|
|
if item == name {
|
|
//log.Printf("%s is NOT a valid parameter name!", item)
|
|
newname = fmt.Sprintf("%s_shuffle", item)
|
|
break
|
|
}
|
|
}
|
|
|
|
newname = strings.Replace(newname, " ", "_", -1)
|
|
newname = strings.Replace(newname, ",", "_", -1)
|
|
newname = strings.Replace(newname, ".", "_", -1)
|
|
newname = strings.Replace(newname, "|", "_", -1)
|
|
newname = strings.Replace(newname, "-", "_", -1)
|
|
|
|
return newname
|
|
}
|
|
|
|
func HandleConnect(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string, optionalParameters []WorkflowAppActionParameter) (WorkflowAppAction, string) {
|
|
// What to do with this, hmm
|
|
functionName := FixFunctionName(path.Connect.Summary, actualPath, true)
|
|
//func FixParamname(paramname string) string {
|
|
|
|
baseUrl := fmt.Sprintf("%s%s", api.Link, actualPath)
|
|
|
|
if strings.Contains(baseUrl, "_shuffle_replace_") {
|
|
//log.Printf("[DEBUG] : %s", baseUrl)
|
|
m := regexp.MustCompile(`_shuffle_replace_\d+`)
|
|
baseUrl = m.ReplaceAllString(baseUrl, "")
|
|
}
|
|
|
|
newDesc := fmt.Sprintf("%s\n\n%s", path.Connect.Description, baseUrl)
|
|
action := WorkflowAppAction{
|
|
Description: newDesc,
|
|
Name: fmt.Sprintf("%s %s", "Connect", path.Connect.Summary),
|
|
Label: fmt.Sprintf("%s", path.Connect.Summary),
|
|
NodeType: "action",
|
|
Environment: api.Environment,
|
|
Parameters: extraParameters,
|
|
}
|
|
|
|
if val, ok := path.Connect.ExtensionProps.Extensions["x-label"]; ok {
|
|
labels := []string{}
|
|
j, err := json.Marshal(&val)
|
|
if err == nil {
|
|
err = json.Unmarshal(j, &labels)
|
|
if err == nil {
|
|
action.CategoryLabel = labels
|
|
} else {
|
|
log.Printf("[ERROR] Could not unmarshal x-label array: %s", err)
|
|
}
|
|
} else {
|
|
log.Printf("[ERROR] Could not marshal x-label: %s", err)
|
|
}
|
|
}
|
|
|
|
action.Returns.Schema.Type = "string"
|
|
handleFile := false
|
|
|
|
//log.Println(path.Parameters)
|
|
|
|
// Parameters: []WorkflowAppActionParameter{},
|
|
//firstQuery := true
|
|
optionalQueries := []string{}
|
|
parameters := []string{}
|
|
|
|
headersFound := []string{}
|
|
if len(path.Connect.Parameters) > 0 {
|
|
for counter, param := range path.Connect.Parameters {
|
|
if param.Value.Schema == nil {
|
|
continue
|
|
} else if param.Value.In == "header" {
|
|
headersFound = append(headersFound, fmt.Sprintf("%s=%s", param.Value.Name, param.Value.Example))
|
|
continue
|
|
}
|
|
|
|
parsedName := param.Value.Name
|
|
parsedName = strings.Replace(parsedName, " ", "_", -1)
|
|
parsedName = strings.Replace(parsedName, ",", "_", -1)
|
|
parsedName = strings.Replace(parsedName, ".", "_", -1)
|
|
parsedName = strings.Replace(parsedName, "|", "_", -1)
|
|
parsedName = strings.Replace(parsedName, "-", "_", -1)
|
|
parsedName = ValidateParameterName(parsedName)
|
|
param.Value.Name = parsedName
|
|
path.Connect.Parameters[counter].Value.Name = parsedName
|
|
|
|
// Force it as a string to avoid nil-pointer
|
|
if param.Value.Schema.Value == nil {
|
|
param.Value.Schema.Value = &openapi3.Schema{
|
|
Type: "string",
|
|
}
|
|
}
|
|
|
|
curParam := WorkflowAppActionParameter{
|
|
Name: parsedName,
|
|
Description: param.Value.Description,
|
|
Multiline: false,
|
|
Required: param.Value.Required,
|
|
Schema: SchemaDefinition{
|
|
Type: param.Value.Schema.Value.Type,
|
|
},
|
|
}
|
|
|
|
if param.Value.Example != nil {
|
|
if exampleVal, ok := param.Value.Example.(string); !ok {
|
|
curParam.Example = fmt.Sprintf("%v", param.Value.Example)
|
|
} else {
|
|
curParam.Example = exampleVal
|
|
}
|
|
|
|
if param.Value.Name == "body" {
|
|
if exampleVal, ok := param.Value.Example.(string); !ok {
|
|
|
|
curParam.Value = fmt.Sprintf("%v", param.Value.Example)
|
|
} else {
|
|
curParam.Value = exampleVal
|
|
}
|
|
}
|
|
}
|
|
|
|
if val, ok := param.Value.ExtensionProps.Extensions["multiline"]; ok {
|
|
j, err := json.Marshal(&val)
|
|
if err == nil {
|
|
b, err := strconv.ParseBool(string(j))
|
|
if err == nil {
|
|
curParam.Multiline = b
|
|
}
|
|
}
|
|
}
|
|
|
|
if param.Value.Required {
|
|
action.Parameters = append(action.Parameters, curParam)
|
|
} else {
|
|
optionalParameters = append(optionalParameters, curParam)
|
|
}
|
|
|
|
if param.Value.In == "path" {
|
|
parameters = append(parameters, curParam.Name)
|
|
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
|
|
} else if param.Value.In == "query" {
|
|
//log.Printf("QUERY!: %s", param.Value.Name)
|
|
if !param.Value.Required {
|
|
optionalQueries = append(optionalQueries, param.Value.Name)
|
|
continue
|
|
}
|
|
|
|
parameters = append(parameters, param.Value.Name)
|
|
|
|
if strings.Contains(baseUrl, fmt.Sprintf("%s={%s}", param.Value.Name, param.Value.Name)) {
|
|
continue
|
|
}
|
|
|
|
if strings.Contains(baseUrl, fmt.Sprintf("{%s}", param.Value.Name)) {
|
|
continue
|
|
}
|
|
|
|
//if firstQuery && !strings.Contains(baseUrl, "?") {
|
|
// baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name)
|
|
//} else {
|
|
// baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name)
|
|
//}
|
|
//firstQuery = false
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if len(headersFound) > 0 {
|
|
setIndex := -1
|
|
for paramIndex, param := range optionalParameters {
|
|
if param.Name == "headers" {
|
|
setIndex = paramIndex
|
|
break
|
|
}
|
|
}
|
|
|
|
if setIndex >= 0 {
|
|
for _, header := range headersFound {
|
|
if !strings.Contains(header, "=") {
|
|
continue
|
|
}
|
|
|
|
headerKey := strings.Split(header, "=")[0]
|
|
if strings.Contains(optionalParameters[setIndex].Value, headerKey) {
|
|
continue
|
|
}
|
|
|
|
optionalParameters[setIndex].Value = fmt.Sprintf("%s%s\n", optionalParameters[setIndex].Value, header)
|
|
}
|
|
|
|
//log.Printf("What: %#v", optionalParameters[setIndex].Value[len(optionalParameters[setIndex].Value)-1])
|
|
//log.Printf("HI: %s",
|
|
//optionalParameters[setIndex].Value[len(optionalParameters[setIndex].Value)-2])
|
|
// Removing newlines at the end
|
|
if len(optionalParameters[setIndex].Value) > 0 && optionalParameters[setIndex].Value[len(optionalParameters[setIndex].Value)-1] == 0xa {
|
|
optionalParameters[setIndex].Value = optionalParameters[setIndex].Value[0 : len(optionalParameters[setIndex].Value)-1]
|
|
}
|
|
|
|
log.Printf("%#v", optionalParameters[setIndex].Value)
|
|
}
|
|
}
|
|
|
|
// Must be here 'cus they should be last
|
|
headerKey := `headers=""`
|
|
if !ArrayContains(parameters, headerKey) {
|
|
parameters = append(parameters, headerKey)
|
|
}
|
|
|
|
queryKey := `queries=""`
|
|
if !ArrayContains(parameters, queryKey) {
|
|
parameters = append(parameters, queryKey)
|
|
}
|
|
|
|
// ensuring that they end up last in the specification
|
|
// (order is ish important for optional params) - they need to be last.
|
|
for _, optionalParam := range optionalParameters {
|
|
optionalParam.Name = strings.ToLower(optionalParam.Name)
|
|
action.Parameters = append(action.Parameters, optionalParam)
|
|
}
|
|
|
|
functionname, curCode := MakePythoncode(swagger, functionName, baseUrl, "connect", parameters, optionalQueries, headersFound, "", api, handleFile)
|
|
|
|
if len(functionname) > 0 {
|
|
action.Name = functionname
|
|
}
|
|
|
|
return action, curCode
|
|
}
|
|
|
|
func HandleGet(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string, optionalParameters []WorkflowAppActionParameter) (WorkflowAppAction, string) {
|
|
// What to do with this, hmm
|
|
functionName := FixFunctionName(path.Get.Summary, actualPath, true)
|
|
|
|
baseUrl := fmt.Sprintf("%s%s", api.Link, actualPath)
|
|
|
|
if strings.Contains(baseUrl, "_shuffle_replace_") {
|
|
//log.Printf("[DEBUG] : %s", baseUrl)
|
|
m := regexp.MustCompile(`_shuffle_replace_\d+`)
|
|
baseUrl = m.ReplaceAllString(baseUrl, "")
|
|
}
|
|
|
|
newDesc := fmt.Sprintf("%s\n\n%s", path.Get.Description, baseUrl)
|
|
action := WorkflowAppAction{
|
|
Description: newDesc,
|
|
Name: fmt.Sprintf("%s %s", "Get", path.Get.Summary),
|
|
Label: fmt.Sprintf("%s", path.Get.Summary),
|
|
NodeType: "action",
|
|
Environment: api.Environment,
|
|
Parameters: extraParameters,
|
|
}
|
|
|
|
if val, ok := path.Get.ExtensionProps.Extensions["x-label"]; ok {
|
|
labels := []string{}
|
|
j, err := json.Marshal(&val)
|
|
if err == nil {
|
|
err = json.Unmarshal(j, &labels)
|
|
if err == nil {
|
|
action.CategoryLabel = labels
|
|
} else {
|
|
log.Printf("[ERROR] Could not unmarshal x-label array: %s", err)
|
|
}
|
|
} else {
|
|
log.Printf("[ERROR] Could not marshal x-label: %s", err)
|
|
}
|
|
}
|
|
|
|
action.Returns.Schema.Type = "string"
|
|
|
|
// Check if it should return as file (binary)
|
|
// FIXME: Don't JUST specif text/plain to allow this.
|
|
handleFile := false
|
|
if strings.Contains(path.Get.Summary, "Download") {
|
|
if defaultInfo, ok := path.Get.Responses["default"]; ok {
|
|
|
|
if content, ok := defaultInfo.Value.Content["text/plain"]; ok {
|
|
if content.Schema.Value.Type == "string" && content.Schema.Value.Format == "binary" {
|
|
handleFile = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parameters: []WorkflowAppActionParameter{},
|
|
//firstQuery := true
|
|
optionalQueries := []string{}
|
|
|
|
// FIXME - remove this when authentication is properly introduced
|
|
parameters := []string{}
|
|
headersFound := []string{}
|
|
if len(path.Get.Parameters) > 0 {
|
|
for counter, param := range path.Get.Parameters {
|
|
if param.Value == nil || param.Value.Schema == nil {
|
|
continue
|
|
} else if param.Value.In == "header" {
|
|
headersFound = append(headersFound, fmt.Sprintf("%s=%s", param.Value.Name, param.Value.Example))
|
|
continue
|
|
}
|
|
|
|
parsedName := param.Value.Name
|
|
parsedName = strings.Replace(parsedName, " ", "_", -1)
|
|
parsedName = strings.Replace(parsedName, ",", "_", -1)
|
|
parsedName = strings.Replace(parsedName, ".", "_", -1)
|
|
parsedName = strings.Replace(parsedName, "|", "_", -1)
|
|
parsedName = ValidateParameterName(parsedName)
|
|
param.Value.Name = parsedName
|
|
path.Get.Parameters[counter].Value.Name = parsedName
|
|
|
|
// Force it as a string to avoid nil-pointer
|
|
if param.Value.Schema.Value == nil {
|
|
param.Value.Schema.Value = &openapi3.Schema{
|
|
Type: "string",
|
|
}
|
|
}
|
|
|
|
curParam := WorkflowAppActionParameter{
|
|
Name: parsedName,
|
|
Description: param.Value.Description,
|
|
Multiline: false,
|
|
Required: param.Value.Required,
|
|
Schema: SchemaDefinition{
|
|
Type: param.Value.Schema.Value.Type,
|
|
},
|
|
}
|
|
|
|
if param.Value.Example != nil {
|
|
if exampleVal, ok := param.Value.Example.(string); !ok {
|
|
curParam.Example = fmt.Sprintf("%v", param.Value.Example)
|
|
} else {
|
|
curParam.Example = exampleVal
|
|
}
|
|
|
|
if param.Value.Name == "body" {
|
|
if exampleVal, ok := param.Value.Example.(string); !ok {
|
|
|
|
curParam.Value = fmt.Sprintf("%v", param.Value.Example)
|
|
} else {
|
|
curParam.Value = exampleVal
|
|
}
|
|
}
|
|
}
|
|
|
|
if val, ok := param.Value.ExtensionProps.Extensions["multiline"]; ok {
|
|
j, err := json.Marshal(&val)
|
|
if err == nil {
|
|
b, err := strconv.ParseBool(string(j))
|
|
if err == nil {
|
|
curParam.Multiline = b
|
|
}
|
|
}
|
|
}
|
|
|
|
if param.Value.Required {
|
|
action.Parameters = append(action.Parameters, curParam)
|
|
} else {
|
|
optionalParameters = append(optionalParameters, curParam)
|
|
}
|
|
|
|
if param.Value.In == "path" {
|
|
parameters = append(parameters, curParam.Name)
|
|
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
|
|
} else if param.Value.In == "query" {
|
|
//log.Printf("QUERY!: %s", param.Value.Name)
|
|
if !param.Value.Required {
|
|
optionalQueries = append(optionalQueries, param.Value.Name)
|
|
continue
|
|
}
|
|
|
|
parameters = append(parameters, param.Value.Name)
|
|
|
|
// Skipping simial
|
|
if strings.Contains(baseUrl, fmt.Sprintf("%s={%s}", param.Value.Name, param.Value.Name)) {
|
|
continue
|
|
}
|
|
|
|
if strings.Contains(baseUrl, fmt.Sprintf("{%s}", param.Value.Name)) {
|
|
continue
|
|
}
|
|
|
|
//if firstQuery && !strings.Contains(baseUrl, "?") {
|
|
// baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name)
|
|
//} else {
|
|
// baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name)
|
|
//}
|
|
//firstQuery = false
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if len(headersFound) > 0 {
|
|
setIndex := -1
|
|
for paramIndex, param := range optionalParameters {
|
|
if param.Name == "headers" {
|
|
setIndex = paramIndex
|
|
break
|
|
}
|
|
}
|
|
|
|
if setIndex >= 0 {
|
|
for _, header := range headersFound {
|
|
if !strings.Contains(header, "=") {
|
|
continue
|
|
}
|
|
|
|
headerKey := strings.Split(header, "=")[0]
|
|
if strings.Contains(optionalParameters[setIndex].Value, headerKey) {
|
|
continue
|
|
}
|
|
|
|
optionalParameters[setIndex].Value = fmt.Sprintf("%s%s\n", optionalParameters[setIndex].Value, header)
|
|
}
|
|
|
|
if len(optionalParameters[setIndex].Value) > 0 && optionalParameters[setIndex].Value[len(optionalParameters[setIndex].Value)-1] == 0xa {
|
|
optionalParameters[setIndex].Value = optionalParameters[setIndex].Value[0 : len(optionalParameters[setIndex].Value)-1]
|
|
|
|
//optionalParameters[setIndex].Example = optionalParameters[setIndex].Example[0 : len(optionalParameters[setIndex].Example)-1]
|
|
}
|
|
|
|
//log.Printf("%#v", optionalParameters[setIndex].Value)
|
|
}
|
|
} else {
|
|
//log.Printf("No headers found for %s", functionName)
|
|
}
|
|
|
|
// Must be here 'cus they should be last
|
|
headerKey := `headers=""`
|
|
if !ArrayContains(parameters, headerKey) {
|
|
parameters = append(parameters, headerKey)
|
|
}
|
|
|
|
queryKey := `queries=""`
|
|
if !ArrayContains(parameters, queryKey) {
|
|
parameters = append(parameters, queryKey)
|
|
}
|
|
|
|
// ensuring that they end up last in the specification
|
|
// (order is ish important for optional params) - they need to be last.
|
|
for _, optionalParam := range optionalParameters {
|
|
optionalParam.Name = strings.ToLower(optionalParam.Name)
|
|
action.Parameters = append(action.Parameters, optionalParam)
|
|
}
|
|
|
|
functionname, curCode := MakePythoncode(swagger, functionName, baseUrl, "get", parameters, optionalQueries, headersFound, "", api, handleFile)
|
|
|
|
if len(functionname) > 0 {
|
|
action.Name = functionname
|
|
}
|
|
|
|
return action, curCode
|
|
}
|
|
|
|
func HandleHead(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string, optionalParameters []WorkflowAppActionParameter) (WorkflowAppAction, string) {
|
|
// What to do with this, hmm
|
|
functionName := FixFunctionName(path.Head.Summary, actualPath, true)
|
|
|
|
baseUrl := fmt.Sprintf("%s%s", api.Link, actualPath)
|
|
|
|
if strings.Contains(baseUrl, "_shuffle_replace_") {
|
|
//log.Printf("[DEBUG] : %s", baseUrl)
|
|
m := regexp.MustCompile(`_shuffle_replace_\d+`)
|
|
baseUrl = m.ReplaceAllString(baseUrl, "")
|
|
}
|
|
|
|
newDesc := fmt.Sprintf("%s\n\n%s", path.Head.Description, baseUrl)
|
|
action := WorkflowAppAction{
|
|
Description: newDesc,
|
|
Name: fmt.Sprintf("%s %s", "Head", path.Head.Summary),
|
|
Label: fmt.Sprintf("%s", path.Head.Summary),
|
|
NodeType: "action",
|
|
Environment: api.Environment,
|
|
Parameters: extraParameters,
|
|
}
|
|
|
|
if val, ok := path.Head.ExtensionProps.Extensions["x-label"]; ok {
|
|
labels := []string{}
|
|
j, err := json.Marshal(&val)
|
|
if err == nil {
|
|
err = json.Unmarshal(j, &labels)
|
|
if err == nil {
|
|
action.CategoryLabel = labels
|
|
} else {
|
|
log.Printf("[ERROR] Could not unmarshal x-label array: %s", err)
|
|
}
|
|
} else {
|
|
log.Printf("[ERROR] Could not marshal x-label: %s", err)
|
|
}
|
|
}
|
|
|
|
action.Returns.Schema.Type = "string"
|
|
handleFile := false
|
|
|
|
//log.Println(path.Parameters)
|
|
|
|
// Parameters: []WorkflowAppActionParameter{},
|
|
//firstQuery := true
|
|
optionalQueries := []string{}
|
|
parameters := []string{}
|
|
headersFound := []string{}
|
|
if len(path.Head.Parameters) > 0 {
|
|
for counter, param := range path.Head.Parameters {
|
|
if param.Value.Schema == nil {
|
|
continue
|
|
} else if param.Value.In == "header" {
|
|
headersFound = append(headersFound, fmt.Sprintf("%s=%s", param.Value.Name, param.Value.Example))
|
|
continue
|
|
}
|
|
|
|
parsedName := param.Value.Name
|
|
parsedName = strings.Replace(parsedName, " ", "_", -1)
|
|
parsedName = strings.Replace(parsedName, ",", "_", -1)
|
|
parsedName = strings.Replace(parsedName, ".", "_", -1)
|
|
parsedName = strings.Replace(parsedName, "|", "_", -1)
|
|
parsedName = ValidateParameterName(parsedName)
|
|
param.Value.Name = parsedName
|
|
path.Head.Parameters[counter].Value.Name = parsedName
|
|
|
|
// Force it as a string to avoid nil-pointer
|
|
if param.Value.Schema.Value == nil {
|
|
param.Value.Schema.Value = &openapi3.Schema{
|
|
Type: "string",
|
|
}
|
|
}
|
|
|
|
curParam := WorkflowAppActionParameter{
|
|
Name: parsedName,
|
|
Description: param.Value.Description,
|
|
Multiline: false,
|
|
Required: param.Value.Required,
|
|
Schema: SchemaDefinition{
|
|
Type: param.Value.Schema.Value.Type,
|
|
},
|
|
}
|
|
|
|
if param.Value.Example != nil {
|
|
if exampleVal, ok := param.Value.Example.(string); !ok {
|
|
curParam.Example = fmt.Sprintf("%v", param.Value.Example)
|
|
} else {
|
|
curParam.Example = exampleVal
|
|
}
|
|
|
|
if param.Value.Name == "body" {
|
|
if exampleVal, ok := param.Value.Example.(string); !ok {
|
|
|
|
curParam.Value = fmt.Sprintf("%v", param.Value.Example)
|
|
} else {
|
|
curParam.Value = exampleVal
|
|
}
|
|
}
|
|
}
|
|
|
|
if val, ok := param.Value.ExtensionProps.Extensions["multiline"]; ok {
|
|
j, err := json.Marshal(&val)
|
|
if err == nil {
|
|
b, err := strconv.ParseBool(string(j))
|
|
if err == nil {
|
|
curParam.Multiline = b
|
|
}
|
|
}
|
|
}
|
|
|
|
if param.Value.Required {
|
|
action.Parameters = append(action.Parameters, curParam)
|
|
} else {
|
|
optionalParameters = append(optionalParameters, curParam)
|
|
}
|
|
|
|
if param.Value.In == "path" {
|
|
parameters = append(parameters, curParam.Name)
|
|
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
|
|
} else if param.Value.In == "query" {
|
|
//log.Printf("QUERY!: %s", param.Value.Name)
|
|
if !param.Value.Required {
|
|
optionalQueries = append(optionalQueries, param.Value.Name)
|
|
continue
|
|
}
|
|
|
|
parameters = append(parameters, param.Value.Name)
|
|
|
|
if strings.Contains(baseUrl, fmt.Sprintf("%s={%s}", param.Value.Name, param.Value.Name)) {
|
|
continue
|
|
}
|
|
|
|
if strings.Contains(baseUrl, fmt.Sprintf("{%s}", param.Value.Name)) {
|
|
continue
|
|
}
|
|
|
|
//if firstQuery && !strings.Contains(baseUrl, "?") {
|
|
// baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name)
|
|
//} else {
|
|
// baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name)
|
|
//}
|
|
//firstQuery = false
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(headersFound) > 0 {
|
|
setIndex := -1
|
|
for paramIndex, param := range optionalParameters {
|
|
if param.Name == "headers" {
|
|
setIndex = paramIndex
|
|
break
|
|
}
|
|
}
|
|
|
|
if setIndex >= 0 {
|
|
for _, header := range headersFound {
|
|
if !strings.Contains(header, "=") {
|
|
continue
|
|
}
|
|
|
|
headerKey := strings.Split(header, "=")[0]
|
|
if strings.Contains(optionalParameters[setIndex].Value, headerKey) {
|
|
continue
|
|
}
|
|
|
|
optionalParameters[setIndex].Value = fmt.Sprintf("%s%s\n", optionalParameters[setIndex].Value, header)
|
|
}
|
|
|
|
//log.Printf("What: %#v", optionalParameters[setIndex].Value[len(optionalParameters[setIndex].Value)-1])
|
|
//log.Printf("HI: %s",
|
|
//optionalParameters[setIndex].Value[len(optionalParameters[setIndex].Value)-2])
|
|
// Removing newlines at the end
|
|
if len(optionalParameters[setIndex].Value) > 0 && optionalParameters[setIndex].Value[len(optionalParameters[setIndex].Value)-1] == 0xa {
|
|
optionalParameters[setIndex].Value = optionalParameters[setIndex].Value[0 : len(optionalParameters[setIndex].Value)-1]
|
|
}
|
|
|
|
//log.Printf("%#v", optionalParameters[setIndex].Value)
|
|
}
|
|
}
|
|
|
|
// Must be here 'cus they should be last
|
|
headerKey := `headers=""`
|
|
if !ArrayContains(parameters, headerKey) {
|
|
parameters = append(parameters, headerKey)
|
|
}
|
|
|
|
queryKey := `queries=""`
|
|
if !ArrayContains(parameters, queryKey) {
|
|
parameters = append(parameters, queryKey)
|
|
}
|
|
|
|
// ensuring that they end up last in the specification
|
|
// (order is ish important for optional params) - they need to be last.
|
|
for _, optionalParam := range optionalParameters {
|
|
optionalParam.Name = strings.ToLower(optionalParam.Name)
|
|
action.Parameters = append(action.Parameters, optionalParam)
|
|
}
|
|
|
|
functionname, curCode := MakePythoncode(swagger, functionName, baseUrl, "head", parameters, optionalQueries, headersFound, "", api, handleFile)
|
|
|
|
if len(functionname) > 0 {
|
|
action.Name = functionname
|
|
}
|
|
|
|
return action, curCode
|
|
}
|
|
|
|
func HandleDelete(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string, optionalParameters []WorkflowAppActionParameter) (WorkflowAppAction, string) {
|
|
// What to do with this, hmm
|
|
functionName := FixFunctionName(path.Delete.Summary, actualPath, true)
|
|
|
|
baseUrl := fmt.Sprintf("%s%s", api.Link, actualPath)
|
|
|
|
if strings.Contains(baseUrl, "_shuffle_replace_") {
|
|
//log.Printf("[DEBUG] : %s", baseUrl)
|
|
m := regexp.MustCompile(`_shuffle_replace_\d+`)
|
|
baseUrl = m.ReplaceAllString(baseUrl, "")
|
|
}
|
|
|
|
newDesc := fmt.Sprintf("%s\n\n%s", path.Delete.Description, baseUrl)
|
|
action := WorkflowAppAction{
|
|
Description: newDesc,
|
|
Name: fmt.Sprintf("%s %s", "Delete", path.Delete.Summary),
|
|
Label: fmt.Sprintf("%s", path.Delete.Summary),
|
|
NodeType: "action",
|
|
Environment: api.Environment,
|
|
Parameters: extraParameters,
|
|
}
|
|
|
|
if val, ok := path.Delete.ExtensionProps.Extensions["x-label"]; ok {
|
|
labels := []string{}
|
|
j, err := json.Marshal(&val)
|
|
if err == nil {
|
|
err = json.Unmarshal(j, &labels)
|
|
if err == nil {
|
|
action.CategoryLabel = labels
|
|
} else {
|
|
log.Printf("[ERROR] Could not unmarshal x-label array: %s", err)
|
|
}
|
|
} else {
|
|
log.Printf("[ERROR] Could not marshal x-label: %s", err)
|
|
}
|
|
}
|
|
|
|
if val, ok := path.Delete.ExtensionProps.Extensions["x-required-fields"]; ok {
|
|
j, err := json.Marshal(&val)
|
|
if err == nil {
|
|
if j[0] == 0x22 && j[len(j)-1] == 0x22 {
|
|
j = j[1 : len(j)-1]
|
|
}
|
|
}
|
|
|
|
newValue := []string{}
|
|
err = json.Unmarshal(j, &newValue)
|
|
if err == nil {
|
|
action.RequiredBodyFields = newValue
|
|
//log.Printf("Setting required bodyfields: %#v", newValue)
|
|
} else {
|
|
log.Printf("[ERROR] Failed to unmarshal required bodyfields %s: %s", string(j), err)
|
|
}
|
|
}
|
|
|
|
action.Returns.Schema.Type = "string"
|
|
handleFile := false
|
|
|
|
//log.Println(path.Parameters)
|
|
|
|
// Parameters: []WorkflowAppActionParameter{},
|
|
//firstQuery := true
|
|
optionalQueries := []string{}
|
|
parameters := []string{}
|
|
|
|
headersFound := []string{}
|
|
if len(path.Delete.Parameters) > 0 {
|
|
for counter, param := range path.Delete.Parameters {
|
|
if param.Value.Schema == nil {
|
|
continue
|
|
} else if param.Value.In == "header" {
|
|
headersFound = append(headersFound, fmt.Sprintf("%s=%s", param.Value.Name, param.Value.Example))
|
|
continue
|
|
}
|
|
|
|
parsedName := param.Value.Name
|
|
parsedName = strings.Replace(parsedName, " ", "_", -1)
|
|
parsedName = strings.Replace(parsedName, ",", "_", -1)
|
|
parsedName = strings.Replace(parsedName, ".", "_", -1)
|
|
parsedName = strings.Replace(parsedName, "|", "_", -1)
|
|
parsedName = ValidateParameterName(parsedName)
|
|
param.Value.Name = parsedName
|
|
path.Delete.Parameters[counter].Value.Name = parsedName
|
|
|
|
// Force it as a string to avoid nil-pointer
|
|
if param.Value.Schema.Value == nil {
|
|
param.Value.Schema.Value = &openapi3.Schema{
|
|
Type: "string",
|
|
}
|
|
}
|
|
|
|
curParam := WorkflowAppActionParameter{
|
|
Name: parsedName,
|
|
Description: param.Value.Description,
|
|
Multiline: false,
|
|
Required: param.Value.Required,
|
|
Schema: SchemaDefinition{
|
|
Type: param.Value.Schema.Value.Type,
|
|
},
|
|
}
|
|
|
|
if param.Value.Example != nil {
|
|
if exampleVal, ok := param.Value.Example.(string); !ok {
|
|
curParam.Example = fmt.Sprintf("%v", param.Value.Example)
|
|
} else {
|
|
curParam.Example = exampleVal
|
|
}
|
|
|
|
if param.Value.Name == "body" {
|
|
if exampleVal, ok := param.Value.Example.(string); !ok {
|
|
|
|
curParam.Value = fmt.Sprintf("%v", param.Value.Example)
|
|
} else {
|
|
curParam.Value = exampleVal
|
|
}
|
|
}
|
|
}
|
|
|
|
if val, ok := param.Value.ExtensionProps.Extensions["multiline"]; ok {
|
|
j, err := json.Marshal(&val)
|
|
if err == nil {
|
|
b, err := strconv.ParseBool(string(j))
|
|
if err == nil {
|
|
curParam.Multiline = b
|
|
}
|
|
}
|
|
}
|
|
|
|
if param.Value.Required {
|
|
action.Parameters = append(action.Parameters, curParam)
|
|
} else {
|
|
optionalParameters = append(optionalParameters, curParam)
|
|
}
|
|
|
|
if param.Value.In == "path" {
|
|
parameters = append(parameters, curParam.Name)
|
|
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
|
|
} else if param.Value.In == "query" {
|
|
//log.Printf("QUERY!: %s", param.Value.Name)
|
|
if !param.Value.Required {
|
|
optionalQueries = append(optionalQueries, param.Value.Name)
|
|
continue
|
|
}
|
|
|
|
parameters = append(parameters, param.Value.Name)
|
|
|
|
if strings.Contains(baseUrl, fmt.Sprintf("%s={%s}", param.Value.Name, param.Value.Name)) {
|
|
continue
|
|
}
|
|
|
|
if strings.Contains(baseUrl, fmt.Sprintf("{%s}", param.Value.Name)) {
|
|
continue
|
|
}
|
|
|
|
//if firstQuery && !strings.Contains(baseUrl, "?") {
|
|
// baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name)
|
|
//} else {
|
|
// baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name)
|
|
//}
|
|
//firstQuery = false
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if len(headersFound) > 0 {
|
|
setIndex := -1
|
|
for paramIndex, param := range optionalParameters {
|
|
if param.Name == "headers" {
|
|
setIndex = paramIndex
|
|
break
|
|
}
|
|
}
|
|
|
|
if setIndex >= 0 {
|
|
for _, header := range headersFound {
|
|
if !strings.Contains(header, "=") {
|
|
continue
|
|
}
|
|
|
|
headerKey := strings.Split(header, "=")[0]
|
|
if strings.Contains(optionalParameters[setIndex].Value, headerKey) {
|
|
continue
|
|
}
|
|
|
|
optionalParameters[setIndex].Value = fmt.Sprintf("%s%s\n", optionalParameters[setIndex].Value, header)
|
|
}
|
|
|
|
//log.Printf("What: %#v", optionalParameters[setIndex].Value[len(optionalParameters[setIndex].Value)-1])
|
|
//log.Printf("HI: %s",
|
|
//optionalParameters[setIndex].Value[len(optionalParameters[setIndex].Value)-2])
|
|
// Removing newlines at the end
|
|
if len(optionalParameters[setIndex].Value) > 0 && optionalParameters[setIndex].Value[len(optionalParameters[setIndex].Value)-1] == 0xa {
|
|
optionalParameters[setIndex].Value = optionalParameters[setIndex].Value[0 : len(optionalParameters[setIndex].Value)-1]
|
|
}
|
|
|
|
//log.Printf("%#v", optionalParameters[setIndex].Value)
|
|
}
|
|
}
|
|
|
|
// Must be here 'cus they should be last
|
|
headerKey := `headers=""`
|
|
if !ArrayContains(parameters, headerKey) {
|
|
parameters = append(parameters, headerKey)
|
|
}
|
|
|
|
queryKey := `queries=""`
|
|
if !ArrayContains(parameters, queryKey) {
|
|
parameters = append(parameters, queryKey)
|
|
}
|
|
|
|
// ensuring that they end up last in the specification
|
|
// (order is ish important for optional params) - they need to be last.
|
|
for _, optionalParam := range optionalParameters {
|
|
optionalParam.Name = strings.ToLower(optionalParam.Name)
|
|
action.Parameters = append(action.Parameters, optionalParam)
|
|
}
|
|
|
|
functionname, curCode := MakePythoncode(swagger, functionName, baseUrl, "delete", parameters, optionalQueries, headersFound, "", api, handleFile)
|
|
|
|
if len(functionname) > 0 {
|
|
action.Name = functionname
|
|
}
|
|
|
|
return action, curCode
|
|
}
|
|
|
|
func HandlePost(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string, optionalParameters []WorkflowAppActionParameter) (WorkflowAppAction, string) {
|
|
// What to do with this, hmm
|
|
//log.Printf("PATH: %s", actualPath)
|
|
functionName := FixFunctionName(path.Post.Summary, actualPath, true)
|
|
|
|
baseUrl := fmt.Sprintf("%s%s", api.Link, actualPath)
|
|
if strings.Contains(baseUrl, "_shuffle_replace_") {
|
|
//log.Printf("[DEBUG] : %s", baseUrl)
|
|
m := regexp.MustCompile(`_shuffle_replace_\d+`)
|
|
baseUrl = m.ReplaceAllString(baseUrl, "")
|
|
}
|
|
|
|
newDesc := fmt.Sprintf("%s\n\n%s", path.Post.Description, baseUrl)
|
|
action := WorkflowAppAction{
|
|
Description: newDesc,
|
|
Name: fmt.Sprintf("%s %s", "Post", path.Post.Summary),
|
|
Label: fmt.Sprintf("%s", path.Post.Summary),
|
|
NodeType: "action",
|
|
Environment: api.Environment,
|
|
Parameters: extraParameters,
|
|
}
|
|
|
|
if val, ok := path.Post.ExtensionProps.Extensions["x-label"]; ok {
|
|
labels := []string{}
|
|
j, err := json.Marshal(&val)
|
|
if err == nil {
|
|
err = json.Unmarshal(j, &labels)
|
|
if err == nil {
|
|
action.CategoryLabel = labels
|
|
} else {
|
|
log.Printf("[ERROR] Could not unmarshal x-label array: %s", err)
|
|
}
|
|
} else {
|
|
log.Printf("[ERROR] Could not marshal x-label: %s", err)
|
|
}
|
|
}
|
|
|
|
if val, ok := path.Post.ExtensionProps.Extensions["x-required-fields"]; ok {
|
|
j, err := json.Marshal(&val)
|
|
if err == nil {
|
|
if j[0] == 0x22 && j[len(j)-1] == 0x22 {
|
|
j = j[1 : len(j)-1]
|
|
}
|
|
}
|
|
|
|
newValue := []string{}
|
|
err = json.Unmarshal(j, &newValue)
|
|
if err == nil {
|
|
action.RequiredBodyFields = newValue
|
|
//log.Printf("Setting required bodyfields: %#v", newValue)
|
|
} else {
|
|
log.Printf("[ERROR] Failed to unmarshal required bodyfields %s: %s", string(j), err)
|
|
}
|
|
}
|
|
|
|
action.Returns.Schema.Type = "string"
|
|
handleFile := false
|
|
|
|
// Parameters: []WorkflowAppActionParameter{},
|
|
// FIXME - add data for POST stuff
|
|
//firstQuery := true
|
|
optionalQueries := []string{}
|
|
parameters := []string{}
|
|
|
|
fileField := ""
|
|
if path.Post.RequestBody != nil {
|
|
value := path.Post.RequestBody.Value
|
|
if val, ok := value.Content["multipart/form-data"]; ok {
|
|
if val.Schema.Value != nil {
|
|
if innerval, ok := val.Schema.Value.Properties["fieldname"]; ok {
|
|
if extensionvalue, ok := innerval.Value.ExtensionProps.Extensions["value"]; ok {
|
|
fieldname := extensionvalue.(json.RawMessage)
|
|
newName := string(fmt.Sprintf("%s", string(fieldname)))
|
|
if newName[0] == 0x22 && newName[len(newName)-1] == 0x22 {
|
|
parsedName := newName[1 : len(newName)-1]
|
|
//log.Printf("Parse name: %s", parsedName)
|
|
fileField = parsedName
|
|
|
|
curParam := WorkflowAppActionParameter{
|
|
Name: "file_id",
|
|
Description: "Files to be uploaded",
|
|
Multiline: false,
|
|
Required: true,
|
|
Schema: SchemaDefinition{
|
|
Type: "string",
|
|
},
|
|
}
|
|
|
|
action.Parameters = append(action.Parameters, curParam)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
headersFound := []string{}
|
|
if len(path.Post.Parameters) > 0 {
|
|
for counter, param := range path.Post.Parameters {
|
|
if param.Value.Schema == nil {
|
|
continue
|
|
} else if param.Value.In == "header" {
|
|
headersFound = append(headersFound, fmt.Sprintf("%s=%s", param.Value.Name, param.Value.Example))
|
|
continue
|
|
}
|
|
|
|
parsedName := param.Value.Name
|
|
parsedName = strings.Replace(parsedName, " ", "_", -1)
|
|
parsedName = strings.Replace(parsedName, ",", "_", -1)
|
|
parsedName = strings.Replace(parsedName, ".", "_", -1)
|
|
parsedName = strings.Replace(parsedName, "|", "_", -1)
|
|
parsedName = ValidateParameterName(parsedName)
|
|
param.Value.Name = parsedName
|
|
path.Post.Parameters[counter].Value.Name = parsedName
|
|
|
|
// Force it as a string to avoid nil-pointer
|
|
if param.Value.Schema.Value == nil {
|
|
param.Value.Schema.Value = &openapi3.Schema{
|
|
Type: "string",
|
|
}
|
|
}
|
|
|
|
curParam := WorkflowAppActionParameter{
|
|
Name: parsedName,
|
|
Description: param.Value.Description,
|
|
Multiline: false,
|
|
Required: param.Value.Required,
|
|
Schema: SchemaDefinition{
|
|
Type: param.Value.Schema.Value.Type,
|
|
},
|
|
}
|
|
|
|
if param.Value.Example != nil {
|
|
if exampleVal, ok := param.Value.Example.(string); !ok {
|
|
curParam.Example = fmt.Sprintf("%v", param.Value.Example)
|
|
} else {
|
|
curParam.Example = exampleVal
|
|
}
|
|
|
|
if param.Value.Name == "body" {
|
|
if exampleVal, ok := param.Value.Example.(string); !ok {
|
|
|
|
curParam.Value = fmt.Sprintf("%v", param.Value.Example)
|
|
} else {
|
|
curParam.Value = exampleVal
|
|
}
|
|
}
|
|
}
|
|
|
|
if val, ok := param.Value.ExtensionProps.Extensions["multiline"]; ok {
|
|
j, err := json.Marshal(&val)
|
|
if err == nil {
|
|
b, err := strconv.ParseBool(string(j))
|
|
if err == nil {
|
|
curParam.Multiline = b
|
|
}
|
|
}
|
|
}
|
|
|
|
if param.Value.Required {
|
|
action.Parameters = append(action.Parameters, curParam)
|
|
} else {
|
|
optionalParameters = append(optionalParameters, curParam)
|
|
}
|
|
|
|
if param.Value.In == "path" {
|
|
parameters = append(parameters, curParam.Name)
|
|
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
|
|
} else if param.Value.In == "query" {
|
|
//log.Printf("QUERY!: %s", param.Value.Name)
|
|
if !param.Value.Required {
|
|
optionalQueries = append(optionalQueries, param.Value.Name)
|
|
continue
|
|
}
|
|
|
|
parameters = append(parameters, param.Value.Name)
|
|
|
|
if strings.Contains(baseUrl, fmt.Sprintf("%s={%s}", param.Value.Name, param.Value.Name)) {
|
|
continue
|
|
}
|
|
|
|
if strings.Contains(baseUrl, fmt.Sprintf("{%s}", param.Value.Name)) {
|
|
continue
|
|
}
|
|
|
|
//if firstQuery && !strings.Contains(baseUrl, "?") {
|
|
// baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name)
|
|
//} else {
|
|
// baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name)
|
|
//}
|
|
//firstQuery = false
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(headersFound) > 0 {
|
|
setIndex := -1
|
|
for paramIndex, param := range optionalParameters {
|
|
if param.Name == "headers" {
|
|
setIndex = paramIndex
|
|
break
|
|
}
|
|
}
|
|
|
|
if setIndex >= 0 {
|
|
for _, header := range headersFound {
|
|
if !strings.Contains(header, "=") {
|
|
continue
|
|
}
|
|
|
|
headerKey := strings.Split(header, "=")[0]
|
|
if strings.Contains(optionalParameters[setIndex].Value, headerKey) {
|
|
continue
|
|
}
|
|
|
|
optionalParameters[setIndex].Value = fmt.Sprintf("%s%s\n", optionalParameters[setIndex].Value, header)
|
|
}
|
|
|
|
//log.Printf("What: %#v", optionalParameters[setIndex].Value[len(optionalParameters[setIndex].Value)-1])
|
|
//log.Printf("HI: %s",
|
|
//optionalParameters[setIndex].Value[len(optionalParameters[setIndex].Value)-2])
|
|
// Removing newlines at the end
|
|
if len(optionalParameters[setIndex].Value) > 0 && optionalParameters[setIndex].Value[len(optionalParameters[setIndex].Value)-1] == 0xa {
|
|
optionalParameters[setIndex].Value = optionalParameters[setIndex].Value[0 : len(optionalParameters[setIndex].Value)-1]
|
|
}
|
|
|
|
//log.Printf("%#v", optionalParameters[setIndex].Value)
|
|
}
|
|
}
|
|
|
|
// Must be here 'cus they should be last
|
|
headerKey := `headers=""`
|
|
if !ArrayContains(parameters, headerKey) {
|
|
parameters = append(parameters, headerKey)
|
|
}
|
|
|
|
queryKey := `queries=""`
|
|
if !ArrayContains(parameters, queryKey) {
|
|
parameters = append(parameters, queryKey)
|
|
}
|
|
|
|
// ensuring that they end up last in the specification
|
|
// (order is ish important for optional params) - they need to be last.
|
|
for _, optionalParam := range optionalParameters {
|
|
optionalParam.Name = strings.ToLower(optionalParam.Name)
|
|
action.Parameters = append(action.Parameters, optionalParam)
|
|
}
|
|
|
|
functionname, curCode := MakePythoncode(swagger, functionName, baseUrl, "post", parameters, optionalQueries, headersFound, fileField, api, handleFile)
|
|
|
|
if len(functionname) > 0 {
|
|
action.Name = functionname
|
|
}
|
|
|
|
//log.Printf("PARAMS: %d", len(action.Parameters))
|
|
//for _, param := range action.Parameters {
|
|
// log.Printf("%#v", param)
|
|
//}
|
|
|
|
return action, curCode
|
|
}
|
|
|
|
func HandlePatch(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string, optionalParameters []WorkflowAppActionParameter) (WorkflowAppAction, string) {
|
|
// What to do with this, hmm
|
|
functionName := FixFunctionName(path.Patch.Summary, actualPath, true)
|
|
|
|
baseUrl := fmt.Sprintf("%s%s", api.Link, actualPath)
|
|
newDesc := fmt.Sprintf("%s\n\n%s", path.Patch.Description, baseUrl)
|
|
action := WorkflowAppAction{
|
|
Description: newDesc,
|
|
Name: fmt.Sprintf("%s %s", "Patch", path.Patch.Summary),
|
|
Label: fmt.Sprintf("%s", path.Patch.Summary),
|
|
NodeType: "action",
|
|
Environment: api.Environment,
|
|
Parameters: extraParameters,
|
|
}
|
|
|
|
if val, ok := path.Patch.ExtensionProps.Extensions["x-label"]; ok {
|
|
labels := []string{}
|
|
j, err := json.Marshal(&val)
|
|
if err == nil {
|
|
err = json.Unmarshal(j, &labels)
|
|
if err == nil {
|
|
action.CategoryLabel = labels
|
|
} else {
|
|
log.Printf("[ERROR] Could not unmarshal x-label array: %s", err)
|
|
}
|
|
} else {
|
|
log.Printf("[ERROR] Could not marshal x-label: %s", err)
|
|
}
|
|
}
|
|
|
|
if val, ok := path.Patch.ExtensionProps.Extensions["x-required-fields"]; ok {
|
|
j, err := json.Marshal(&val)
|
|
if err == nil {
|
|
if j[0] == 0x22 && j[len(j)-1] == 0x22 {
|
|
j = j[1 : len(j)-1]
|
|
}
|
|
}
|
|
|
|
newValue := []string{}
|
|
err = json.Unmarshal(j, &newValue)
|
|
if err == nil {
|
|
action.RequiredBodyFields = newValue
|
|
//log.Printf("Setting required bodyfields: %#v", newValue)
|
|
} else {
|
|
log.Printf("[ERROR] Failed to unmarshal required bodyfields %s: %s", string(j), err)
|
|
}
|
|
}
|
|
|
|
action.Returns.Schema.Type = "string"
|
|
if strings.Contains(baseUrl, "_shuffle_replace_") {
|
|
//log.Printf("[DEBUG] : %s", baseUrl)
|
|
m := regexp.MustCompile(`_shuffle_replace_\d+`)
|
|
baseUrl = m.ReplaceAllString(baseUrl, "")
|
|
}
|
|
handleFile := false
|
|
|
|
//log.Println(path.Parameters)
|
|
|
|
// Parameters: []WorkflowAppActionParameter{},
|
|
//firstQuery := true
|
|
optionalQueries := []string{}
|
|
parameters := []string{}
|
|
|
|
headersFound := []string{}
|
|
if len(path.Patch.Parameters) > 0 {
|
|
for counter, param := range path.Patch.Parameters {
|
|
if param.Value.Schema == nil {
|
|
continue
|
|
} else if param.Value.In == "header" {
|
|
headersFound = append(headersFound, fmt.Sprintf("%s=%s", param.Value.Name, param.Value.Example))
|
|
continue
|
|
}
|
|
|
|
parsedName := param.Value.Name
|
|
parsedName = strings.Replace(parsedName, " ", "_", -1)
|
|
parsedName = strings.Replace(parsedName, ",", "_", -1)
|
|
parsedName = strings.Replace(parsedName, ".", "_", -1)
|
|
parsedName = strings.Replace(parsedName, "|", "_", -1)
|
|
parsedName = ValidateParameterName(parsedName)
|
|
param.Value.Name = parsedName
|
|
path.Patch.Parameters[counter].Value.Name = parsedName
|
|
|
|
// Force it as a string to avoid nil-pointer
|
|
if param.Value.Schema.Value == nil {
|
|
param.Value.Schema.Value = &openapi3.Schema{
|
|
Type: "string",
|
|
}
|
|
}
|
|
|
|
curParam := WorkflowAppActionParameter{
|
|
Name: parsedName,
|
|
Description: param.Value.Description,
|
|
Multiline: false,
|
|
Required: param.Value.Required,
|
|
Schema: SchemaDefinition{
|
|
Type: param.Value.Schema.Value.Type,
|
|
},
|
|
}
|
|
|
|
if param.Value.Example != nil {
|
|
if exampleVal, ok := param.Value.Example.(string); !ok {
|
|
curParam.Example = fmt.Sprintf("%v", param.Value.Example)
|
|
} else {
|
|
curParam.Example = exampleVal
|
|
}
|
|
|
|
if param.Value.Name == "body" {
|
|
if exampleVal, ok := param.Value.Example.(string); !ok {
|
|
|
|
curParam.Value = fmt.Sprintf("%v", param.Value.Example)
|
|
} else {
|
|
curParam.Value = exampleVal
|
|
}
|
|
}
|
|
}
|
|
|
|
if val, ok := param.Value.ExtensionProps.Extensions["multiline"]; ok {
|
|
j, err := json.Marshal(&val)
|
|
if err == nil {
|
|
b, err := strconv.ParseBool(string(j))
|
|
if err == nil {
|
|
curParam.Multiline = b
|
|
}
|
|
}
|
|
}
|
|
|
|
if param.Value.Required {
|
|
action.Parameters = append(action.Parameters, curParam)
|
|
} else {
|
|
optionalParameters = append(optionalParameters, curParam)
|
|
}
|
|
|
|
if param.Value.In == "path" {
|
|
parameters = append(parameters, curParam.Name)
|
|
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
|
|
} else if param.Value.In == "query" {
|
|
//log.Printf("QUERY!: %s", param.Value.Name)
|
|
if !param.Value.Required {
|
|
optionalQueries = append(optionalQueries, param.Value.Name)
|
|
continue
|
|
}
|
|
|
|
parameters = append(parameters, param.Value.Name)
|
|
|
|
if strings.Contains(baseUrl, fmt.Sprintf("%s={%s}", param.Value.Name, param.Value.Name)) {
|
|
continue
|
|
}
|
|
|
|
if strings.Contains(baseUrl, fmt.Sprintf("{%s}", param.Value.Name)) {
|
|
continue
|
|
}
|
|
|
|
//if firstQuery && !strings.Contains(baseUrl, "?") {
|
|
// baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name)
|
|
//} else {
|
|
// baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name)
|
|
//}
|
|
//firstQuery = false
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(headersFound) > 0 {
|
|
setIndex := -1
|
|
for paramIndex, param := range optionalParameters {
|
|
if param.Name == "headers" {
|
|
setIndex = paramIndex
|
|
break
|
|
}
|
|
}
|
|
|
|
if setIndex >= 0 {
|
|
for _, header := range headersFound {
|
|
if !strings.Contains(header, "=") {
|
|
continue
|
|
}
|
|
|
|
headerKey := strings.Split(header, "=")[0]
|
|
if strings.Contains(optionalParameters[setIndex].Value, headerKey) {
|
|
continue
|
|
}
|
|
|
|
optionalParameters[setIndex].Value = fmt.Sprintf("%s%s\n", optionalParameters[setIndex].Value, header)
|
|
}
|
|
|
|
//log.Printf("What: %#v", optionalParameters[setIndex].Value[len(optionalParameters[setIndex].Value)-1])
|
|
//log.Printf("HI: %s",
|
|
//optionalParameters[setIndex].Value[len(optionalParameters[setIndex].Value)-2])
|
|
// Removing newlines at the end
|
|
if len(optionalParameters[setIndex].Value) > 0 && optionalParameters[setIndex].Value[len(optionalParameters[setIndex].Value)-1] == 0xa {
|
|
optionalParameters[setIndex].Value = optionalParameters[setIndex].Value[0 : len(optionalParameters[setIndex].Value)-1]
|
|
}
|
|
|
|
//log.Printf("%#v", optionalParameters[setIndex].Value)
|
|
}
|
|
}
|
|
|
|
// Must be here 'cus they should be last
|
|
headerKey := `headers=""`
|
|
if !ArrayContains(parameters, headerKey) {
|
|
parameters = append(parameters, headerKey)
|
|
}
|
|
|
|
queryKey := `queries=""`
|
|
if !ArrayContains(parameters, queryKey) {
|
|
parameters = append(parameters, queryKey)
|
|
}
|
|
|
|
// ensuring that they end up last in the specification
|
|
// (order is ish important for optional params) - they need to be last.
|
|
for _, optionalParam := range optionalParameters {
|
|
optionalParam.Name = strings.ToLower(optionalParam.Name)
|
|
action.Parameters = append(action.Parameters, optionalParam)
|
|
}
|
|
|
|
functionname, curCode := MakePythoncode(swagger, functionName, baseUrl, "patch", parameters, optionalQueries, headersFound, "", api, handleFile)
|
|
|
|
if len(functionname) > 0 {
|
|
action.Name = functionname
|
|
}
|
|
|
|
return action, curCode
|
|
}
|
|
|
|
func HandlePut(swagger *openapi3.Swagger, api WorkflowApp, extraParameters []WorkflowAppActionParameter, path *openapi3.PathItem, actualPath string, optionalParameters []WorkflowAppActionParameter) (WorkflowAppAction, string) {
|
|
// What to do with this, hmm
|
|
functionName := FixFunctionName(path.Put.Summary, actualPath, true)
|
|
|
|
baseUrl := fmt.Sprintf("%s%s", api.Link, actualPath)
|
|
|
|
if strings.Contains(baseUrl, "_shuffle_replace_") {
|
|
//log.Printf("[DEBUG] : %s", baseUrl)
|
|
m := regexp.MustCompile(`_shuffle_replace_\d+`)
|
|
baseUrl = m.ReplaceAllString(baseUrl, "")
|
|
}
|
|
|
|
newDesc := fmt.Sprintf("%s\n\n%s", path.Put.Description, baseUrl)
|
|
action := WorkflowAppAction{
|
|
Description: newDesc,
|
|
Name: fmt.Sprintf("%s %s", "Put", path.Put.Summary),
|
|
Label: fmt.Sprintf("%s", path.Put.Summary),
|
|
NodeType: "action",
|
|
Environment: api.Environment,
|
|
Parameters: extraParameters,
|
|
}
|
|
|
|
if val, ok := path.Put.ExtensionProps.Extensions["x-label"]; ok {
|
|
labels := []string{}
|
|
j, err := json.Marshal(&val)
|
|
if err == nil {
|
|
err = json.Unmarshal(j, &labels)
|
|
if err == nil {
|
|
action.CategoryLabel = labels
|
|
} else {
|
|
log.Printf("[ERROR] Could not unmarshal x-label array: %s", err)
|
|
}
|
|
} else {
|
|
log.Printf("[ERROR] Could not marshal x-label: %s", err)
|
|
}
|
|
}
|
|
|
|
if val, ok := path.Put.ExtensionProps.Extensions["x-required-fields"]; ok {
|
|
j, err := json.Marshal(&val)
|
|
if err == nil {
|
|
if j[0] == 0x22 && j[len(j)-1] == 0x22 {
|
|
j = j[1 : len(j)-1]
|
|
}
|
|
}
|
|
|
|
newValue := []string{}
|
|
err = json.Unmarshal(j, &newValue)
|
|
if err == nil {
|
|
action.RequiredBodyFields = newValue
|
|
//log.Printf("Setting required bodyfields: %#v", newValue)
|
|
} else {
|
|
log.Printf("[ERROR] Failed to unmarshal required bodyfields %s: %s", string(j), err)
|
|
}
|
|
}
|
|
|
|
action.Returns.Schema.Type = "string"
|
|
handleFile := false
|
|
|
|
//log.Println(path.Parameters)
|
|
|
|
// Parameters: []WorkflowAppActionParameter{},
|
|
//firstQuery := true
|
|
optionalQueries := []string{}
|
|
parameters := []string{}
|
|
|
|
headersFound := []string{}
|
|
if len(path.Put.Parameters) > 0 {
|
|
for counter, param := range path.Put.Parameters {
|
|
if param.Value.Schema == nil {
|
|
continue
|
|
} else if param.Value.In == "header" {
|
|
headersFound = append(headersFound, fmt.Sprintf("%s=%s", param.Value.Name, param.Value.Example))
|
|
continue
|
|
}
|
|
|
|
parsedName := param.Value.Name
|
|
parsedName = strings.Replace(parsedName, " ", "_", -1)
|
|
parsedName = strings.Replace(parsedName, ",", "_", -1)
|
|
parsedName = strings.Replace(parsedName, ".", "_", -1)
|
|
parsedName = strings.Replace(parsedName, "|", "_", -1)
|
|
parsedName = ValidateParameterName(parsedName)
|
|
param.Value.Name = parsedName
|
|
path.Put.Parameters[counter].Value.Name = parsedName
|
|
|
|
// Force it as a string to avoid nil-pointer
|
|
if param.Value.Schema.Value == nil {
|
|
param.Value.Schema.Value = &openapi3.Schema{
|
|
Type: "string",
|
|
}
|
|
}
|
|
|
|
curParam := WorkflowAppActionParameter{
|
|
Name: parsedName,
|
|
Description: param.Value.Description,
|
|
Multiline: false,
|
|
Required: param.Value.Required,
|
|
Schema: SchemaDefinition{
|
|
Type: param.Value.Schema.Value.Type,
|
|
},
|
|
}
|
|
|
|
if param.Value.Example != nil {
|
|
if exampleVal, ok := param.Value.Example.(string); !ok {
|
|
curParam.Example = fmt.Sprintf("%v", param.Value.Example)
|
|
} else {
|
|
curParam.Example = exampleVal
|
|
}
|
|
|
|
if param.Value.Name == "body" {
|
|
if exampleVal, ok := param.Value.Example.(string); !ok {
|
|
|
|
curParam.Value = fmt.Sprintf("%v", param.Value.Example)
|
|
} else {
|
|
curParam.Value = exampleVal
|
|
}
|
|
}
|
|
}
|
|
|
|
if val, ok := param.Value.ExtensionProps.Extensions["multiline"]; ok {
|
|
j, err := json.Marshal(&val)
|
|
if err == nil {
|
|
b, err := strconv.ParseBool(string(j))
|
|
if err == nil {
|
|
curParam.Multiline = b
|
|
}
|
|
}
|
|
}
|
|
|
|
if param.Value.Required {
|
|
action.Parameters = append(action.Parameters, curParam)
|
|
} else {
|
|
optionalParameters = append(optionalParameters, curParam)
|
|
}
|
|
|
|
if param.Value.In == "path" {
|
|
parameters = append(parameters, param.Value.Name)
|
|
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
|
|
} else if param.Value.In == "query" {
|
|
//log.Printf("QUERY!: %s", param.Value.Name)
|
|
if !param.Value.Required {
|
|
optionalQueries = append(optionalQueries, param.Value.Name)
|
|
continue
|
|
}
|
|
|
|
parameters = append(parameters, param.Value.Name)
|
|
|
|
if strings.Contains(baseUrl, fmt.Sprintf("%s={%s}", param.Value.Name, param.Value.Name)) {
|
|
continue
|
|
}
|
|
|
|
if strings.Contains(baseUrl, fmt.Sprintf("{%s}", param.Value.Name)) {
|
|
continue
|
|
}
|
|
|
|
//if firstQuery && !strings.Contains(baseUrl, "?") {
|
|
// baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name)
|
|
//} else {
|
|
// baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name)
|
|
//}
|
|
//firstQuery = false
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if len(headersFound) > 0 {
|
|
setIndex := -1
|
|
for paramIndex, param := range optionalParameters {
|
|
if param.Name == "headers" {
|
|
setIndex = paramIndex
|
|
break
|
|
}
|
|
}
|
|
|
|
if setIndex >= 0 {
|
|
for _, header := range headersFound {
|
|
if !strings.Contains(header, "=") {
|
|
continue
|
|
}
|
|
|
|
headerKey := strings.Split(header, "=")[0]
|
|
if strings.Contains(optionalParameters[setIndex].Value, headerKey) {
|
|
continue
|
|
}
|
|
|
|
optionalParameters[setIndex].Value = fmt.Sprintf("%s%s\n", optionalParameters[setIndex].Value, header)
|
|
}
|
|
|
|
//log.Printf("What: %#v", optionalParameters[setIndex].Value[len(optionalParameters[setIndex].Value)-1])
|
|
//log.Printf("HI: %s",
|
|
//optionalParameters[setIndex].Value[len(optionalParameters[setIndex].Value)-2])
|
|
// Removing newlines at the end
|
|
if len(optionalParameters[setIndex].Value) > 0 && optionalParameters[setIndex].Value[len(optionalParameters[setIndex].Value)-1] == 0xa {
|
|
optionalParameters[setIndex].Value = optionalParameters[setIndex].Value[0 : len(optionalParameters[setIndex].Value)-1]
|
|
}
|
|
|
|
//log.Printf("%#v", optionalParameters[setIndex].Value)
|
|
}
|
|
}
|
|
|
|
// Must be here 'cus they should be last
|
|
headerKey := `headers=""`
|
|
if !ArrayContains(parameters, headerKey) {
|
|
parameters = append(parameters, headerKey)
|
|
}
|
|
|
|
queryKey := `queries=""`
|
|
if !ArrayContains(parameters, queryKey) {
|
|
parameters = append(parameters, queryKey)
|
|
}
|
|
|
|
// ensuring that they end up last in the specification
|
|
// (order is ish important for optional params) - they need to be last.
|
|
for _, optionalParam := range optionalParameters {
|
|
optionalParam.Name = strings.ToLower(optionalParam.Name)
|
|
action.Parameters = append(action.Parameters, optionalParam)
|
|
}
|
|
|
|
functionname, curCode := MakePythoncode(swagger, functionName, baseUrl, "put", parameters, optionalQueries, headersFound, "", api, handleFile)
|
|
|
|
if len(functionname) > 0 {
|
|
action.Name = functionname
|
|
}
|
|
|
|
return action, curCode
|
|
}
|
|
|
|
func GetAppRequirements() string {
|
|
return "requests==2.32.3\nurllib3==2.3.0\nliquidpy==0.8.2\nMarkupSafe==3.0.2\nflask[async]==3.1.0\npython-dateutil==2.9.0.post0\nPyJWT==2.10.1\ncryptography==44.0.2\nshufflepy==0.2.2\nshuffle-sdk==0.0.38"
|
|
}
|
|
|
|
// Removes JSON values from the input
|
|
func RemoveJsonValues(input []byte, depth int64) ([]byte, string, error) {
|
|
// Make the byte into a map[string]interface{} so we can iterate over it
|
|
keyToken := ""
|
|
|
|
var jsonParsed map[string]interface{}
|
|
err := json.Unmarshal(input, &jsonParsed)
|
|
if err != nil {
|
|
return input, keyToken, err
|
|
}
|
|
|
|
// Sort the keys so we can iterate over them in order
|
|
keys := make([]string, 0, len(jsonParsed))
|
|
for k := range jsonParsed {
|
|
keys = append(keys, k)
|
|
}
|
|
|
|
sort.Strings(keys)
|
|
|
|
// Iterate over the map[string]interface{} and remove the values
|
|
for _, k := range keys {
|
|
keyToken += k
|
|
// Get the value of the key as a map[string]interface{}
|
|
//log.Printf("k: %v, %#v", k, jsonParsed[k])
|
|
// Check if it's a list or not
|
|
if _, ok := jsonParsed[k].([]interface{}); ok {
|
|
// Recurse this function
|
|
|
|
newListItem := []interface{}{}
|
|
for loopItem, v := range jsonParsed[k].([]interface{}) {
|
|
_ = loopItem
|
|
|
|
if parsedValue, ok := v.(map[string]interface{}); ok {
|
|
// Marshal the value
|
|
newParsedValue, err := json.MarshalIndent(parsedValue, "", "\t")
|
|
if err != nil {
|
|
log.Printf("[ERROR] Error in index %d of key %s: %v", loopItem, k, err)
|
|
continue
|
|
}
|
|
|
|
returnJson, newKeyToken, err := RemoveJsonValues([]byte(string(newParsedValue)), depth+1)
|
|
_ = newKeyToken
|
|
|
|
if err != nil {
|
|
log.Printf("[ERROR] Error: %v", err)
|
|
} else {
|
|
//log.Printf("returnJson (1): %v", string(returnJson))
|
|
// Unmarshal the byte back into a map[string]interface{}
|
|
var jsonParsed2 map[string]interface{}
|
|
err := json.Unmarshal(returnJson, &jsonParsed2)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Error: %v", err)
|
|
} else {
|
|
newListItem = append(newListItem, jsonParsed2)
|
|
}
|
|
}
|
|
|
|
} else if _, ok := v.([]interface{}); ok {
|
|
// FIXME: No loop in loop for now
|
|
log.Printf("[ERROR] No Handler Error in index %d of key %s: %v", loopItem, k, err)
|
|
} else if _, ok := v.(string); ok {
|
|
newListItem = append(newListItem, "")
|
|
} else if _, ok := v.(float64); ok {
|
|
newListItem = append(newListItem, 0)
|
|
} else if _, ok := v.(bool); ok {
|
|
newListItem = append(newListItem, false)
|
|
} else {
|
|
//log.Printf("[ERROR] No Handler Error in index %d of key %s: %v", loopItem, k, err)
|
|
}
|
|
}
|
|
|
|
jsonParsed[k] = newListItem
|
|
}
|
|
|
|
// Check if it's a string
|
|
if _, ok := jsonParsed[k].(string); ok {
|
|
// Remove the value
|
|
jsonParsed[k] = ""
|
|
} else if _, ok := jsonParsed[k].(float64); ok {
|
|
jsonParsed[k] = 0
|
|
} else if _, ok := jsonParsed[k].(bool); ok {
|
|
jsonParsed[k] = false
|
|
} else if _, ok := jsonParsed[k].(map[string]interface{}); ok {
|
|
newParsedValue, err := json.MarshalIndent(jsonParsed[k].(map[string]interface{}), "", "\t")
|
|
if err != nil {
|
|
log.Printf("[ERROR] Error in key %s: %v", k, err)
|
|
continue
|
|
}
|
|
|
|
returnJson, newKeyToken, err := RemoveJsonValues([]byte(string(newParsedValue)), depth+1)
|
|
|
|
if depth < 3 && len(newKeyToken) > 0 {
|
|
keyToken += "." + newKeyToken
|
|
}
|
|
|
|
if err != nil {
|
|
log.Printf("[ERROR] Error: %v", err)
|
|
} else {
|
|
//log.Printf("returnJson (2): %v", string(returnJson))
|
|
// Unmarshal the byte back into a map[string]interface{}
|
|
var jsonParsed2 map[string]interface{}
|
|
err := json.Unmarshal(returnJson, &jsonParsed2)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Error: %v", err)
|
|
} else {
|
|
jsonParsed[k] = jsonParsed2
|
|
}
|
|
}
|
|
|
|
} else {
|
|
//log.Printf("[ERROR] No Handler Error in key %s: %v", k, err)
|
|
}
|
|
|
|
// Check if the value is a map[string]interface{}
|
|
//if _, ok := v.(map[string]interface{}); ok {
|
|
// // Remove the value
|
|
// v = nil
|
|
//}
|
|
}
|
|
|
|
// Marshal the map[string]interface{} back into a byte
|
|
input, err = json.MarshalIndent(jsonParsed, "", "\t")
|
|
if err != nil {
|
|
return input, keyToken, err
|
|
}
|
|
|
|
return input, keyToken, nil
|
|
}
|
|
|
|
// TIL:
|
|
func splitRef(full string) (host, repo, tag string) {
|
|
parts := strings.SplitN(full, "/", 2)
|
|
host = parts[0]
|
|
remainder := ""
|
|
if len(parts) > 1 {
|
|
remainder = parts[1]
|
|
}
|
|
tag = "latest"
|
|
if i := strings.LastIndex(remainder, ":"); i != -1 {
|
|
repo = remainder[:i]
|
|
tag = remainder[i+1:]
|
|
} else {
|
|
repo = remainder
|
|
}
|
|
|
|
log.Printf("%s -> %s %s %s", full, host, repo, tag)
|
|
return
|
|
}
|
|
|
|
func hexOf(b []byte) string {
|
|
h := sha256.Sum256(b)
|
|
return fmt.Sprintf("%x", h[:])
|
|
}
|
|
|
|
// upload a single layer.tar by gzipping on the fly and streaming to registry using chunked upload.
|
|
// returns (compressedDigest, compressedSize, diffID).
|
|
func uploadLayerToRegistry(regBase, repoPath string, layer io.Reader) (string, int64, string, error) {
|
|
startURL := fmt.Sprintf("%s/v2/%s/blobs/uploads/", regBase, repoPath)
|
|
reqStart, _ := http.NewRequest(http.MethodPost, startURL, nil)
|
|
loc, err := followLocation(reqStart)
|
|
if err != nil {
|
|
return "", 0, "", fmt.Errorf("start upload: %s", err)
|
|
}
|
|
|
|
// pipe: gzip(layer) -> (count+hash) -> PATCH
|
|
pr, pw := io.Pipe()
|
|
var wg sync.WaitGroup
|
|
var compBytes int64
|
|
compHash := sha256.New()
|
|
diffHash := sha256.New()
|
|
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
defer pw.Close()
|
|
gzw, _ := gzip.NewWriterLevel(io.MultiWriter(pw, countingWriter{&compBytes}, compHash), gzip.BestSpeed)
|
|
_, copyErr := io.Copy(gzw, io.TeeReader(layer, diffHash))
|
|
if cerr := gzw.Close(); copyErr == nil {
|
|
copyErr = cerr
|
|
}
|
|
if copyErr != nil {
|
|
_ = pw.CloseWithError(copyErr)
|
|
}
|
|
}()
|
|
|
|
reqPatch, _ := http.NewRequest("PATCH", loc, pr)
|
|
reqPatch.Header.Set("Content-Type", "application/octet-stream")
|
|
loc, err = followLocation(reqPatch)
|
|
if err != nil {
|
|
return "", 0, "", fmt.Errorf("patch upload: %w", err)
|
|
}
|
|
|
|
wg.Wait()
|
|
compDigest := "sha256:" + fmt.Sprintf("%x", compHash.Sum(nil))
|
|
diffID := "sha256:" + fmt.Sprintf("%x", diffHash.Sum(nil))
|
|
|
|
finalURL := loc
|
|
if strings.Contains(finalURL, "?") {
|
|
finalURL = finalURL + "&digest=" + compDigest
|
|
} else {
|
|
finalURL = finalURL + "?digest=" + compDigest
|
|
}
|
|
|
|
reqPut, _ := http.NewRequest(http.MethodPut, finalURL, nil)
|
|
if _, err := regDo(reqPut); err != nil {
|
|
return "", 0, "", fmt.Errorf("finalize upload: %w", err)
|
|
}
|
|
|
|
return compDigest, compBytes, diffID, nil
|
|
}
|
|
|
|
func uploadBlobOnce(regBase, repoPath string, r io.Reader, size int64, digest, contentType string) error {
|
|
startURL := fmt.Sprintf("%s/v2/%s/blobs/uploads/", regBase, repoPath)
|
|
reqStart, _ := http.NewRequest("POST", startURL, nil)
|
|
loc, err := followLocation(reqStart)
|
|
if err != nil {
|
|
return fmt.Errorf("start upload: %s", err)
|
|
}
|
|
|
|
reqPatch, _ := http.NewRequest("PATCH", loc, r)
|
|
if contentType == "" {
|
|
contentType = "application/octet-stream"
|
|
}
|
|
|
|
reqPatch.Header.Set("Content-Type", contentType)
|
|
loc, err = followLocation(reqPatch)
|
|
if err != nil {
|
|
return fmt.Errorf("patch upload: %s", err)
|
|
}
|
|
|
|
finalURL := loc
|
|
if strings.Contains(finalURL, "?") {
|
|
finalURL = finalURL + "&digest=" + digest
|
|
} else {
|
|
finalURL = finalURL + "?digest=" + digest
|
|
}
|
|
|
|
reqPut, _ := http.NewRequest("PUT", finalURL, nil)
|
|
_, err = regDo(reqPut)
|
|
return err
|
|
}
|
|
|
|
func followLocation(req *http.Request) (string, error) {
|
|
resp, err := regDo(req)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
loc := resp.Header.Get("Location")
|
|
resp.Body.Close()
|
|
if loc == "" {
|
|
return "", fmt.Errorf("missing Location in response to %s %s", req.Method, req.URL.String())
|
|
}
|
|
|
|
u, _ := url.Parse(loc)
|
|
if !u.IsAbs() {
|
|
base := &url.URL{Scheme: req.URL.Scheme, Host: req.URL.Host, Path: loc}
|
|
return base.String(), nil
|
|
}
|
|
|
|
return loc, nil
|
|
}
|
|
|
|
func regDo(req *http.Request) (*http.Response, error) {
|
|
// TODO: inject Authorization for registry if required
|
|
c := &http.Client{Timeout: 0}
|
|
resp, err := c.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func (w countingWriter) Write(p []byte) (int, error) {
|
|
*w.n += int64(len(p))
|
|
return len(p), nil
|
|
}
|
|
|
|
func DownloadDockerImageBackend(topClient *http.Client, imageName string) error {
|
|
// Check environment SHUFFLE_AUTO_IMAGE_DOWNLOAD
|
|
if os.Getenv("SHUFFLE_AUTO_IMAGE_DOWNLOAD") == "false" {
|
|
log.Printf("[DEBUG] SHUFFLE_AUTO_IMAGE_DOWNLOAD is false. NOT downloading image %s", imageName)
|
|
return nil
|
|
}
|
|
|
|
var dwnImage sync.Mutex
|
|
|
|
// Remove from downloadedImages after 5 minutes for a redownload
|
|
time.AfterFunc(time.Minute*5, func() {
|
|
dwnImage.Lock()
|
|
defer dwnImage.Unlock()
|
|
|
|
cleanedImages := downloadedImages[:0] // len=0 cap=same, ptr same
|
|
for _, img := range downloadedImages {
|
|
if img != imageName {
|
|
cleanedImages = append(cleanedImages, img)
|
|
}
|
|
}
|
|
downloadedImages = cleanedImages
|
|
})
|
|
|
|
if ArrayContains(downloadedImages, imageName) && project.Environment == "worker" {
|
|
log.Printf("[DEBUG] Image %s already downloaded - not re-downloading. This only applies to workers.", imageName)
|
|
return nil
|
|
}
|
|
|
|
baseUrl := os.Getenv("BASE_URL")
|
|
//log.Printf("[DEBUG] Trying to download image %s from backend %s as it doesn't exist", imageName, baseUrl)
|
|
|
|
if !ArrayContains(downloadedImages, imageName) {
|
|
downloadedImages = append(downloadedImages, imageName)
|
|
}
|
|
|
|
dockerImgUrl := fmt.Sprintf("%s/api/v1/get_docker_image?image=%s", baseUrl, strings.Replace(imageName, " ", "-", -1))
|
|
|
|
isCloudDownload := false
|
|
if strings.Contains(baseUrl, "ngrok") || strings.Contains(baseUrl, "shuffler.io") || strings.Contains(baseUrl, ".run.app") {
|
|
log.Printf("[DEBUG] Downloading as GET request with redirects")
|
|
dockerImgUrl = fmt.Sprintf("%s/api/v1/get_docker_image?image=%s", baseUrl, strings.Replace(imageName, " ", "-", -1))
|
|
isCloudDownload = true
|
|
} else {
|
|
//log.Printf("[DEBUG] Downloading image as POST request WITHOUT redirects due to not being cloud")
|
|
}
|
|
|
|
// Set request timeout to 5 min (max)
|
|
topClient.Timeout = time.Minute * 10
|
|
arch := runtime.GOARCH
|
|
if strings.Contains(strings.ToLower(arch), "arm") {
|
|
if strings.Contains(dockerImgUrl, "?") {
|
|
dockerImgUrl = fmt.Sprintf("%s&arch=%s", dockerImgUrl, arch)
|
|
} else {
|
|
dockerImgUrl = fmt.Sprintf("%s?arch=%s", dockerImgUrl, arch)
|
|
}
|
|
}
|
|
|
|
relevantBody := DockerRequestCheck{
|
|
Name: strings.Replace(imageName, " ", "-", -1),
|
|
}
|
|
|
|
marshalledBody, err := json.Marshal(relevantBody)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to marshal body to be sent: %s", err)
|
|
return err
|
|
}
|
|
|
|
req, err := http.NewRequest(
|
|
"POST",
|
|
dockerImgUrl,
|
|
bytes.NewBuffer(marshalledBody),
|
|
)
|
|
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to create request for %s: %s", imageName, err)
|
|
return err
|
|
}
|
|
|
|
if isCloudDownload {
|
|
log.Printf("[DEBUG] Running GET request for cloud download for URL %s", dockerImgUrl)
|
|
req.Method = "GET"
|
|
req.Body = nil
|
|
}
|
|
|
|
// Specific to the worker
|
|
authorization := os.Getenv("AUTHORIZATION")
|
|
if len(authorization) > 0 {
|
|
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", authorization))
|
|
} else {
|
|
// Specific to Orborus auth (org + auth) -> environment auth
|
|
authorization = os.Getenv("AUTH")
|
|
if len(authorization) > 0 {
|
|
//log.Printf("[DEBUG] Found Orborus environment auth - adding to header.")
|
|
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", authorization))
|
|
|
|
org := os.Getenv("ORG")
|
|
if len(org) > 0 {
|
|
req.Header.Add("Org-Id", org)
|
|
}
|
|
|
|
} else {
|
|
log.Printf("[WARNING] No auth found - running backend download without it.")
|
|
}
|
|
}
|
|
|
|
if len(os.Getenv("IS_KUBERNETES")) > 0 && os.Getenv("IS_KUBERNETES") == "true" {
|
|
log.Printf("[INFO] In kubernetes pushing it to private registry")
|
|
localRegistry := os.Getenv("SHUFFLE_STREAM_PRIVATE_REGISTRY")
|
|
if localRegistry == "" {
|
|
log.Printf("[ERROR] No private registry defined")
|
|
return err
|
|
}
|
|
|
|
imgPath := strings.TrimPrefix(strings.ReplaceAll(imageName, " ", "-"), "/")
|
|
refStr := fmt.Sprintf("%s/%s", strings.TrimSuffix(localRegistry, "/"), imgPath)
|
|
|
|
insecure := os.Getenv("SHUFFLE_STREAM_PRIVATE_REGISTRY_INSECURE") == "true"
|
|
scheme := "https"
|
|
if insecure {
|
|
scheme = "http"
|
|
}
|
|
|
|
regHost, repoPath, tag := splitRef(refStr)
|
|
regBase := scheme + "://" + regHost
|
|
|
|
resp, err := topClient.Do(req.Clone(req.Context()))
|
|
if err != nil {
|
|
log.Printf("[ERROR] Bucket request failed")
|
|
return fmt.Errorf("bucket request failed: %w", err)
|
|
}
|
|
|
|
if resp.StatusCode != 200 {
|
|
defer resp.Body.Close()
|
|
log.Printf("[ERROR] Bucket request failed bad staus code")
|
|
return fmt.Errorf("bucket bad status %s", resp.Status)
|
|
}
|
|
|
|
br := bufio.NewReader(resp.Body)
|
|
var tarR *tar.Reader
|
|
if peek, _ := br.Peek(2); len(peek) == 2 && peek[0] == 0x1f && peek[1] == 0x8b {
|
|
gr, err := gzip.NewReader(br)
|
|
if err != nil {
|
|
resp.Body.Close()
|
|
log.Printf("[ERROR] Gzip init failed")
|
|
return fmt.Errorf("gzip init failed: %w", err)
|
|
}
|
|
|
|
defer gr.Close()
|
|
defer resp.Body.Close()
|
|
tarR = tar.NewReader(gr)
|
|
} else {
|
|
defer resp.Body.Close()
|
|
tarR = tar.NewReader(br)
|
|
}
|
|
|
|
type desc struct {
|
|
Digest string
|
|
Size int64
|
|
}
|
|
layerByDiff := map[string]desc{}
|
|
var diffOrder []string
|
|
var configJSON []byte
|
|
|
|
// walk tar once: upload layers & capture config
|
|
for {
|
|
hdr, err := tarR.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("tar read: %w", err)
|
|
}
|
|
name := hdr.Name
|
|
|
|
switch {
|
|
case strings.HasSuffix(name, "/layer.tar"):
|
|
// stream this layer: compute diffID (uncompressed), gzip+upload to registry, record digest & size
|
|
compDig, compSize, diffID, err := uploadLayerToRegistry(regBase, repoPath, tarR)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to upload layer %s", err)
|
|
return fmt.Errorf("upload layer %s: %w", name, err)
|
|
}
|
|
|
|
layerByDiff[diffID] = desc{Digest: compDig, Size: compSize}
|
|
|
|
case strings.HasSuffix(name, ".json") && name != "manifest.json":
|
|
if len(configJSON) == 0 {
|
|
var buf bytes.Buffer
|
|
if _, err := io.Copy(&buf, tarR); err != nil {
|
|
log.Printf("[ERROR] Failed to read config.json")
|
|
return fmt.Errorf("read config.json: %w", err)
|
|
}
|
|
|
|
var probe struct {
|
|
RootFS struct {
|
|
DiffIDs []string `json:"diff_ids"`
|
|
} `json:"rootfs"`
|
|
}
|
|
|
|
if json.Unmarshal(buf.Bytes(), &probe) == nil && len(probe.RootFS.DiffIDs) > 0 {
|
|
configJSON = buf.Bytes()
|
|
for _, d := range probe.RootFS.DiffIDs {
|
|
if !strings.HasPrefix(d, "sha256:") {
|
|
d = "sha256:" + d
|
|
}
|
|
diffOrder = append(diffOrder, d)
|
|
}
|
|
}
|
|
}
|
|
|
|
default:
|
|
}
|
|
}
|
|
|
|
if len(configJSON) == 0 || len(diffOrder) == 0 {
|
|
log.Printf("[ERROR] Failed to docker save tar config")
|
|
return fmt.Errorf("docker save tar missing config")
|
|
}
|
|
|
|
layers := make([]map[string]any, 0, len(diffOrder))
|
|
for _, d := range diffOrder {
|
|
desc, ok := layerByDiff[d]
|
|
if !ok {
|
|
log.Printf("[ERROR] Failed to upload layer (2)")
|
|
return fmt.Errorf("missing uploaded layer for diffID %s", d)
|
|
}
|
|
layers = append(layers, map[string]any{
|
|
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
|
"size": desc.Size,
|
|
"digest": desc.Digest,
|
|
})
|
|
}
|
|
|
|
cfgDigest := "sha256:" + hexOf(configJSON)
|
|
if err := uploadBlobOnce(regBase, repoPath, bytes.NewReader(configJSON), int64(len(configJSON)), cfgDigest, "application/octet-stream"); err != nil {
|
|
log.Printf("[ERROR] Failed to upload config (2)")
|
|
return fmt.Errorf("upload config: %w", err)
|
|
}
|
|
|
|
manifest := map[string]any{
|
|
"schemaVersion": 2,
|
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
|
"config": map[string]any{
|
|
"mediaType": "application/vnd.docker.container.image.v1+json",
|
|
"size": len(configJSON),
|
|
"digest": cfgDigest,
|
|
},
|
|
"layers": layers,
|
|
}
|
|
|
|
manBytes, _ := json.Marshal(manifest)
|
|
putURL := fmt.Sprintf("%s/v2/%s/manifests/%s", regBase, repoPath, tag)
|
|
reqM, _ := http.NewRequest(http.MethodPut, putURL, bytes.NewReader(manBytes))
|
|
reqM.Header.Set("Content-Type", "application/vnd.docker.distribution.manifest.v2+json")
|
|
reqM.Header.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json")
|
|
if _, err := regDo(reqM); err != nil {
|
|
log.Printf("[ERROR] Failed to put mainfest")
|
|
return fmt.Errorf("put manifest: %w", err)
|
|
}
|
|
|
|
log.Printf("[INFO] Pushed image to private registry as %s", refStr)
|
|
return nil
|
|
}
|
|
|
|
newresp, err := topClient.Do(req)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed download request for %s: %s", imageName, err)
|
|
return err
|
|
}
|
|
|
|
if newresp.Request.URL.String() != dockerImgUrl {
|
|
log.Printf("[DEBUG] Redirected download URL: %s -> %s", dockerImgUrl, newresp.Request.URL.String())
|
|
}
|
|
|
|
defer newresp.Body.Close()
|
|
if newresp.StatusCode != 200 {
|
|
//log.Printf("[ERROR] Docker download for image %s (backend) StatusCode (1): %d", imageName, newresp.StatusCode)
|
|
return errors.New(fmt.Sprintf("Failed to get image - status code %d", newresp.StatusCode))
|
|
}
|
|
|
|
newImageName := strings.Replace(imageName, "/", "_", -1)
|
|
newFileName := newImageName + ".tar"
|
|
|
|
tar, err := os.Create(newFileName)
|
|
if err != nil {
|
|
log.Printf("[WARNING] Failed creating file: %s", err)
|
|
return err
|
|
}
|
|
|
|
defer tar.Close()
|
|
_, err = io.Copy(tar, newresp.Body)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed response body copying for file %s: %s", newFileName, err)
|
|
return err
|
|
}
|
|
|
|
tar.Seek(0, 0)
|
|
dockercli, err := docker.NewEnvClient()
|
|
if err != nil {
|
|
log.Printf("[ERROR] Unable to create docker client (3): %s", err)
|
|
return err
|
|
}
|
|
|
|
//log.Printf("[DEBUG] Starting to load zip file for image %s. This is a background process and may take a while.", imageName)
|
|
//imageLoadResponse, err := dockercli.ImageLoad(context.Background(), tar, true)
|
|
defer dockercli.Close()
|
|
//imageLoadResponse, err := dockercli.ImageLoad(context.Background(), tar)
|
|
imageLoadResponse, err := dockercli.ImageLoad(context.Background(), tar)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed loading docker images: %s", err)
|
|
return err
|
|
}
|
|
|
|
//log.Printf("[DEBUG] Finished loading zip file for image %s", imageName)
|
|
defer imageLoadResponse.Body.Close()
|
|
body, err := ioutil.ReadAll(imageLoadResponse.Body)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed reading docker image: %s", err)
|
|
return err
|
|
}
|
|
|
|
if strings.Contains(string(body), "no such file") {
|
|
return errors.New(string(body))
|
|
}
|
|
|
|
os.Remove(newFileName)
|
|
if strings.Contains(strings.ToLower(string(body)), "error") {
|
|
log.Printf("[ERROR] Error loading image %s: %s", imageName, string(body))
|
|
return errors.New(string(body))
|
|
}
|
|
|
|
baseTag := strings.Split(imageName, ":")
|
|
if len(baseTag) > 1 {
|
|
tag := baseTag[1]
|
|
//log.Printf("[DEBUG] Creating tag copies of downloaded containers from tag %s", tag)
|
|
|
|
// Remapping
|
|
ctx := context.Background()
|
|
dockercli.ImageTag(ctx, imageName, fmt.Sprintf("frikky/shuffle:%s", tag))
|
|
dockercli.ImageTag(ctx, imageName, fmt.Sprintf("registry.hub.docker.com/frikky/shuffle:%s", tag))
|
|
|
|
downloadedImages = append(downloadedImages, fmt.Sprintf("frikky/shuffle:%s", tag))
|
|
downloadedImages = append(downloadedImages, fmt.Sprintf("registry.hub.docker.com/frikky/shuffle:%s", tag))
|
|
|
|
}
|
|
|
|
//log.Printf("[INFO] Successfully loaded image %s: %s", imageName, string(body))
|
|
|
|
return nil
|
|
}
|
|
|
|
func GetAppNameSplit(version DockerRequestCheck) (string, string, string, error) {
|
|
if len(version.Image) > 0 && len(version.Name) == 0 {
|
|
version.Name = version.Image
|
|
}
|
|
|
|
if len(version.Name) == 0 {
|
|
return "", "", "", errors.New("No image name found")
|
|
}
|
|
|
|
identifier := strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(version.Name, "_", "-"), ".", "-"))
|
|
if strings.Contains(identifier, ":") {
|
|
identifier = strings.Split(identifier, ":")[1]
|
|
}
|
|
|
|
appname := version.Name
|
|
appnameSplit := strings.Split(version.Name, ":")
|
|
if len(appnameSplit) > 1 {
|
|
appname = appnameSplit[1]
|
|
}
|
|
|
|
appId := ""
|
|
appnameSplit2 := strings.Split(appname, ".")
|
|
appnameSplit3 := strings.Split(appname, "_")
|
|
if len(appnameSplit2) <= 2 {
|
|
|
|
// Check last item of app split on _ if it's an md5
|
|
if len(appnameSplit3) > 1 && len(appnameSplit3[len(appnameSplit3)-1]) == 32 {
|
|
appId = appnameSplit3[len(appnameSplit3)-1]
|
|
|
|
// Remove the md5 from the appname
|
|
appname = strings.Join(appnameSplit3[0:len(appnameSplit3)-1], "_")
|
|
|
|
} else {
|
|
err := errors.New(fmt.Sprintf("Invalid image appname format: %s", version.Name))
|
|
return "", "", "", err
|
|
}
|
|
}
|
|
|
|
baseAppname := appnameSplit2[0][0 : len(appnameSplit2[0])-2]
|
|
|
|
// In case of a weird version name/number that is not semantic
|
|
if len(appnameSplit2) > 3 {
|
|
// JUST remove the LAST dot parts
|
|
newstring := ""
|
|
for cnt, part := range appnameSplit2 {
|
|
if cnt > len(appnameSplit2)-2 {
|
|
continue
|
|
}
|
|
|
|
newstring += part + "."
|
|
}
|
|
|
|
// Removes the last _1 of _1.0.0 (or similar) in versions
|
|
baseAppname = newstring[0 : len(newstring)-5]
|
|
}
|
|
|
|
if debug {
|
|
log.Printf("%#v - BASEAPPNAME: %#v, %#v", appname, baseAppname, appnameSplit2)
|
|
}
|
|
|
|
// Check if baseAppname ends with _<md5> and if so, remove it
|
|
if len(appId) > 0 {
|
|
// Remove appId from baseAppname by removing last _<md5> part
|
|
baseAppname = strings.Join(appnameSplit3[0:len(appnameSplit3)-1], "_")
|
|
}
|
|
|
|
appVersion := ""
|
|
if len(appnameSplit2) >= 2 {
|
|
appVersion = appnameSplit2[0][len(appnameSplit2[0])-1:] + "." + strings.Join(appnameSplit2[1:], ".")
|
|
}
|
|
|
|
appname = strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(appname, "_", "-"), ".", "-"))
|
|
|
|
return appname, baseAppname, appVersion, nil
|
|
}
|
|
|
|
func handleDatastoreAutomationWebhook(ctx context.Context, marshalledBody []byte, cacheData CacheKeyData, automation DatastoreAutomation, url, runType string) error {
|
|
var err error
|
|
|
|
// Dedup here with cache
|
|
cacheName := fmt.Sprintf("automation_%s_%s_%s", runType, cacheData.Category, cacheData.Key)
|
|
_, err = GetCache(ctx, cacheName)
|
|
if err == nil {
|
|
if debug {
|
|
log.Printf("[DEBUG] Found existing cache for %s - skipping execution to prevent duplicates", cacheName)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Makes sure we wait 2500ms
|
|
SetCache(ctx, cacheName, []byte("1"), 2500, true)
|
|
|
|
if runType == "run_workflow" {
|
|
|
|
} else if runType == "webhook" {
|
|
webhookUrl := ""
|
|
for _, option := range automation.Options {
|
|
if option.Key == "webhook_url" {
|
|
webhookUrl = option.Value
|
|
break
|
|
}
|
|
}
|
|
|
|
if !strings.HasPrefix(webhookUrl, "http") {
|
|
return errors.New(fmt.Sprintf("Webhook URL %s is not valid", webhookUrl))
|
|
}
|
|
|
|
parsedBody := WorkflowAppAction{
|
|
AppID: "HTTP",
|
|
AppName: "HTTP",
|
|
Environment: "cloud",
|
|
//Name: "custom_command",
|
|
Name: "POST",
|
|
NodeType: "action",
|
|
Parameters: []WorkflowAppActionParameter{
|
|
WorkflowAppActionParameter{
|
|
Name: "url",
|
|
Value: webhookUrl,
|
|
},
|
|
WorkflowAppActionParameter{
|
|
Name: "method",
|
|
Value: "POST",
|
|
},
|
|
WorkflowAppActionParameter{
|
|
Name: "body",
|
|
Value: string(marshalledBody),
|
|
},
|
|
WorkflowAppActionParameter{
|
|
Name: "headers",
|
|
Value: "Content-Type: application/json\nAccept: application/json",
|
|
},
|
|
},
|
|
}
|
|
|
|
if project.Environment != "cloud" {
|
|
environments, err := GetEnvironments(ctx, cacheData.OrgId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, env := range environments {
|
|
if env.Default {
|
|
parsedBody.Environment = env.Name
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
marshalledBody, err = json.Marshal(parsedBody)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to marshal parsedBody for webhook %s: %s", webhookUrl, err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Find a user to use
|
|
org, err := GetOrg(ctx, cacheData.OrgId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
foundApikey := ""
|
|
for _, user := range org.Users {
|
|
if user.Role != "admin" {
|
|
continue
|
|
}
|
|
|
|
if len(user.ApiKey) > 0 {
|
|
foundApikey = user.ApiKey
|
|
break
|
|
} else {
|
|
foundUser, err := GetUser(ctx, user.Id)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to get user by ID %s: %s", user.Id, err)
|
|
continue
|
|
}
|
|
|
|
if len(foundUser.ApiKey) > 0 {
|
|
foundApikey = foundUser.ApiKey
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Send request to
|
|
backendUrl := os.Getenv("BASE_URL")
|
|
if len(os.Getenv("SHUFFLE_CLOUDRUN_URL")) > 0 && strings.Contains(os.Getenv("SHUFFLE_CLOUDRUN_URL"), "http") {
|
|
backendUrl = os.Getenv("SHUFFLE_CLOUDRUN_URL")
|
|
}
|
|
|
|
//parsedUrl := fmt.Sprintf("%s/api/v1/apps/HTTP/run", backendUrl)
|
|
parsedUrl := fmt.Sprintf("%s%s", backendUrl, url)
|
|
client := &http.Client{
|
|
Timeout: time.Second * 5,
|
|
}
|
|
|
|
req, err := http.NewRequest(
|
|
"POST",
|
|
parsedUrl,
|
|
bytes.NewBuffer(marshalledBody),
|
|
)
|
|
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to create request for webhook %s: %s", parsedUrl, err)
|
|
return err
|
|
}
|
|
|
|
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", foundApikey))
|
|
req.Header.Add("Org-Id", cacheData.OrgId)
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to send webhook request to %s: %s", parsedUrl, err)
|
|
return err
|
|
}
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to read response body from webhook request to %s: %s", parsedUrl, err)
|
|
return err
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode >= 300 {
|
|
if runType == "run_workflow" {
|
|
log.Printf("[ERROR] Datastore Automation: Workflow Run request to %s failed with status code %d", parsedUrl, resp.StatusCode)
|
|
return errors.New(fmt.Sprintf("Workflow Run request failed with status code %d. Body: %s", resp.StatusCode, body))
|
|
} else {
|
|
log.Printf("[ERROR] Datastore Automation: Webhook request to %s failed with status code %d", parsedUrl, resp.StatusCode)
|
|
return errors.New(fmt.Sprintf("Webhook request failed with status code %d. Body: %s", resp.StatusCode, body))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func handleRunDatastoreAutomation(ctx context.Context, cacheData CacheKeyData, automation DatastoreAutomation) error {
|
|
if len(cacheData.OrgId) == 0 {
|
|
return errors.New("CacheKeyData.OrgId is required for handleRunAutomation")
|
|
}
|
|
|
|
if len(cacheData.Category) == 0 {
|
|
return errors.New("CacheKeyData.Category is required for handleRunAutomation")
|
|
}
|
|
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
|
|
parsedName := strings.ReplaceAll(strings.ToLower(automation.Name), " ", "_")
|
|
|
|
// These are ran pre-execution
|
|
if parsedName == "security_rules" {
|
|
return nil
|
|
}
|
|
|
|
// Unmarshal cacheData.Value to parsedOutput
|
|
parsedOutput := map[string]interface{}{}
|
|
if err := json.Unmarshal([]byte(cacheData.Value), &parsedOutput); err != nil {
|
|
log.Printf("[ERROR] Failed to unmarshal cacheData.Value: %s", err)
|
|
parsedOutput = map[string]interface{}{}
|
|
parsedOutput["value"] = cacheData.Value
|
|
}
|
|
|
|
if parsedOutput == nil {
|
|
parsedOutput = map[string]interface{}{}
|
|
}
|
|
|
|
parsedOutput["shuffle_datastore"] = map[string]interface{}{
|
|
"action": "update",
|
|
"key": cacheData.Key,
|
|
"category": cacheData.Category,
|
|
"org_id": cacheData.OrgId,
|
|
"timestamp": cacheData.Edited,
|
|
"workflow_id": cacheData.WorkflowId,
|
|
"suborg_distribution": cacheData.SuborgDistribution,
|
|
"tags": cacheData.Tags,
|
|
}
|
|
|
|
marshalledBody, err := json.Marshal(parsedOutput)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to marshal parsedOutput. Key %s, Category: %s, org: %s, err: %s", cacheData.Key, cacheData.Category, cacheData.OrgId, err)
|
|
return err
|
|
}
|
|
|
|
backendUrl := "https://shuffler.io"
|
|
if len(os.Getenv("BASE_URL")) > 0 {
|
|
backendUrl = os.Getenv("BASE_URL")
|
|
}
|
|
|
|
if len(os.Getenv("SHUFFLE_CLOUDRUN_URL")) > 0 && strings.Contains(os.Getenv("SHUFFLE_CLOUDRUN_URL"), "http") {
|
|
backendUrl = os.Getenv("SHUFFLE_CLOUDRUN_URL")
|
|
}
|
|
|
|
org, err := GetOrg(ctx, cacheData.OrgId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
foundApikey := ""
|
|
for _, user := range org.Users {
|
|
foundUser, err := GetUser(ctx, user.Id)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
if len(foundUser.Role) == 0 || foundUser.Role == "org-reader" {
|
|
continue
|
|
}
|
|
|
|
if len(foundUser.ApiKey) > 0 {
|
|
foundApikey = foundUser.ApiKey
|
|
break
|
|
}
|
|
}
|
|
|
|
if parsedName == "correlate_categories" {
|
|
// Correlations don't matter anymore as ngrams are automatic. Cleaned up
|
|
// november 2025 after adding graphic system to datastore
|
|
|
|
} else if parsedName == "run_ai_agent" {
|
|
log.Printf("[DEBUG] AI agent: Handling run_ai_agent automation for key %s in category %s", cacheData.Key, cacheData.Category)
|
|
if len(foundApikey) == 0 {
|
|
log.Printf("[ERROR] No admin user with API key found for org %s", cacheData.OrgId)
|
|
return errors.New("No admin user with API key found")
|
|
}
|
|
|
|
// Already handled check
|
|
for optionKey, option := range automation.Options {
|
|
// 'remove' icon in the UI does this
|
|
if option.Disabled {
|
|
continue
|
|
}
|
|
|
|
if len(option.Value) < 10 {
|
|
//log.Printf("[DEBUG] Actions info too short: %s - skipping", option.Key)
|
|
continue
|
|
}
|
|
|
|
agentTagName := fmt.Sprintf("agent-%s", option.Key)
|
|
if ArrayContains(cacheData.Tags, agentTagName) {
|
|
continue
|
|
}
|
|
|
|
// Check if previous has finished/timed out
|
|
// This allows next to run. Default agent cache timeout is 30 seconds~
|
|
if optionKey > 0 {
|
|
oldKey := automation.Options[optionKey-1]
|
|
oldCacheName := fmt.Sprintf("%s_%s_%s_%s", cacheData.Key, cacheData.Category, cacheData.OrgId, oldKey.Key)
|
|
_, err := GetCache(ctx, oldCacheName)
|
|
if err == nil {
|
|
if debug {
|
|
log.Printf("[DEBUG] PREV agent cache hit for %s - skipping for now", oldCacheName)
|
|
}
|
|
|
|
continue
|
|
}
|
|
}
|
|
|
|
// As a fallback in case of slow datastore update
|
|
// Prevents super quick reruns
|
|
cacheName := fmt.Sprintf("%s_%s_%s_%s", cacheData.Key, cacheData.Category, cacheData.OrgId, option.Key)
|
|
_, err := GetCache(ctx, cacheName)
|
|
if err == nil {
|
|
//log.Printf("[DEBUG] Cache hit for %s - skipping to avoid re-running agent", cacheName)
|
|
continue
|
|
}
|
|
|
|
// 30 seconds
|
|
SetCache(ctx, cacheName, []byte("1"), 60000, true)
|
|
if !strings.Contains(option.Key, "action") {
|
|
log.Printf("[WARNING] Agent option key %s does not contain 'action' - skipping to avoid confusion. This may cause the agent to not run if no other options are present.", option.Key)
|
|
continue
|
|
}
|
|
|
|
allowedApps := strings.Join(option.Apps, ",")
|
|
parsedParams := []map[string]string{
|
|
map[string]string{
|
|
"name": "app_name",
|
|
"value": allowedApps,
|
|
},
|
|
map[string]string{
|
|
"name": "action",
|
|
"value": "API",
|
|
},
|
|
}
|
|
|
|
// option.Value += fmt.Sprintf("\n%s", cacheData.Value)
|
|
parsedParams = append(parsedParams, map[string]string{
|
|
"name": "input",
|
|
"value": fmt.Sprintf("TASK: %s\n\nKey: %s\nCategory: %s\n\nRAW DATA:\n%s", option.Value, cacheData.Key, cacheData.Category, cacheData.Value),
|
|
})
|
|
|
|
agentUrl := fmt.Sprintf("%s/api/v1/apps/agent_starter/run", backendUrl)
|
|
agentStartRequest := AgentStartRequest{
|
|
//ID string `json:"id"`
|
|
Name: "agent",
|
|
AppName: "AI Agent",
|
|
AppID: "shuffle_agent",
|
|
AppVersion: "1.0.0",
|
|
Environment: "cloud",
|
|
Parameters: parsedParams,
|
|
}
|
|
|
|
newParsedBody, err := json.Marshal(agentStartRequest)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to marshal body for ai agent execution: %s", err)
|
|
return err
|
|
}
|
|
|
|
client := GetExternalClient(agentUrl)
|
|
req, err := http.NewRequest(
|
|
"POST",
|
|
agentUrl,
|
|
bytes.NewBuffer(newParsedBody),
|
|
)
|
|
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to create request for enrichment workflow execution: %s", err)
|
|
return err
|
|
}
|
|
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", foundApikey))
|
|
req.Header.Set("Org-Id", cacheData.OrgId)
|
|
req.Header.Set("X-Internal-Caller", "handleRunDatastoreAutomation")
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to send enrichment workflow execution request: %s", err)
|
|
return err
|
|
}
|
|
|
|
// Makes sure we don't re-run the same twice
|
|
cacheData.Tags = append(cacheData.Tags, agentTagName)
|
|
err = SetDatastoreKeyMeta(ctx, cacheData)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to set cache key after running AI agent: %s", err)
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to read response body from AI AGENT execution request: %s", err)
|
|
return err
|
|
}
|
|
|
|
if debug {
|
|
log.Printf("[DEBUG] RESP FOR RUNNING AI AGENT (%d): %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
break
|
|
}
|
|
|
|
} else if parsedName == "enrich" {
|
|
// Prevent recursion
|
|
cacheKey := fmt.Sprintf("enrich_wait_%s_%s_%s", cacheData.OrgId, cacheData.Category, cacheData.Key)
|
|
|
|
|
|
// Validates if the data is the same. Need a proper data diff
|
|
//md5sum := Md5sum([]byte(cacheData.Value))
|
|
//log.Printf("VALUE (%s):\n\n%s\n\n", md5sum, cacheData.Value)
|
|
|
|
data, err := GetCache(ctx, cacheKey)
|
|
if err == nil && data != nil {
|
|
//cacheData := []byte(data.([]uint8))
|
|
//if string(cacheData) == md5sum {
|
|
// return nil
|
|
//}
|
|
|
|
return nil
|
|
}
|
|
|
|
if debug {
|
|
log.Printf("[DEBUG] Running enrich automation for key %s in category %s", cacheData.Key, cacheData.Category)
|
|
}
|
|
|
|
//SetCache(ctx, cacheKey, []byte("1"), 1)
|
|
var timeout int32 = 5000
|
|
if project.Environment != "cloud" {
|
|
timeout = 60000
|
|
}
|
|
SetCache(ctx, cacheKey, []byte("1"), timeout, true)
|
|
if cacheData.Enrichments != nil && len(cacheData.Enrichments) > 0 {
|
|
}
|
|
|
|
// Send the data into shuffle_tools => parse_ioc?
|
|
// Or generate a workflow that runs for it? :thinking:
|
|
|
|
// Example process:
|
|
// 1. Ingest IOC (hash/IP/domain/alert) => inject into datastore category
|
|
// 2. Query reputation + passive DNS + WHOIS + SSL CT.
|
|
// 3. Run lookup in historic sightings (SIEM, MISP).
|
|
// 4. If file hash: submit to sandbox + static YARA.
|
|
// 5. Map results to ATT&CK techniques and assign a risk score.
|
|
// 6. Push enriched alert to SIEM/EDR/SOAR for automated playbook or analyst triage.
|
|
// 7. If high confidence, add to blocklists / trigger containment / share via STIX/TAXII or MISP.
|
|
|
|
// Getting started:
|
|
// 1. Check for enrichments key. Stop if it exists.
|
|
/*
|
|
types := []iocParser.IndicatorType{
|
|
iocParser.IPV4,
|
|
iocParser.URL_LINK,
|
|
iocParser.Domain,
|
|
iocParser.Email,
|
|
}
|
|
foundIocs := iocParser.Parse(string(marshalledBody), types)
|
|
log.Printf("RESP: %#v", foundIocs)
|
|
if len(foundIocs) == 0 {
|
|
log.Printf("[DEBUG] No IOCs found to enrich.")
|
|
return nil
|
|
}
|
|
|
|
log.Printf("[DEBUG] Found %d IOCs to enrich.", len(foundIocs))
|
|
for _, foundIoc := range foundIocs {
|
|
log.Printf("[DEBUG] Found IOC: %#v", foundIoc)
|
|
}
|
|
*/
|
|
|
|
if len(foundApikey) == 0 {
|
|
log.Printf("[ERROR] No admin user with API key found for org %s", cacheData.OrgId)
|
|
return errors.New("No admin user with API key found")
|
|
}
|
|
|
|
// Uses the same as the API /api/v*/workflows/generate
|
|
seedString := fmt.Sprintf("%s_Enable Threat feeds_webhook", cacheData.OrgId)
|
|
|
|
hash := sha1.New()
|
|
hash.Write([]byte(seedString))
|
|
hashBytes := hash.Sum(nil)
|
|
|
|
uuidBytes := make([]byte, 16)
|
|
copy(uuidBytes, hashBytes)
|
|
relevantWorkflowId := uuid.Must(uuid.FromBytes(uuidBytes)).String()
|
|
|
|
// FIXME: If workflow doesn't exist - generate it
|
|
fullUrl := fmt.Sprintf("%s/api/v1/workflows/%s/execute", backendUrl, relevantWorkflowId)
|
|
if debug {
|
|
log.Printf("[DEBUG] Running enrich automation workflow %s for key %s in category %s", relevantWorkflowId, cacheData.Key, cacheData.Category)
|
|
}
|
|
|
|
executionRequest := ExecutionRequest{
|
|
ExecutionArgument: string(marshalledBody),
|
|
ExecutionSource: fmt.Sprintf("datastore|%s|%s", cacheData.Category, cacheData.Key),
|
|
}
|
|
|
|
newParsedBody, err := json.Marshal(executionRequest)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to marshal body for enrichment workflow execution: %s", err)
|
|
return err
|
|
}
|
|
|
|
client := GetExternalClient(fullUrl)
|
|
req, err := http.NewRequest(
|
|
"POST",
|
|
fullUrl,
|
|
bytes.NewBuffer(newParsedBody),
|
|
)
|
|
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to create request for enrichment workflow execution: %s", err)
|
|
return err
|
|
}
|
|
|
|
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", foundApikey))
|
|
req.Header.Add("Org-Id", cacheData.OrgId)
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to send enrichment workflow execution request: %s", err)
|
|
return err
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed to read response body from enrichment workflow execution request: %s", err)
|
|
return err
|
|
}
|
|
|
|
if resp.StatusCode != 200 {
|
|
log.Printf("[ERROR] Enrichment workflow execution request failed with status code %d. Body: %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
if debug {
|
|
log.Printf("[DEBUG] RESP FOR RUNNING ENRICHMENT (%d): %s", resp.StatusCode, string(body))
|
|
}
|
|
|
|
} else if parsedName == "run_workflow" {
|
|
for _, option := range automation.Options {
|
|
if option.Key != "workflow_id" {
|
|
continue
|
|
}
|
|
|
|
if len(option.Value) == 0 {
|
|
continue
|
|
}
|
|
|
|
cacheData.WorkflowId = option.Value
|
|
workflowIds := strings.Split(option.Value, ",")
|
|
|
|
handled := []string{}
|
|
for _, workflowId := range workflowIds {
|
|
workflowId = strings.TrimSpace(workflowId)
|
|
if ArrayContains(handled, workflowId) {
|
|
continue
|
|
}
|
|
|
|
handled = append(handled, workflowId)
|
|
formattedBodyStruct := ExecutionRequest{
|
|
ExecutionSource: fmt.Sprintf("datastore_%s_%s", cacheData.Category, cacheData.Key),
|
|
ExecutionArgument: string(marshalledBody),
|
|
}
|
|
|
|
marshalledFormattedBody, err := json.Marshal(formattedBodyStruct)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed in marshalling data in 'run_workflow' datastore automation for workflow %s", workflowId)
|
|
} else {
|
|
marshalledBody = marshalledFormattedBody
|
|
}
|
|
|
|
go handleDatastoreAutomationWebhook(ctx, marshalledBody, cacheData, automation, fmt.Sprintf("/api/v1/workflows/%s/execute", workflowId), "run_workflow")
|
|
}
|
|
|
|
break
|
|
}
|
|
|
|
} else if parsedName == "send_webhook" {
|
|
if debug {
|
|
log.Printf("[DEBUG] Sending webhook for url %s", automation.Options[0].Value)
|
|
}
|
|
|
|
return handleDatastoreAutomationWebhook(ctx, marshalledBody, cacheData, automation, "/api/v1/apps/HTTP/run", "webhook")
|
|
|
|
// Send the webhook using the HTTP app with a POST request
|
|
|
|
} else {
|
|
return fmt.Errorf("Unknown automation name %s", automation.Name)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func PushTarStreamToRegistry(r io.Reader, refStr string) error {
|
|
return nil
|
|
}
|
|
|
|
// Primarily used for cross-region app propagation of public apps on first run
|
|
// Also used to rebuild apps when necessary IF buildApp = true is passed.
|
|
func LoadAppConfigFromMain(fileId string, buildApp bool) {
|
|
// Send request to /api/v1/apps/{fileId}/config
|
|
// Parse out the config and add it to the database
|
|
ctx := context.Background()
|
|
|
|
app, err := GetApp(ctx, fileId, User{}, false)
|
|
if err == nil && len(app.Name) > 0 && len(app.ID) > 0 {
|
|
log.Printf("[INFO] Found app %s (%s) for config loading. Running cross-region DOWNLOAD shuffler.io->local if it's generated==false (python). Actions: %d. Generated: %t", app.Name, app.ID, len(app.Actions), app.Generated)
|
|
//if len(app.Actions) > 0 {
|
|
// return
|
|
//}
|
|
|
|
// Python apps can't be distributed this easily (sadly)
|
|
if !app.Generated {
|
|
return
|
|
}
|
|
}
|
|
|
|
app.ID = fileId
|
|
|
|
backendHost := fmt.Sprintf("https://shuffler.io")
|
|
appApi := fmt.Sprintf("%s/api/v1/apps/%s/config", backendHost, fileId)
|
|
if buildApp {
|
|
if os.Getenv("BASE_URL") != "" {
|
|
backendHost = os.Getenv("BASE_URL")
|
|
}
|
|
|
|
if os.Getenv("SHUFFLE_CLOUDRUN_URL") != "" {
|
|
backendHost = os.Getenv("SHUFFLE_CLOUDRUN_URL")
|
|
}
|
|
|
|
// Check cache if it happened recently JUST IN CASE
|
|
// Done per region
|
|
appApi = fmt.Sprintf("%s/api/v1/apps/%s/config", backendHost, fileId)
|
|
_, err := GetCache(ctx, appApi)
|
|
if err == nil {
|
|
return
|
|
}
|
|
|
|
SetCache(ctx, appApi, []byte("1"), 60)
|
|
log.Printf("[WARNING] Auto-rebuilding app with ID %s. This is primarily when custom_action does not exist for generated apps (old apps)", appApi)
|
|
}
|
|
|
|
client := &http.Client{}
|
|
req, err := http.NewRequest(
|
|
"GET",
|
|
appApi,
|
|
nil,
|
|
)
|
|
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed creating request for app config: %s", err)
|
|
return
|
|
}
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed getting app config: %s", err)
|
|
return
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed reading app config: %s", err)
|
|
return
|
|
}
|
|
|
|
if resp.StatusCode != 200 {
|
|
log.Printf("[ERROR] Failed getting app config for ID %s: %d. Body: %s", fileId, resp.StatusCode, string(body))
|
|
return
|
|
}
|
|
|
|
newApp := AppParser{}
|
|
err = json.Unmarshal(body, &newApp)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed unmarshaling app config: %s", err)
|
|
return
|
|
}
|
|
|
|
//log.Printf("[INFO] Got app config: %s", string(body))
|
|
if !newApp.Success {
|
|
log.Printf("[ERROR] No success in app config for id %s", fileId)
|
|
return
|
|
}
|
|
|
|
if len(newApp.App) == 0 {
|
|
log.Printf("[ERROR] No app found for id %s", app.ID)
|
|
} else {
|
|
|
|
err = json.Unmarshal(newApp.App, &app)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed unmarshaling app for id %s: %s", app.ID, err)
|
|
return
|
|
}
|
|
|
|
err = SetWorkflowAppDatastore(ctx, *app, app.ID)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed saving app for id %s: %s", app.ID, err)
|
|
}
|
|
}
|
|
|
|
if len(newApp.OpenAPI) > 0 || buildApp {
|
|
// Save the data to the database with the ParsedOpenApi struct
|
|
parsedOpenApi := ParsedOpenApi{}
|
|
err = json.Unmarshal(newApp.OpenAPI, &parsedOpenApi)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed unmarshaling openapi for id %s: %s", app.ID, err)
|
|
return
|
|
}
|
|
|
|
err = SetOpenApiDatastore(ctx, parsedOpenApi.ID, parsedOpenApi)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed saving openapi for id %s: %s", app.ID, err)
|
|
}
|
|
|
|
// Run verify openapi here to make sure we send the correct request
|
|
actualOpenApi := &openapi3.Swagger{}
|
|
err = json.Unmarshal([]byte(parsedOpenApi.Body), &actualOpenApi)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Problem with openapi3 mapping pre rebuild for %s: %s", parsedOpenApi.ID, err)
|
|
}
|
|
|
|
parsedOpenApiBody, err := json.Marshal(actualOpenApi)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed unmarshalling %s", parsedOpenApi.ID)
|
|
return
|
|
}
|
|
|
|
if buildApp {
|
|
// Only partial part of it
|
|
//type Test struct {
|
|
// Editing bool `datastore:"editing"`
|
|
// Id string `datastore:"id"`
|
|
// Image string `datastore:"image"`
|
|
//}
|
|
|
|
// easiest way to force a rebuild by injecting 2 fields
|
|
parsedOpenApiBody = []byte(strings.TrimSuffix(string(parsedOpenApiBody), "}"))
|
|
parsedOpenApiBody = []byte(fmt.Sprintf(`%s, "id": "%s", "editing": true}`, string(parsedOpenApiBody), string(parsedOpenApi.ID)))
|
|
|
|
log.Printf("\n\n\nNew body: %s\n\n", string(parsedOpenApiBody))
|
|
}
|
|
|
|
baseurl := "http://localhost:5002"
|
|
if os.Getenv("BASE_URL") != "" {
|
|
baseurl = os.Getenv("BASE_URL")
|
|
}
|
|
|
|
if os.Getenv("SHUFFLE_CLOUDRUN_URL") != "" {
|
|
baseurl = os.Getenv("SHUFFLE_CLOUDRUN_URL")
|
|
}
|
|
|
|
fullUrl := fmt.Sprintf("%s/api/v1/verify_swagger", baseurl)
|
|
req, err := http.NewRequest(
|
|
"POST",
|
|
fullUrl,
|
|
bytes.NewBuffer(parsedOpenApiBody),
|
|
)
|
|
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed creating request for openapi verification: %s", err)
|
|
return
|
|
}
|
|
|
|
if len(os.Getenv("SHUFFLE_OPS_DASHBOARD_APIKEY")) > 0 {
|
|
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", os.Getenv("SHUFFLE_OPS_DASHBOARD_APIKEY")))
|
|
}
|
|
|
|
req.Header.Set("Content-Type", "application/json")
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed verifying openapi: %s", err)
|
|
return
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != 200 {
|
|
log.Printf("[ERROR] Failed building openapi for ID %s: %d", fileId, resp.StatusCode)
|
|
return
|
|
}
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
log.Printf("[ERROR] Failed reading openapi verification: %s", err)
|
|
return
|
|
}
|
|
|
|
log.Printf("[INFO] OpenAPI build: %s", string(body))
|
|
} else {
|
|
log.Printf("[ERROR] No openapi found for id %s", app.ID)
|
|
}
|
|
}
|