feature: Additional APIs for StartAction (#188)
This commit is contained in:
parent
ca921c1890
commit
d0f74c1ab7
|
|
@ -60,6 +60,30 @@ message StartActionResponse {
|
||||||
string execution_uuid = 2;
|
string execution_uuid = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message StartActionAndWaitRequest {
|
||||||
|
string action_name = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StartActionAndWaitResponse {
|
||||||
|
LogEntry log_entry = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StartActionByAliasRequest {
|
||||||
|
string action_alias = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StartActionByAliasResponse {
|
||||||
|
string execution_uuid = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StartActionByAliasAndWaitRequest {
|
||||||
|
string action_alias = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StartActionByAliasAndWaitResponse {
|
||||||
|
LogEntry log_entry = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message GetLogsRequest{};
|
message GetLogsRequest{};
|
||||||
|
|
||||||
message LogEntry {
|
message LogEntry {
|
||||||
|
|
@ -137,6 +161,27 @@ service OliveTinApiService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rpc StartActionAndWait(StartActionAndWaitRequest) returns (StartActionAndWaitResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/api/StartActionAndWait"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc StartActionByAlias(StartActionByAliasRequest) returns (StartActionByAliasResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/api/StartActionByAlias/{action_alias}"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc StartActionByAliasAndWait(StartActionByAliasAndWaitRequest) returns (StartActionByAliasAndWaitResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/api/StartActionByAliasAndWait/{action_alias}"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
rpc ExecutionStatus(ExecutionStatusRequest) returns (ExecutionStatusResponse) {
|
rpc ExecutionStatus(ExecutionStatusRequest) returns (ExecutionStatusResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/api/ExecutionStatus"
|
post: "/api/ExecutionStatus"
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ actions:
|
||||||
# Restart lightdm on host "server1"
|
# Restart lightdm on host "server1"
|
||||||
# Docs: https://docs.olivetin.app/action-ping.html
|
# Docs: https://docs.olivetin.app/action-ping.html
|
||||||
- title: restart httpd
|
- title: restart httpd
|
||||||
|
titleAlias: restart_httpd
|
||||||
icon: restart
|
icon: restart
|
||||||
shell: ssh root@server1 'service httpd restart'
|
shell: ssh root@server1 'service httpd restart'
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ package config
|
||||||
type Action struct {
|
type Action struct {
|
||||||
ID string
|
ID string
|
||||||
Title string
|
Title string
|
||||||
|
TitleAlias string
|
||||||
Icon string
|
Icon string
|
||||||
Shell string
|
Shell string
|
||||||
ShellAfterCompleted string
|
ShellAfterCompleted string
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@ type Executor struct {
|
||||||
// Executor. They're created from the grpcapi.
|
// Executor. They're created from the grpcapi.
|
||||||
type ExecutionRequest struct {
|
type ExecutionRequest struct {
|
||||||
ActionName string
|
ActionName string
|
||||||
|
Action *config.Action
|
||||||
Arguments map[string]string
|
Arguments map[string]string
|
||||||
UUID string
|
UUID string
|
||||||
Tags []string
|
Tags []string
|
||||||
action *config.Action
|
|
||||||
Cfg *config.Config
|
Cfg *config.Config
|
||||||
AuthenticatedUser *acl.AuthenticatedUser
|
AuthenticatedUser *acl.AuthenticatedUser
|
||||||
logEntry *InternalLogEntry
|
logEntry *InternalLogEntry
|
||||||
|
|
@ -165,8 +165,8 @@ func stepConcurrencyCheck(req *ExecutionRequest) bool {
|
||||||
concurrentCount := getConcurrentCount(req)
|
concurrentCount := getConcurrentCount(req)
|
||||||
|
|
||||||
// Note that the current execution is counted int the logs, so when checking we +1
|
// Note that the current execution is counted int the logs, so when checking we +1
|
||||||
if concurrentCount >= (req.action.MaxConcurrent + 1) {
|
if concurrentCount >= (req.Action.MaxConcurrent + 1) {
|
||||||
msg := fmt.Sprintf("Blocked from executing. This would mean this action is running %d times concurrently, but this action has maxExecutions set to %d.", concurrentCount, req.action.MaxConcurrent)
|
msg := fmt.Sprintf("Blocked from executing. This would mean this action is running %d times concurrently, but this action has maxExecutions set to %d.", concurrentCount, req.Action.MaxConcurrent)
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"actionTitle": req.ActionName,
|
"actionTitle": req.ActionName,
|
||||||
|
|
@ -181,6 +181,10 @@ func stepConcurrencyCheck(req *ExecutionRequest) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func stepFindAction(req *ExecutionRequest) bool {
|
func stepFindAction(req *ExecutionRequest) bool {
|
||||||
|
if req.Action != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
actualAction := req.Cfg.FindAction(req.ActionName)
|
actualAction := req.Cfg.FindAction(req.ActionName)
|
||||||
|
|
||||||
if actualAction == nil {
|
if actualAction == nil {
|
||||||
|
|
@ -193,20 +197,20 @@ func stepFindAction(req *ExecutionRequest) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
req.action = actualAction
|
req.Action = actualAction
|
||||||
req.logEntry.ActionIcon = actualAction.Icon
|
req.logEntry.ActionIcon = actualAction.Icon
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func stepACLCheck(req *ExecutionRequest) bool {
|
func stepACLCheck(req *ExecutionRequest) bool {
|
||||||
return acl.IsAllowedExec(req.Cfg, req.AuthenticatedUser, req.action)
|
return acl.IsAllowedExec(req.Cfg, req.AuthenticatedUser, req.Action)
|
||||||
}
|
}
|
||||||
|
|
||||||
func stepParseArgs(req *ExecutionRequest) bool {
|
func stepParseArgs(req *ExecutionRequest) bool {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
req.finalParsedCommand, err = parseActionArguments(req.action.Shell, req.Arguments, req.action)
|
req.finalParsedCommand, err = parseActionArguments(req.Action.Shell, req.Arguments, req.Action)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
req.logEntry.Stdout = err.Error()
|
req.logEntry.Stdout = err.Error()
|
||||||
|
|
@ -229,8 +233,8 @@ func stepLogRequested(req *ExecutionRequest) bool {
|
||||||
|
|
||||||
func stepLogStart(req *ExecutionRequest) bool {
|
func stepLogStart(req *ExecutionRequest) bool {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"actionTitle": req.action.Title,
|
"actionTitle": req.Action.Title,
|
||||||
"timeout": req.action.Timeout,
|
"timeout": req.Action.Timeout,
|
||||||
}).Infof("Action starting")
|
}).Infof("Action starting")
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
@ -238,7 +242,7 @@ func stepLogStart(req *ExecutionRequest) bool {
|
||||||
|
|
||||||
func stepLogFinish(req *ExecutionRequest) bool {
|
func stepLogFinish(req *ExecutionRequest) bool {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"actionTitle": req.action.Title,
|
"actionTitle": req.Action.Title,
|
||||||
"stdout": req.logEntry.Stdout,
|
"stdout": req.logEntry.Stdout,
|
||||||
"stderr": req.logEntry.Stderr,
|
"stderr": req.logEntry.Stderr,
|
||||||
"timedOut": req.logEntry.TimedOut,
|
"timedOut": req.logEntry.TimedOut,
|
||||||
|
|
@ -263,7 +267,7 @@ func wrapCommandInShell(ctx context.Context, finalParsedCommand string) *exec.Cm
|
||||||
}
|
}
|
||||||
|
|
||||||
func stepExec(req *ExecutionRequest) bool {
|
func stepExec(req *ExecutionRequest) bool {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(req.action.Timeout)*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(req.Action.Timeout)*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
|
|
@ -303,11 +307,11 @@ func stepExec(req *ExecutionRequest) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func stepExecAfter(req *ExecutionRequest) bool {
|
func stepExecAfter(req *ExecutionRequest) bool {
|
||||||
if req.action.ShellAfterCompleted == "" {
|
if req.Action.ShellAfterCompleted == "" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(req.action.Timeout)*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(req.Action.Timeout)*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
var stdout bytes.Buffer
|
var stdout bytes.Buffer
|
||||||
|
|
@ -318,7 +322,7 @@ func stepExecAfter(req *ExecutionRequest) bool {
|
||||||
"exitCode": fmt.Sprintf("%v", req.logEntry.ExitCode),
|
"exitCode": fmt.Sprintf("%v", req.logEntry.ExitCode),
|
||||||
}
|
}
|
||||||
|
|
||||||
finalParsedCommand, _ := parseActionArguments(req.action.ShellAfterCompleted, args, req.action)
|
finalParsedCommand, _ := parseActionArguments(req.Action.ShellAfterCompleted, args, req.Action)
|
||||||
|
|
||||||
cmd := wrapCommandInShell(ctx, finalParsedCommand)
|
cmd := wrapCommandInShell(ctx, finalParsedCommand)
|
||||||
cmd.Stdout = &stdout
|
cmd.Stdout = &stdout
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@ package grpcapi
|
||||||
import (
|
import (
|
||||||
ctx "context"
|
ctx "context"
|
||||||
pb "github.com/OliveTin/OliveTin/gen/grpc"
|
pb "github.com/OliveTin/OliveTin/gen/grpc"
|
||||||
|
"github.com/google/uuid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
|
@ -20,7 +22,8 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
type oliveTinAPI struct {
|
type oliveTinAPI struct {
|
||||||
pb.UnimplementedOliveTinApiServiceServer
|
// Uncomment this if you want to allow undefined methods during dev.
|
||||||
|
// pb.UnimplementedOliveTinApiServiceServer
|
||||||
|
|
||||||
executor *executor.Executor
|
executor *executor.Executor
|
||||||
}
|
}
|
||||||
|
|
@ -28,8 +31,6 @@ type oliveTinAPI struct {
|
||||||
func (api *oliveTinAPI) StartAction(ctx ctx.Context, req *pb.StartActionRequest) (*pb.StartActionResponse, error) {
|
func (api *oliveTinAPI) StartAction(ctx ctx.Context, req *pb.StartActionRequest) (*pb.StartActionResponse, error) {
|
||||||
args := make(map[string]string)
|
args := make(map[string]string)
|
||||||
|
|
||||||
log.Debugf("SA %v", req)
|
|
||||||
|
|
||||||
for _, arg := range req.Arguments {
|
for _, arg := range req.Arguments {
|
||||||
args[arg.Name] = arg.Value
|
args[arg.Name] = arg.Value
|
||||||
}
|
}
|
||||||
|
|
@ -47,19 +48,103 @@ func (api *oliveTinAPI) StartAction(ctx ctx.Context, req *pb.StartActionRequest)
|
||||||
return &pb.StartActionResponse{
|
return &pb.StartActionResponse{
|
||||||
ExecutionUuid: uuid,
|
ExecutionUuid: uuid,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *oliveTinAPI) ExecutionStatus(ctx ctx.Context, req *pb.ExecutionStatusRequest) (*pb.ExecutionStatusResponse, error) {
|
func (api *oliveTinAPI) StartActionAndWait(ctx ctx.Context, req *pb.StartActionAndWaitRequest) (*pb.StartActionAndWaitResponse, error) {
|
||||||
res := &pb.ExecutionStatusResponse{}
|
args := make(map[string]string)
|
||||||
|
|
||||||
logEntry, ok := api.executor.Logs[req.ExecutionUuid]
|
execReq := executor.ExecutionRequest{
|
||||||
|
ActionName: req.ActionName,
|
||||||
if !ok {
|
UUID: uuid.NewString(),
|
||||||
return res, nil
|
Arguments: args,
|
||||||
|
AuthenticatedUser: acl.UserFromContext(ctx, cfg),
|
||||||
|
Cfg: cfg,
|
||||||
}
|
}
|
||||||
|
|
||||||
res.LogEntry = &pb.LogEntry{
|
wg, _ := api.executor.ExecRequest(&execReq)
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
internalLogEntry, ok := api.executor.Logs[execReq.UUID]
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
return &pb.StartActionAndWaitResponse{
|
||||||
|
LogEntry: internalLogEntryToPb(internalLogEntry),
|
||||||
|
}, nil
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("Execution not found!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *oliveTinAPI) StartActionByAlias(ctx ctx.Context, req *pb.StartActionByAliasRequest) (*pb.StartActionByAliasResponse, error) {
|
||||||
|
args := make(map[string]string)
|
||||||
|
|
||||||
|
action := findActionByAlias(req.ActionAlias)
|
||||||
|
|
||||||
|
if action == nil {
|
||||||
|
log.Warnf("ByAlias action alias not found: %v, cannot start execution.", req.ActionAlias)
|
||||||
|
return &pb.StartActionByAliasResponse{
|
||||||
|
ExecutionUuid: "",
|
||||||
|
}, errors.New("ByAlias action alias not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
execReq := executor.ExecutionRequest{
|
||||||
|
ActionName: action.Title,
|
||||||
|
Action: action,
|
||||||
|
UUID: uuid.NewString(),
|
||||||
|
Arguments: args,
|
||||||
|
AuthenticatedUser: &acl.AuthenticatedUser{
|
||||||
|
Username: "webhook",
|
||||||
|
Usergroup: "webhook",
|
||||||
|
},
|
||||||
|
Cfg: cfg,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, uuid := api.executor.ExecRequest(&execReq)
|
||||||
|
|
||||||
|
return &pb.StartActionByAliasResponse{
|
||||||
|
ExecutionUuid: uuid,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *oliveTinAPI) StartActionByAliasAndWait(ctx ctx.Context, req *pb.StartActionByAliasAndWaitRequest) (*pb.StartActionByAliasAndWaitResponse, error) {
|
||||||
|
args := make(map[string]string)
|
||||||
|
|
||||||
|
action := findActionByAlias(req.ActionAlias)
|
||||||
|
|
||||||
|
if action == nil {
|
||||||
|
log.Warnf("ByAlias action alias not found: %v, cannot start execution.", req.ActionAlias)
|
||||||
|
|
||||||
|
return &pb.StartActionByAliasAndWaitResponse{}, errors.New("ByAlias action alias not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
execReq := executor.ExecutionRequest{
|
||||||
|
ActionName: action.Title,
|
||||||
|
Action: action,
|
||||||
|
UUID: uuid.NewString(),
|
||||||
|
Arguments: args,
|
||||||
|
AuthenticatedUser: &acl.AuthenticatedUser{
|
||||||
|
Username: "webhook",
|
||||||
|
Usergroup: "webhook",
|
||||||
|
},
|
||||||
|
Cfg: cfg,
|
||||||
|
}
|
||||||
|
|
||||||
|
wg, _ := api.executor.ExecRequest(&execReq)
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
internalLogEntry, ok := api.executor.Logs[execReq.UUID]
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
return &pb.StartActionByAliasAndWaitResponse{
|
||||||
|
LogEntry: internalLogEntryToPb(internalLogEntry),
|
||||||
|
}, nil
|
||||||
|
} else {
|
||||||
|
return nil, errors.New("Execution not found!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func internalLogEntryToPb(logEntry *executor.InternalLogEntry) *pb.LogEntry {
|
||||||
|
return &pb.LogEntry{
|
||||||
ActionTitle: logEntry.ActionTitle,
|
ActionTitle: logEntry.ActionTitle,
|
||||||
ActionIcon: logEntry.ActionIcon,
|
ActionIcon: logEntry.ActionIcon,
|
||||||
DatetimeStarted: logEntry.DatetimeStarted,
|
DatetimeStarted: logEntry.DatetimeStarted,
|
||||||
|
|
@ -74,6 +159,18 @@ func (api *oliveTinAPI) ExecutionStatus(ctx ctx.Context, req *pb.ExecutionStatus
|
||||||
ExecutionStarted: logEntry.ExecutionStarted,
|
ExecutionStarted: logEntry.ExecutionStarted,
|
||||||
ExecutionFinished: logEntry.ExecutionFinished,
|
ExecutionFinished: logEntry.ExecutionFinished,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *oliveTinAPI) ExecutionStatus(ctx ctx.Context, req *pb.ExecutionStatusRequest) (*pb.ExecutionStatusResponse, error) {
|
||||||
|
res := &pb.ExecutionStatusResponse{}
|
||||||
|
|
||||||
|
logEntry, ok := api.executor.Logs[req.ExecutionUuid]
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res.LogEntry = internalLogEntryToPb(logEntry)
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
@ -126,21 +223,10 @@ func (api *oliveTinAPI) GetLogs(ctx ctx.Context, req *pb.GetLogsRequest) (*pb.Ge
|
||||||
// TODO Limit to 10 entries or something to prevent browser lag.
|
// TODO Limit to 10 entries or something to prevent browser lag.
|
||||||
|
|
||||||
for uuid, logEntry := range api.executor.Logs {
|
for uuid, logEntry := range api.executor.Logs {
|
||||||
ret.Logs = append(ret.Logs, &pb.LogEntry{
|
pbLogEntry := internalLogEntryToPb(logEntry)
|
||||||
ActionTitle: logEntry.ActionTitle,
|
pbLogEntry.ExecutionUuid = uuid
|
||||||
ActionIcon: logEntry.ActionIcon,
|
|
||||||
DatetimeStarted: logEntry.DatetimeStarted,
|
ret.Logs = append(ret.Logs, pbLogEntry)
|
||||||
DatetimeFinished: logEntry.DatetimeFinished,
|
|
||||||
Stdout: logEntry.Stdout,
|
|
||||||
Stderr: logEntry.Stderr,
|
|
||||||
TimedOut: logEntry.TimedOut,
|
|
||||||
Blocked: logEntry.Blocked,
|
|
||||||
ExitCode: logEntry.ExitCode,
|
|
||||||
Tags: logEntry.Tags,
|
|
||||||
ExecutionUuid: uuid,
|
|
||||||
ExecutionStarted: logEntry.ExecutionStarted,
|
|
||||||
ExecutionFinished: logEntry.ExecutionFinished,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sorter := func(i, j int) bool {
|
sorter := func(i, j int) bool {
|
||||||
|
|
|
||||||
|
|
@ -62,3 +62,15 @@ func buildChoices(choices []config.ActionArgumentChoice) []*pb.ActionArgumentCho
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findActionByAlias(alias string) *config.Action {
|
||||||
|
for _, action := range cfg.Actions {
|
||||||
|
if action.TitleAlias != "" {
|
||||||
|
if action.TitleAlias == alias {
|
||||||
|
return &action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue