diff --git a/cmd/lk/agent.go b/cmd/lk/agent.go index a6ec5e6f..9ea2292d 100644 --- a/cmd/lk/agent.go +++ b/cmd/lk/agent.go @@ -248,7 +248,7 @@ var ( { Name: "deploy", Usage: "Deploy a new version of the agent", - Before: createAgentClient, + Before: prepareAgentDeploy, Action: deployAgent, Flags: []cli.Flag{ attributesFlag, @@ -435,6 +435,18 @@ func noAgentError() error { "To get started, see: https://docs.livekit.io/agents/quickstart") } +func prepareAgentDeploy(ctx context.Context, cmd *cli.Command) (context.Context, error) { + // The prebuilt upload client has no deployment selector. Check + // before client setup, secret updates, or acquiring an image push target. + if deployment := cmd.String("deployment"); deployment != "" && + (cmd.String("image") != "" || cmd.String("image-tar") != "") { + return ctx, fmt.Errorf("--deployment %q is not supported with --image or --image-tar; "+ + "prebuilt image deployments use the default production deployment. "+ + "Deploy from source to target a named deployment; omit --deployment only if production is intended", deployment) + } + return createAgentClient(ctx, cmd) +} + func createAgentClient(ctx context.Context, cmd *cli.Command) (context.Context, error) { return createAgentClientWithOpts(ctx, cmd) } diff --git a/cmd/lk/agent_deploy_test.go b/cmd/lk/agent_deploy_test.go new file mode 100644 index 00000000..7f549785 --- /dev/null +++ b/cmd/lk/agent_deploy_test.go @@ -0,0 +1,72 @@ +package main + +import ( + "context" + "io" + "testing" + + "github.com/livekit/livekit-cli/v2/pkg/config" + "github.com/stretchr/testify/require" + "github.com/urfave/cli/v3" +) + +func TestAgentDeployRejectsImageDeployment(t *testing.T) { + cmd := agentDeployTestCommand(t) + + err := cmd.Run(context.Background(), []string{"deploy", "--image", "local:latest", "--deployment", "staging"}) + + require.ErrorContains(t, err, "--deployment \"staging\" is not supported with --image or --image-tar") +} + +func TestAgentDeployRejectsImageTarDeployment(t *testing.T) { + cmd := agentDeployTestCommand(t) + + err := cmd.Run(context.Background(), []string{"deploy", "--image-tar", "image.tar", "--deployment", "staging"}) + + require.ErrorContains(t, err, "--deployment \"staging\" is not supported with --image or --image-tar") + require.Nil(t, agentsClient, "rejection must happen before client creation") +} + +func TestAgentDeployAllowsPrebuiltWithoutDeployment(t *testing.T) { + cmd := agentDeployTestCommand(t) + + err := cmd.Run(context.Background(), []string{"deploy", "--image-tar", "image.tar"}) + + require.NoError(t, err) +} + +func TestAgentDeployAllowsNamedSourceDeployment(t *testing.T) { + cmd := agentDeployTestCommand(t) + + err := cmd.Run(context.Background(), []string{"deploy", "--deployment", "staging"}) + + require.NoError(t, err) +} + +func agentDeployTestCommand(t *testing.T) *cli.Command { + t.Helper() + // Client setup uses package globals, so these tests must not run in parallel. + oldProject, oldConfig, oldClient, oldDir := project, lkConfig, agentsClient, workingDir + t.Cleanup(func() { + project, lkConfig, agentsClient, workingDir = oldProject, oldConfig, oldClient, oldDir + }) + project = &config.ProjectConfig{URL: "https://fixture.livekit.cloud", APIKey: "fixture-key", APISecret: "fixture-secret"} + lkConfig, agentsClient = nil, nil + workingDir = t.TempDir() + + agent := findCommandByName(AgentCommands, "agent") + deploy := findCommandByName(agent.Commands, "deploy") + return &cli.Command{ + Name: "deploy", + Before: deploy.Before, + Writer: io.Discard, + ErrWriter: io.Discard, + Flags: []cli.Flag{ + &cli.StringFlag{Name: "image"}, + &cli.StringFlag{Name: "image-tar"}, + &cli.StringFlag{Name: "deployment"}, + }, + // Exercise the registered Before hook without loading or deploying an image. + Action: func(context.Context, *cli.Command) error { return nil }, + } +}