@@ -910,6 +910,7 @@ func (a *App) runRestish(ctx context.Context, service *agent.Service, client *ca
910910 if err != nil {
911911 return err
912912 }
913+ var genericOperation * restish.OperationInspection
913914 if server , ok := selectedResourceServer (servers , args ); ok {
914915 profile := "default"
915916 inspection , inspectErr := runtime .InspectAPI (ctx , server .CommandName , profile )
@@ -949,6 +950,7 @@ func (a *App) runRestish(ctx context.Context, service *agent.Service, client *ca
949950 if ! operationSelected {
950951 return fmt .Errorf ("%s %s does not match one published operation for Resource Server %q" , strings .ToUpper (args [0 ]), args [1 ], server .CommandName )
951952 }
953+ genericOperation = & operation
952954 if operationRequiresAuthority (operation ) {
953955 details , detailsErr := client .AuthorizationDetails (ctx , server )
954956 if detailsErr != nil {
@@ -972,19 +974,63 @@ func (a *App) runRestish(ctx context.Context, service *agent.Service, client *ca
972974 }
973975 }
974976 argvArgs := append ([]string (nil ), args ... )
977+ if genericOperation != nil {
978+ argvArgs , err = prepareGenericIdempotency (* genericOperation , argvArgs )
979+ if err != nil {
980+ return err
981+ }
982+ }
975983 if ! hasRuntimeFlag (argvArgs , "--rsh-print" ) {
976984 argvArgs = append (argvArgs , "--rsh-print" , "b" )
977985 }
978986 if a .json {
979987 argvArgs = append (argvArgs , "--rsh-output-format" , "json" )
980988 }
981989 argv := append ([]string {"realmroot toolbox" }, argvArgs ... )
982- if err := runtime .Run (argv ); err != nil {
990+ runOptions := restish.RunOptions {}
991+ if genericOperation != nil {
992+ runOptions .IdempotencyProtected = genericOperation .RequiresIdempotencyKey
993+ }
994+ if err := runtime .RunWithOptions (argv , runOptions ); err != nil {
983995 return toolboxRuntimeError {cause : err }
984996 }
985997 return nil
986998}
987999
1000+ func prepareGenericIdempotency (operation restish.OperationInspection , args []string ) ([]string , error ) {
1001+ prepared := append ([]string (nil ), args ... )
1002+ if ! operation .RequiresIdempotencyKey {
1003+ return prepared , nil
1004+ }
1005+ if ! hasHeader (prepared , "Idempotency-Key" ) {
1006+ key , err := restish .NewIdempotencyKey ()
1007+ if err != nil {
1008+ return nil , err
1009+ }
1010+ prepared = append (prepared , "--rsh-header" , "Idempotency-Key: " + key )
1011+ }
1012+ return prepared , nil
1013+ }
1014+
1015+ func hasHeader (args []string , name string ) bool {
1016+ for index , argument := range args {
1017+ var header string
1018+ switch {
1019+ case argument == "--rsh-header" && index + 1 < len (args ):
1020+ header = args [index + 1 ]
1021+ case strings .HasPrefix (argument , "--rsh-header=" ):
1022+ header = strings .TrimPrefix (argument , "--rsh-header=" )
1023+ default :
1024+ continue
1025+ }
1026+ headerName , _ , found := strings .Cut (header , ":" )
1027+ if found && strings .EqualFold (strings .TrimSpace (headerName ), name ) {
1028+ return true
1029+ }
1030+ }
1031+ return false
1032+ }
1033+
9881034func hasRuntimeFlag (args []string , name string ) bool {
9891035 for _ , argument := range args {
9901036 if argument == name || strings .HasPrefix (argument , name + "=" ) {
@@ -1015,7 +1061,8 @@ func (a *App) newRestishRuntime(service *agent.Service, config *restish.Config)
10151061 return a .newRestishRuntimeWithCommandSurface (service , config , restish.CommandSurface {
10161062 HTTPMethods : []string {"get" , "head" , "post" , "put" , "patch" , "delete" }, RegisteredAPIs : true , HideSupportCommands : true ,
10171063 MetadataRefreshTimeout : 30 * time .Second , IgnoreUserConfig : true , DisablePlugins : true , HideInternalFlags : true ,
1018- CompactOperationHelp : true ,
1064+ CompactOperationHelp : true ,
1065+ AutomaticIdempotencyKeys : true ,
10191066 })
10201067}
10211068
0 commit comments