feat: persist and transfer script command payloads
This commit is contained in:
+78
-11
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user