Major Refactoring, Client can now be used as a library
Some checks failed
Build (artifact) / build (push) Failing after 1m3s
Some checks failed
Build (artifact) / build (push) Failing after 1m3s
This commit is contained in:
37
app/executor/commands.go
Normal file
37
app/executor/commands.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
executorServer *Server
|
||||
|
||||
ExecutorCmd = &cobra.Command{
|
||||
Use: "executor",
|
||||
Short: "Privileged daemon",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return startExecutorCmd()
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func startExecutorCmd() error {
|
||||
executorServer = &Server{}
|
||||
|
||||
sigCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-sigCh
|
||||
executorServer.Stop()
|
||||
}()
|
||||
if err := executorServer.Start(); err != nil {
|
||||
return fmt.Errorf("failed to start executor server: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
43
app/executor/executor.go
Normal file
43
app/executor/executor.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
|
||||
pb "git.nevets.tech/Keys/certman/proto/v1"
|
||||
"github.com/coreos/go-systemd/v22/activation"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
listener net.Listener
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
func (s *Server) Start() error {
|
||||
listeners, err := activation.Listeners()
|
||||
if err != nil {
|
||||
return fmt.Errorf("systemd activation listeners: %v", err)
|
||||
}
|
||||
if len(listeners) != 1 {
|
||||
return fmt.Errorf("systemd activation listeners: expected 1, got %d", len(listeners))
|
||||
}
|
||||
|
||||
s.listener = listeners[0]
|
||||
srv := grpc.NewServer()
|
||||
pb.RegisterHookServiceServer(srv, &hookServer{})
|
||||
|
||||
err = srv.Serve(s.listener)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating grpc listener: %v", err)
|
||||
}
|
||||
fmt.Printf("Started gRPC server on %s\n", s.listener.Addr())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) Stop() {
|
||||
if s.listener != nil {
|
||||
_ = s.listener.Close()
|
||||
}
|
||||
}
|
||||
73
app/executor/hook.go
Normal file
73
app/executor/hook.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package executor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"git.nevets.tech/Keys/certman/common"
|
||||
pb "git.nevets.tech/Keys/certman/proto/v1"
|
||||
)
|
||||
|
||||
type hookServer struct {
|
||||
pb.UnimplementedHookServiceServer
|
||||
}
|
||||
|
||||
func (s *hookServer) ExecuteHook(ctx context.Context, req *pb.ExecuteHookRequest) (*pb.ExecuteHookResponse, error) {
|
||||
h := req.GetHook()
|
||||
if h == nil {
|
||||
return &pb.ExecuteHookResponse{Error: "missing hook"}, nil
|
||||
}
|
||||
|
||||
// Minimal validation
|
||||
if len(h.GetCommand()) == 0 {
|
||||
return &pb.ExecuteHookResponse{Error: "command is empty"}, nil
|
||||
}
|
||||
|
||||
// Timeout
|
||||
timeout := time.Duration(h.GetTimeoutSeconds()) * time.Second
|
||||
if timeout <= 0 {
|
||||
timeout = 30 * time.Second
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(ctx, timeout)
|
||||
defer cancel()
|
||||
|
||||
// Build command
|
||||
cmdArgs := h.GetCommand()
|
||||
cmd := exec.CommandContext(ctx, cmdArgs[0], cmdArgs[1:]...)
|
||||
if cwd := h.GetCwd(); cwd != "" {
|
||||
cmd.Dir = cwd
|
||||
}
|
||||
|
||||
// Env: inherit current + overlay provided
|
||||
env := os.Environ()
|
||||
for k, v := range h.GetEnv() {
|
||||
env = append(env, k+"="+v)
|
||||
}
|
||||
cmd.Env = env
|
||||
|
||||
// Run as user/group if specified (Linux/Unix)
|
||||
if h.GetUser() != "" || h.GetGroup() != "" {
|
||||
cred, err := common.MakeCredential(h.GetUser(), h.GetGroup())
|
||||
if err != nil {
|
||||
return &pb.ExecuteHookResponse{Error: brief(err)}, nil
|
||||
}
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Credential: cred,
|
||||
}
|
||||
}
|
||||
|
||||
// We’re intentionally NOT returning stdout/stderr; only a brief error on failure.
|
||||
if err := cmd.Run(); err != nil {
|
||||
// If context deadline hit, make the error message short and explicit.
|
||||
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
||||
return &pb.ExecuteHookResponse{Error: "hook timed out"}, nil
|
||||
}
|
||||
return &pb.ExecuteHookResponse{Error: brief(err)}, nil
|
||||
}
|
||||
|
||||
return &pb.ExecuteHookResponse{Error: ""}, nil
|
||||
}
|
||||
8
app/executor/util.go
Normal file
8
app/executor/util.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package executor
|
||||
|
||||
import "fmt"
|
||||
|
||||
// brief tries to keep errors short and non-leaky.
|
||||
func brief(err error) string {
|
||||
return fmt.Sprintf("hook failed: %v", err)
|
||||
}
|
||||
Reference in New Issue
Block a user