diff --git a/cli/cli/command_str_consts/command_str_consts.go b/cli/cli/command_str_consts/command_str_consts.go index 926766bdb1..646e3c5b35 100644 --- a/cli/cli/command_str_consts/command_str_consts.go +++ b/cli/cli/command_str_consts/command_str_consts.go @@ -13,6 +13,7 @@ var KurtosisCmdStr = path.Base(os.Args[0]) const ( Analytics = "analytics" CleanCmdStr = "clean" + CloudAddCmdStr = "add" CloudCmdStr = "cloud" CloudLoadCmdStr = "load" ClusterCmdStr = "cluster" diff --git a/cli/cli/commands/analytics/analytics.go b/cli/cli/commands/analytics/analytics.go index 1d8bc0a60b..3a9d5e8b7c 100644 --- a/cli/cli/commands/analytics/analytics.go +++ b/cli/cli/commands/analytics/analytics.go @@ -77,7 +77,7 @@ func run(ctx context.Context, flags *flags.ParsedFlags, args *args.ParsedArgs) e metricsUserIdStore := metrics_user_id_store.GetMetricsUserIDStore() metricsUserId, err := metricsUserIdStore.GetUserID() if err != nil { - return stacktrace.Propagate(err, "An error occurred while getting the users metrics id") + return stacktrace.Propagate(err, "An error occurred while getting the user's metrics id") } out.PrintOutLn(metricsUserId) return nil diff --git a/cli/cli/commands/cloud/add/add.go b/cli/cli/commands/cloud/add/add.go new file mode 100644 index 0000000000..be504c354f --- /dev/null +++ b/cli/cli/commands/cloud/add/add.go @@ -0,0 +1,73 @@ +package add + +import ( + "context" + "fmt" + "github.com/kurtosis-tech/kurtosis/api/golang/engine/lib/cloud" + "github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel" + "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" + cloudhelper "github.com/kurtosis-tech/kurtosis/cli/cli/helpers/cloud" + "github.com/kurtosis-tech/kurtosis/cli/cli/helpers/metrics_user_id_store" + api "github.com/kurtosis-tech/kurtosis/cloud/api/golang/kurtosis_backend_server_rpc_api_bindings" + "github.com/kurtosis-tech/stacktrace" + "github.com/sirupsen/logrus" +) + +var AddCmd = &lowlevel.LowlevelKurtosisCommand{ + CommandStr: command_str_consts.CloudAddCmdStr, + ShortDescription: "Create a new Kurtosis Cloud instance", + LongDescription: "Create a new remote Kurtosis Cloud instance", + Flags: []*flags.FlagConfig{}, + Args: nil, + PreValidationAndRunFunc: nil, + RunFunc: run, + PostValidationAndRunFunc: nil, +} + +func run(ctx context.Context, _ *flags.ParsedFlags, _ *args.ParsedArgs) error { + logrus.Info("Creating a new remote Kurtosis Cloud instance") + apiKey, err := cloudhelper.LoadApiKey() + if err != nil { + return stacktrace.Propagate(err, "Could not load an API Key. Check that it's defined using the "+ + "%s env var and it's a valid (active) key", cloudhelper.KurtosisCloudApiKeyEnvVarArg) + } + + // Use metrics id for now until we replace with a proper auth'd id: + metricsUserIdStore := metrics_user_id_store.GetMetricsUserIDStore() + metricsUserId, err := metricsUserIdStore.GetUserID() + if err != nil { + return stacktrace.Propagate(err, "An error occurred while getting the user's id") + } + + cloudConfig, err := cloudhelper.GetCloudConfig() + if err != nil { + return stacktrace.Propagate(err, "An error occurred while loading the Cloud Config") + } + // Create the connection + connectionStr := fmt.Sprintf("%s:%d", cloudConfig.ApiUrl, cloudConfig.Port) + client, err := cloud.CreateCloudClient(connectionStr, cloudConfig.CertificateChain) + if err != nil { + return stacktrace.Propagate(err, "Error building client for Kurtosis Cloud") + } + + getConfigArgs := &api.CreateCloudInstanceConfigArgs{ + ApiKey: *apiKey, + UserId: metricsUserId, + } + result, err := client.CreateCloudInstance(ctx, getConfigArgs) + if err != nil { + return stacktrace.Propagate(err, "An error occurred while calling the Kurtosis Cloud API") + } + + instanceId := result.GetInstanceId() + logrus.Infof("Success! The Kurtosis Cloud instance is being created with id: %s", instanceId) + logrus.Infof("The Kurtosis cloud instance is currently being created and it should take about 2-3 mins to become ready. "+ + "Once ready, load the instance by calling: kurtosis %s %s %s", + command_str_consts.CloudCmdStr, + command_str_consts.CloudLoadCmdStr, + instanceId, + ) + return nil +} diff --git a/cli/cli/commands/cloud/cloud.go b/cli/cli/commands/cloud/cloud.go index c770ec09d7..9b65a652e9 100644 --- a/cli/cli/commands/cloud/cloud.go +++ b/cli/cli/commands/cloud/cloud.go @@ -2,6 +2,7 @@ package cloud import ( "github.com/kurtosis-tech/kurtosis/cli/cli/command_str_consts" + "github.com/kurtosis-tech/kurtosis/cli/cli/commands/cloud/add" "github.com/kurtosis-tech/kurtosis/cli/cli/commands/cloud/load" "github.com/spf13/cobra" ) @@ -16,4 +17,5 @@ var CloudCmd = &cobra.Command{ func init() { CloudCmd.AddCommand(load.LoadCmd.MustGetCobraCommand()) + CloudCmd.AddCommand(add.AddCmd.MustGetCobraCommand()) } diff --git a/cli/cli/commands/cloud/load/load.go b/cli/cli/commands/cloud/load/load.go index b9008638ba..0996a88613 100644 --- a/cli/cli/commands/cloud/load/load.go +++ b/cli/cli/commands/cloud/load/load.go @@ -12,13 +12,11 @@ import ( "github.com/kurtosis-tech/kurtosis/cli/cli/command_str_consts" "github.com/kurtosis-tech/kurtosis/cli/cli/commands/kurtosis_context/add" "github.com/kurtosis-tech/kurtosis/cli/cli/commands/kurtosis_context/context_switch" - "github.com/kurtosis-tech/kurtosis/cli/cli/kurtosis_config" - "github.com/kurtosis-tech/kurtosis/cli/cli/kurtosis_config/resolved_config" + cloudhelper "github.com/kurtosis-tech/kurtosis/cli/cli/helpers/cloud" api "github.com/kurtosis-tech/kurtosis/cloud/api/golang/kurtosis_backend_server_rpc_api_bindings" "github.com/kurtosis-tech/kurtosis/contexts-config-store/store" "github.com/kurtosis-tech/stacktrace" "github.com/sirupsen/logrus" - "os" ) const ( @@ -49,15 +47,15 @@ func run(ctx context.Context, _ *flags.ParsedFlags, args *args.ParsedArgs) error } logrus.Infof("Loading cloud instance %s", instanceID) - apiKey, err := loadApiKey() + apiKey, err := cloudhelper.LoadApiKey() if err != nil { return stacktrace.Propagate(err, "Could not load an API Key. Check that it's defined using the "+ "%s env var and it's a valid (active) key", kurtosisCloudApiKeyEnvVarArg) } - cloudConfig, err := getCloudConfig() + cloudConfig, err := cloudhelper.GetCloudConfig() if err != nil { - return stacktrace.Propagate(err, "An error occured while loading the Cloud Config") + return stacktrace.Propagate(err, "An error occurred while loading the Cloud Config") } // Create the connection connectionStr := fmt.Sprintf("%s:%d", cloudConfig.ApiUrl, cloudConfig.Port) @@ -74,6 +72,14 @@ func run(ctx context.Context, _ *flags.ParsedFlags, args *args.ParsedArgs) error if err != nil { return stacktrace.Propagate(err, "An error occurred while calling the Kurtosis Cloud API") } + + // TODO: Create shared enums for instance states: + if result.Status != "running" { + logrus.Warnf("The Kurtosis Cloud instance is in state \"%s\" and cannot currently be loaded."+ + " Instance needs to be in state \"running\"", result.Status) + return nil + } + decodedConfigBytes, err := base64.StdEncoding.DecodeString(result.ContextConfig) if err != nil { return stacktrace.Propagate(err, "Failed to base64 decode context config") @@ -96,38 +102,3 @@ func run(ctx context.Context, _ *flags.ParsedFlags, args *args.ParsedArgs) error contextIdentifier := parsedContext.GetName() return context_switch.SwitchContext(ctx, contextIdentifier) } - -func loadApiKey() (*string, error) { - apiKey := os.Getenv(kurtosisCloudApiKeyEnvVarArg) - if len(apiKey) < 1 { - return nil, stacktrace.NewError("No API Key was found. An API Key must be provided as env var %s", kurtosisCloudApiKeyEnvVarArg) - } - logrus.Info("Successfully Loaded API Key...") - return &apiKey, nil -} - -func getCloudConfig() (*resolved_config.KurtosisCloudConfig, error) { - // Get the configuration - kurtosisConfigStore := kurtosis_config.GetKurtosisConfigStore() - configProvider := kurtosis_config.NewKurtosisConfigProvider(kurtosisConfigStore) - kurtosisConfig, err := configProvider.GetOrInitializeConfig() - if err != nil { - return nil, stacktrace.Propagate(err, "Failed to get or initialize Kurtosis configuration") - } - if kurtosisConfig.GetCloudConfig() == nil { - return nil, stacktrace.Propagate(err, "No cloud config was found. This is an internal Kurtosis error.") - } - cloudConfig := kurtosisConfig.GetCloudConfig() - - if cloudConfig.Port == 0 { - cloudConfig.Port = resolved_config.DefaultCloudConfigPort - } - if len(cloudConfig.ApiUrl) < 1 { - cloudConfig.ApiUrl = resolved_config.DefaultCloudConfigApiUrl - } - if len(cloudConfig.CertificateChain) < 1 { - cloudConfig.CertificateChain = resolved_config.DefaultCertificateChain - } - - return cloudConfig, nil -} diff --git a/cli/cli/helpers/cloud/cloud_helpers.go b/cli/cli/helpers/cloud/cloud_helpers.go new file mode 100644 index 0000000000..04a15916dd --- /dev/null +++ b/cli/cli/helpers/cloud/cloud_helpers.go @@ -0,0 +1,48 @@ +package cloud + +import ( + "github.com/kurtosis-tech/kurtosis/cli/cli/kurtosis_config" + "github.com/kurtosis-tech/kurtosis/cli/cli/kurtosis_config/resolved_config" + "github.com/kurtosis-tech/stacktrace" + "github.com/sirupsen/logrus" + "os" +) + +const ( + KurtosisCloudApiKeyEnvVarArg = "KURTOSIS_CLOUD_API_KEY" +) + +func LoadApiKey() (*string, error) { + apiKey := os.Getenv(KurtosisCloudApiKeyEnvVarArg) + if len(apiKey) < 1 { + return nil, stacktrace.NewError("No API Key was found. An API Key must be provided as env var %s", KurtosisCloudApiKeyEnvVarArg) + } + logrus.Info("Successfully Loaded API Key...") + return &apiKey, nil +} + +func GetCloudConfig() (*resolved_config.KurtosisCloudConfig, error) { + // Get the configuration + kurtosisConfigStore := kurtosis_config.GetKurtosisConfigStore() + configProvider := kurtosis_config.NewKurtosisConfigProvider(kurtosisConfigStore) + kurtosisConfig, err := configProvider.GetOrInitializeConfig() + if err != nil { + return nil, stacktrace.Propagate(err, "Failed to get or initialize Kurtosis configuration") + } + if kurtosisConfig.GetCloudConfig() == nil { + return nil, stacktrace.Propagate(err, "No cloud config was found. This is an internal Kurtosis error.") + } + cloudConfig := kurtosisConfig.GetCloudConfig() + + if cloudConfig.Port == 0 { + cloudConfig.Port = resolved_config.DefaultCloudConfigPort + } + if len(cloudConfig.ApiUrl) < 1 { + cloudConfig.ApiUrl = resolved_config.DefaultCloudConfigApiUrl + } + if len(cloudConfig.CertificateChain) < 1 { + cloudConfig.CertificateChain = resolved_config.DefaultCertificateChain + } + + return cloudConfig, nil +}