diff --git a/.circleci/config.yml b/.circleci/config.yml index d53f12c984..0282014bfb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -778,6 +778,7 @@ jobs: # Service stop and start - run: "${KURTOSIS_BINPATH} service stop test-enclave test1" - run: "${KURTOSIS_BINPATH} service start test-enclave test1" + - run: "${KURTOSIS_BINPATH} service restart test-enclave test1" # Service inspect - run: "${KURTOSIS_BINPATH} service inspect test-enclave test1" diff --git a/cli/cli/command_str_consts/command_str_consts.go b/cli/cli/command_str_consts/command_str_consts.go index 9874cb4985..011490b969 100644 --- a/cli/cli/command_str_consts/command_str_consts.go +++ b/cli/cli/command_str_consts/command_str_consts.go @@ -61,6 +61,7 @@ const ( ServiceRmCmdStr = "rm" ServiceShellCmdStr = "shell" ServiceStartCmdStr = "start" + ServiceRestartCmdStr = "restart" ServiceStopCmdStr = "stop" ServiceInspectCmdStr = "inspect" StarlarkRunCmdStr = "run" diff --git a/cli/cli/commands/service/restart/restart.go b/cli/cli/commands/service/restart/restart.go new file mode 100644 index 0000000000..e83d84a1f1 --- /dev/null +++ b/cli/cli/commands/service/restart/restart.go @@ -0,0 +1,127 @@ +package restart + +import ( + "context" + "fmt" + "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/starlark_run_config" + "github.com/sirupsen/logrus" + + "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/enclaves" + "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/services" + "github.com/kurtosis-tech/kurtosis/api/golang/engine/kurtosis_engine_rpc_api_bindings" + "github.com/kurtosis-tech/kurtosis/api/golang/engine/lib/kurtosis_context" + "github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/highlevel/enclave_id_arg" + "github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/highlevel/engine_consuming_kurtosis_command" + "github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/highlevel/service_identifier_arg" + "github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/args" + "github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/flags" + "github.com/kurtosis-tech/kurtosis/cli/cli/command_str_consts" + "github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface" + "github.com/kurtosis-tech/kurtosis/metrics-library/golang/lib/metrics_client" + "github.com/kurtosis-tech/stacktrace" +) + +const ( + enclaveIdentifierArgKey = "enclave" + isEnclaveIdArgOptional = false + isEnclaveIdArgGreedy = false + + serviceIdentifierArgKey = "service" + isServiceIdentifierArgOptional = false + isServiceIdentifierArgGreedy = true + + kurtosisBackendCtxKey = "kurtosis-backend" + engineClientCtxKey = "engine-client" + + starlarkScript = ` +def run(plan, args): + plan.stop_service(name=args["service_name"]) + plan.start_service(name=args["service_name"]) +` +) + +var ServiceRestartCmd = &engine_consuming_kurtosis_command.EngineConsumingKurtosisCommand{ + CommandStr: command_str_consts.ServiceRestartCmdStr, + ShortDescription: "Restarts one or many services", + LongDescription: "Restarts services with the given service identifiers in the given enclave", + KurtosisBackendContextKey: kurtosisBackendCtxKey, + EngineClientContextKey: engineClientCtxKey, + Args: []*args.ArgConfig{ + enclave_id_arg.NewEnclaveIdentifierArg( + enclaveIdentifierArgKey, + engineClientCtxKey, + isEnclaveIdArgOptional, + isEnclaveIdArgGreedy, + ), + service_identifier_arg.NewServiceIdentifierArg( + serviceIdentifierArgKey, + enclaveIdentifierArgKey, + isServiceIdentifierArgOptional, + isServiceIdentifierArgGreedy, + ), + }, + Flags: []*flags.FlagConfig{}, + RunFunc: run, +} + +func run( + ctx context.Context, + _ backend_interface.KurtosisBackend, + _ kurtosis_engine_rpc_api_bindings.EngineServiceClient, + _ metrics_client.MetricsClient, + _ *flags.ParsedFlags, + args *args.ParsedArgs, +) error { + enclaveIdentifier, err := args.GetNonGreedyArg(enclaveIdentifierArgKey) + if err != nil { + return stacktrace.Propagate(err, "An error occurred getting the enclave identifier value using key '%v'", enclaveIdentifierArgKey) + } + + serviceIdentifiers, err := args.GetGreedyArg(serviceIdentifierArgKey) + if err != nil { + return stacktrace.Propagate(err, "An error occurred getting the service identifiers values using key '%v'", serviceIdentifierArgKey) + } + + kurtosisCtx, err := kurtosis_context.NewKurtosisContextFromLocalEngine() + if err != nil { + return stacktrace.Propagate(err, "An error occurred creating Kurtosis Context from local engine") + } + + enclaveCtx, err := kurtosisCtx.GetEnclaveContext(ctx, enclaveIdentifier) + if err != nil { + return stacktrace.Propagate(err, "An error occurred getting an enclave context from enclave info for enclave '%v'", enclaveIdentifier) + } + + for _, serviceIdentifier := range serviceIdentifiers { + logrus.Infof("Restarting service '%v'", serviceIdentifier) + serviceContext, err := enclaveCtx.GetServiceContext(serviceIdentifier) + if err != nil { + return stacktrace.NewError("Couldn't validate whether the service exists for identifier '%v'", serviceIdentifier) + } + + serviceName := serviceContext.GetServiceName() + + if err := restartServiceStarlarkCommand(ctx, enclaveCtx, serviceName); err != nil { + return stacktrace.Propagate(err, "An error occurred restarting service '%v' from enclave '%v'", serviceIdentifier, enclaveIdentifier) + } + } + return nil +} + +func restartServiceStarlarkCommand(ctx context.Context, enclaveCtx *enclaves.EnclaveContext, serviceName services.ServiceName) error { + serviceNameString := fmt.Sprintf(`{"service_name": "%s"}`, serviceName) + runResult, err := enclaveCtx.RunStarlarkScriptBlocking(ctx, starlarkScript, starlark_run_config.NewRunStarlarkConfig(starlark_run_config.WithSerializedParams(serviceNameString))) + if err != nil { + return stacktrace.Propagate(err, "An unexpected error occurred on Starlark for restarting service") + } + if runResult.ExecutionError != nil { + return stacktrace.NewError("An error occurred during Starlark script execution for restarting service: %s", runResult.ExecutionError.GetErrorMessage()) + } + if runResult.InterpretationError != nil { + return stacktrace.NewError("An error occurred during Starlark script interpretation for restarting service: %s", runResult.InterpretationError.GetErrorMessage()) + } + if len(runResult.ValidationErrors) > 0 { + return stacktrace.NewError("An error occurred during Starlark script validation for restarting service: %v", runResult.ValidationErrors) + } + return nil +} diff --git a/cli/cli/commands/service/service.go b/cli/cli/commands/service/service.go index 8fc9ddbd6a..f708859577 100644 --- a/cli/cli/commands/service/service.go +++ b/cli/cli/commands/service/service.go @@ -11,6 +11,7 @@ import ( "github.com/kurtosis-tech/kurtosis/cli/cli/commands/service/exec" "github.com/kurtosis-tech/kurtosis/cli/cli/commands/service/inspect" "github.com/kurtosis-tech/kurtosis/cli/cli/commands/service/logs" + "github.com/kurtosis-tech/kurtosis/cli/cli/commands/service/restart" "github.com/kurtosis-tech/kurtosis/cli/cli/commands/service/rm" "github.com/kurtosis-tech/kurtosis/cli/cli/commands/service/shell" "github.com/kurtosis-tech/kurtosis/cli/cli/commands/service/start" @@ -35,4 +36,5 @@ func init() { ServiceCmd.AddCommand(start.ServiceStartCmd.MustGetCobraCommand()) ServiceCmd.AddCommand(stop.ServiceStopCmd.MustGetCobraCommand()) ServiceCmd.AddCommand(inspect.ServiceInspectCmd.MustGetCobraCommand()) + ServiceCmd.AddCommand(restart.ServiceRestartCmd.MustGetCobraCommand()) } diff --git a/docs/docs/cli-reference/service-restart.md b/docs/docs/cli-reference/service-restart.md new file mode 100644 index 0000000000..714a57c939 --- /dev/null +++ b/docs/docs/cli-reference/service-restart.md @@ -0,0 +1,13 @@ +--- +title: service restart +sidebar_label: service restart +slug: /service-restart +--- + +Services can be restarted like so: + +```bash +kurtosis service restart $THE_ENCLAVE_IDENTIFIER $THE_SERVICE_IDENTIFIER +``` + +where `$THE_ENCLAVE_IDENTIFIER` and the `$THE_SERVICE_IDENTIFIER` are [resource identifiers](../advanced-concepts/resource-identifier.md) for the enclave and service, respectively.