/
executor.go
96 lines (84 loc) · 2.69 KB
/
executor.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package executor
import (
"errors"
"fmt"
"net/url"
"strings"
"time"
)
const (
flagQueryTimeout = "timeout"
)
var (
ErrExecutionimeout = errors.New("execution timeout")
ErrRestNotOk = errors.New("rest return non 2XX response")
)
type ExecResult struct {
Output []byte
Code uint32
Version string
}
type Executor interface {
Exec(exec []byte, arg string, env interface{}) (ExecResult, error)
}
var testProgram []byte = []byte(
"#!/usr/bin/env python3\nimport os\nimport sys\nprint(sys.argv[1], os.getenv('BAND_CHAIN_ID'))",
)
// NewExecutor returns executor by name and executor URL
func NewExecutor(executor string) (exec Executor, err error) {
name, base, timeout, err := parseExecutor(executor)
if err != nil {
return nil, err
}
switch name {
case "rest":
exec = NewRestExec(base, timeout)
case "docker":
return nil, fmt.Errorf("docker executor is currently not supported")
default:
return nil, fmt.Errorf("invalid executor name: %s, base: %s", name, base)
}
// TODO: Remove hardcode in test execution
res, err := exec.Exec(testProgram, "TEST_ARG", map[string]interface{}{
"BAND_CHAIN_ID": "test-chain-id",
"BAND_VALIDATOR": "test-validator",
"BAND_REQUEST_ID": "test-request-id",
"BAND_EXTERNAL_ID": "test-external-id",
"BAND_REPORTER": "test-reporter",
"BAND_SIGNATURE": "test-signature",
})
if err != nil {
return nil, fmt.Errorf("failed to run test program: %s", err.Error())
}
if res.Code != 0 {
return nil, fmt.Errorf("test program returned nonzero code: %d", res.Code)
}
if string(res.Output) != "TEST_ARG test-chain-id\n" {
return nil, fmt.Errorf("test program returned wrong output: %s", res.Output)
}
return exec, nil
}
// parseExecutor splits the executor string in the form of "name:base?timeout=" into parts.
func parseExecutor(executorStr string) (name string, base string, timeout time.Duration, err error) {
executor := strings.SplitN(executorStr, ":", 2)
if len(executor) != 2 {
return "", "", 0, fmt.Errorf("invalid executor, cannot parse executor: %s", executorStr)
}
u, err := url.Parse(executor[1])
if err != nil {
return "", "", 0, fmt.Errorf("invalid url, cannot parse %s to url with error: %s", executor[1], err.Error())
}
query := u.Query()
timeoutStr := query.Get(flagQueryTimeout)
if timeoutStr == "" {
return "", "", 0, fmt.Errorf("invalid timeout, executor requires query timeout")
}
// Remove timeout from query because we need to return `base`
query.Del(flagQueryTimeout)
u.RawQuery = query.Encode()
timeout, err = time.ParseDuration(timeoutStr)
if err != nil {
return "", "", 0, fmt.Errorf("invalid timeout, cannot parse duration with error: %s", err.Error())
}
return executor[0], u.String(), timeout, nil
}