diff --git a/internal/cli/agent/deploy.go b/internal/cli/agent/deploy.go index 5ef11b0b..cf75ac59 100644 --- a/internal/cli/agent/deploy.go +++ b/internal/cli/agent/deploy.go @@ -6,6 +6,7 @@ import ( "os" "strings" + cliCommon "github.com/agentregistry-dev/agentregistry/internal/cli/common" cliUtils "github.com/agentregistry-dev/agentregistry/internal/cli/utils" "github.com/agentregistry-dev/agentregistry/pkg/models" "github.com/spf13/cobra" @@ -34,6 +35,7 @@ func runDeploy(cmd *cobra.Command, args []string) error { providerID, _ := cmd.Flags().GetString("provider-id") namespace, _ := cmd.Flags().GetString("namespace") envFlags, _ := cmd.Flags().GetStringArray("env") + wait, _ := cmd.Flags().GetBool("wait") if version == "" { version = "latest" @@ -81,7 +83,7 @@ func runDeploy(cmd *cobra.Command, args []string) error { if providerID == "local" { return deployLocal(name, version, config, providerID) } - return deployToProvider(name, version, config, namespace, providerID) + return deployToProvider(name, version, config, namespace, providerID, wait) } // buildDeployConfig creates the configuration map with all necessary environment variables. @@ -127,7 +129,7 @@ func deployLocal(name, version string, config map[string]string, providerID stri } // deployToProvider deploys an agent to a non-local provider. -func deployToProvider(name, version string, config map[string]string, namespace string, providerID string) error { +func deployToProvider(name, version string, config map[string]string, namespace string, providerID string, wait bool) error { deployment, err := apiClient.DeployAgent(name, version, config, providerID) if err != nil { return fmt.Errorf("failed to deploy agent: %w", err) @@ -137,6 +139,14 @@ func deployToProvider(name, version string, config map[string]string, namespace if ns == "" { ns = "(default)" } + + if wait { + fmt.Printf("Waiting for agent '%s' to become ready...\n", deployment.ServerName) + if err := cliCommon.WaitForDeploymentReady(apiClient, deployment.ID); err != nil { + return err + } + } + fmt.Printf("Agent '%s' version '%s' deployed to providerId=%s in namespace '%s'\n", deployment.ServerName, deployment.Version, providerID, ns) return nil } @@ -146,5 +156,6 @@ func init() { DeployCmd.Flags().String("provider-id", "", "Deployment target provider ID (defaults to local when omitted)") DeployCmd.Flags().Bool("prefer-remote", false, "Prefer using a remote source when available") DeployCmd.Flags().String("namespace", "", "Kubernetes namespace for agent deployment (defaults to current kubeconfig context)") + DeployCmd.Flags().Bool("wait", true, "Wait for the deployment to become ready before returning") DeployCmd.Flags().StringArrayP("env", "e", []string{}, "Environment variables to set on the deployed agent (KEY=VALUE)") } diff --git a/internal/cli/common/wait.go b/internal/cli/common/wait.go new file mode 100644 index 00000000..c4b4a867 --- /dev/null +++ b/internal/cli/common/wait.go @@ -0,0 +1,45 @@ +package common + +import ( + "fmt" + "time" + + "github.com/agentregistry-dev/agentregistry/internal/client" +) + +const ( + defaultWaitTimeout = 5 * time.Minute + defaultPollInterval = 2 * time.Second +) + +// WaitForDeploymentReady polls a deployment until it reaches a terminal state +// (deployed or failed). Returns an error if the deployment fails or the timeout +// is exceeded. +func WaitForDeploymentReady(c *client.Client, deploymentID string) error { + deadline := time.Now().Add(defaultWaitTimeout) + + for { + dep, err := c.GetDeploymentByID(deploymentID) + if err != nil { + return fmt.Errorf("polling deployment status: %w", err) + } + if dep == nil { + return fmt.Errorf("deployment %s not found", deploymentID) + } + + switch dep.Status { + case "deployed": + return nil + case "failed": + return fmt.Errorf("deployment failed") + case "cancelled": + return fmt.Errorf("deployment was cancelled") + } + + if time.Now().After(deadline) { + return fmt.Errorf("timed out waiting for deployment to become ready (current status: %s)", dep.Status) + } + + time.Sleep(defaultPollInterval) + } +} diff --git a/internal/cli/mcp/deploy.go b/internal/cli/mcp/deploy.go index b8ad9375..fad2ffe8 100644 --- a/internal/cli/mcp/deploy.go +++ b/internal/cli/mcp/deploy.go @@ -17,6 +17,7 @@ var ( deployYes bool deployProviderID string deployNamespace string + deployWait bool ) var DeployCmd = &cobra.Command{ @@ -38,6 +39,7 @@ func init() { DeployCmd.Flags().BoolVarP(&deployYes, "yes", "y", false, "Automatically accept all prompts (use default/latest version)") DeployCmd.Flags().StringVar(&deployProviderID, "provider-id", "", "Deployment target provider ID (defaults to local when omitted)") DeployCmd.Flags().StringVar(&deployNamespace, "namespace", "", "Kubernetes namespace for deployment (if provider targets Kubernetes)") + DeployCmd.Flags().BoolVar(&deployWait, "wait", true, "Wait for the deployment to become ready before returning") } func runDeploy(cmd *cobra.Command, args []string) error { @@ -93,6 +95,13 @@ func runDeploy(cmd *cobra.Command, args []string) error { return fmt.Errorf("failed to deploy server: %w", err) } + if deployProviderID != "local" && deployWait { + fmt.Printf("Waiting for server '%s' to become ready...\n", deployment.ServerName) + if err := common.WaitForDeploymentReady(apiClient, deployment.ID); err != nil { + return err + } + } + fmt.Printf("\n✓ Deployed %s (%s) with providerId=%s\n", deployment.ServerName, common.FormatVersionForDisplay(deployment.Version), deployProviderID) if deployNamespace != "" { ns := deployNamespace