feat: persist and transfer script command payloads

This commit is contained in:
2026-09-06 11:11:36 +00:00
parent 26ac4c1cac
commit 3f84d3b2f1
18 changed files with 1155 additions and 31 deletions
+78 -11
View File
@@ -40,9 +40,16 @@ func run(args []string, output, diagnostics io.Writer) error {
if err != nil {
return err
}
requestID, args, err := globalRequestID(args)
if err != nil {
return err
}
if len(args) == 0 {
return errors.New("a command is required (stat or run)")
}
if requestID != "" && !readOnlyCommand(args) {
args = append([]string{args[0], "--request-id", requestID}, args[1:]...)
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
connection, err := dial(ctx, socket)
@@ -95,6 +102,44 @@ func globalSocket(args []string) (string, []string, error) {
return socket, remaining, nil
}
// globalRequestID removes the optional request ID wherever the CLI's global
// option parser finds it. It is injected before the subcommand's positional
// arguments so Go's flag package cannot mistake it for command text. Read-only
// commands intentionally discard the value in run().
func globalRequestID(args []string) (string, []string, error) {
var requestID string
remaining := make([]string, 0, len(args))
for index := 0; index < len(args); index++ {
if args[index] == "--request-id" {
if index+1 >= len(args) {
return "", nil, errors.New("--request-id requires a UUIDv7")
}
if requestID != "" {
return "", nil, errors.New("--request-id may be supplied only once")
}
requestID = args[index+1]
index++
continue
}
if strings.HasPrefix(args[index], "--request-id=") {
if requestID != "" {
return "", nil, errors.New("--request-id may be supplied only once")
}
requestID = strings.TrimPrefix(args[index], "--request-id=")
continue
}
remaining = append(remaining, args[index])
}
return requestID, remaining, nil
}
func readOnlyCommand(args []string) bool {
if len(args) == 0 || args[0] == "stat" {
return true
}
return args[0] == "storage" && len(args) > 1 && args[1] == "incidents"
}
func dial(ctx context.Context, socket string) (*grpc.ClientConn, error) {
dialer := func(ctx context.Context, _ string) (net.Conn, error) {
return (&net.Dialer{}).DialContext(ctx, "unix", socket)
@@ -165,6 +210,7 @@ func runCommand(ctx context.Context, client rvboxv1.ControlClient, args []string
cwd := flags.String("cwd", "", "command working directory")
shell := flags.String("shell", "", "shell (sh, bash, cmd, powershell)")
requestID := flags.String("request-id", "", "canonical UUIDv7 used for idempotent admission")
elevated := flags.Bool("elevated", false, "request the Windows elevated execution policy")
queueTTL := flags.Duration("queue-ttl", -1, "queue TTL; zero means no expiry")
scriptPath := flags.String("script", "", "script file path")
envValues := repeatedFlag{}
@@ -188,7 +234,7 @@ func runCommand(ctx context.Context, client rvboxv1.ControlClient, args []string
if _, err := domain.ParseUUIDv7(*requestID); err != nil {
return fmt.Errorf("--request-id: %w", err)
}
spec := &rvboxv1.ExecutionSpec{Cwd: *cwd}
spec := &rvboxv1.ExecutionSpec{Cwd: *cwd, Elevated: *elevated}
spec.ShellType, _ = parseShell(*shell)
for _, value := range envValues {
parts := strings.SplitN(value, "=", 2)
@@ -383,12 +429,20 @@ func killCommand(ctx context.Context, client rvboxv1.ControlClient, args []strin
}
func storage(ctx context.Context, client rvboxv1.ControlClient, args []string, output io.Writer) error {
flags := flag.NewFlagSet("storage", flag.ContinueOnError)
flags.SetOutput(io.Discard)
requestID := flags.String("request-id", "", "canonical UUIDv7")
includeResolved := flags.Bool("all", false, "include resolved incidents")
if err := flags.Parse(args); err != nil {
return err
}
args = flags.Args()
if len(args) == 0 {
return errors.New("storage requires incidents, repair, or acknowledge")
}
switch args[0] {
case "incidents":
response, err := client.ListStorageIncidents(ctx, &rvboxv1.ListStorageIncidentsRequest{IncludeResolved: contains(args[1:], "--all")})
response, err := client.ListStorageIncidents(ctx, &rvboxv1.ListStorageIncidentsRequest{IncludeResolved: *includeResolved})
if err != nil {
return err
}
@@ -400,12 +454,15 @@ func storage(ctx context.Context, client rvboxv1.ControlClient, args []string, o
if len(args) < 2 {
return errors.New("storage mutation requires INCIDENT_ID")
}
requestID, err := generatedRequestID()
if err != nil {
return err
if *requestID == "" {
var err error
*requestID, err = generatedRequestID()
if err != nil {
return err
}
}
if args[0] == "repair" {
response, callErr := client.RepairStorageIncident(ctx, &rvboxv1.RepairStorageIncidentRequest{IncidentId: args[1], RequestId: requestID})
response, callErr := client.RepairStorageIncident(ctx, &rvboxv1.RepairStorageIncidentRequest{IncidentId: args[1], RequestId: *requestID})
if callErr != nil {
return callErr
}
@@ -416,7 +473,7 @@ func storage(ctx context.Context, client rvboxv1.ControlClient, args []string, o
if note == "" {
return errors.New("storage acknowledge requires a note")
}
response, callErr := client.AcknowledgeStorageIncident(ctx, &rvboxv1.AcknowledgeStorageIncidentRequest{IncidentId: args[1], RequestId: requestID, Note: note})
response, callErr := client.AcknowledgeStorageIncident(ctx, &rvboxv1.AcknowledgeStorageIncidentRequest{IncidentId: args[1], RequestId: *requestID, Note: note})
if callErr != nil {
return callErr
}
@@ -428,17 +485,27 @@ func storage(ctx context.Context, client rvboxv1.ControlClient, args []string, o
}
func clientCommand(ctx context.Context, client rvboxv1.ControlClient, args []string, output io.Writer) error {
flags := flag.NewFlagSet("client", flag.ContinueOnError)
flags.SetOutput(io.Discard)
requestID := flags.String("request-id", "", "canonical UUIDv7")
if err := flags.Parse(args); err != nil {
return err
}
args = flags.Args()
if len(args) != 3 || args[0] != "takeover" {
return errors.New("client takeover requires CLIENT INSTANCE_ID")
}
if _, err := domain.ParseUUIDv7(args[2]); err != nil {
return err
}
requestID, err := generatedRequestID()
if err != nil {
return err
if *requestID == "" {
var err error
*requestID, err = generatedRequestID()
if err != nil {
return err
}
}
response, err := client.AuthorizeClientTakeover(ctx, &rvboxv1.AuthorizeClientTakeoverRequest{ClientId: args[1], ClientInstanceId: args[2], RequestId: requestID})
response, err := client.AuthorizeClientTakeover(ctx, &rvboxv1.AuthorizeClientTakeoverRequest{ClientId: args[1], ClientInstanceId: args[2], RequestId: *requestID})
if err != nil {
return err
}
+15
View File
@@ -32,3 +32,18 @@ func TestGlobalSocketAndCLIValueParsing_HP_CTL_11(t *testing.T) {
t.Fatalf("zero queue duration = %v", got)
}
}
func TestGlobalRequestIDIsInjectedOnlyForMutations_HP_CTL_12(t *testing.T) {
t.Parallel()
id := "019c46f1-1d02-7000-8000-0000000000f1"
requestID, remaining, err := globalRequestID([]string{"--request-id", id, "run", "client", "echo"})
if err != nil || requestID != id || len(remaining) != 3 || remaining[0] != "run" {
t.Fatalf("global request ID = %q %#v %v", requestID, remaining, err)
}
if !readOnlyCommand([]string{"stat", "client"}) || !readOnlyCommand([]string{"storage", "incidents"}) || readOnlyCommand([]string{"run", "client", "echo"}) {
t.Fatal("read-only command classification is incorrect")
}
if _, _, err := globalRequestID([]string{"--request-id", id, "--request-id", id}); err == nil {
t.Fatal("duplicate global request IDs accepted")
}
}