Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/lk/agent_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ func resolveCredentials(ctx context.Context, cmd *cli.Command, dir string, loadO
// priority order. Without this, ambient LIVEKIT_* shell variables (which
// count as explicit above) would override — or worse, mix into — the
// project the user asked for by name.
if cmd.String("project") != "" || cmd.String("subdomain") != "" {
if namedProject(cmd) {
explicit = agentCredentials{}
}

Expand Down
10 changes: 10 additions & 0 deletions cmd/lk/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ func requireProjectWithOpts(ctx context.Context, cmd *cli.Command, opts ...loadO
case errors.Is(err, config.ErrInvalidConfig):
// something is wrong with the project config file
return ctx, err
case err != nil && namedProject(cmd):
// the user named a project that doesn't resolve: say so rather than
// act on another one
return ctx, err
case err != nil:
// no project could be resolved automatically; choose from existing
// credentials or authenticate, then announce once below.
Expand Down Expand Up @@ -197,6 +201,12 @@ func requireProjectWithOpts(ctx context.Context, cmd *cli.Command, opts ...loadO
return ctx, nil
}

// namedProject reports whether the user named a project on the command line
// for resolveProject to find, with --project or --subdomain.
func namedProject(cmd *cli.Command) bool {
return cmd.String("project") != "" || cmd.String("subdomain") != ""
}

// selectProject resolves the package-level `project` interactively: it picks from the
// configured projects, or (when none exist) offers to authenticate one via `lk cloud auth`.
// It does not print a confirmation; the caller announces the result exactly once.
Expand Down
17 changes: 8 additions & 9 deletions cmd/lk/testdata/script/mistyped-project.txtar
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
# In API-key mode, a --project that names no configured project is an error
# listing the configured ones. lk never falls back to the only project: the
# fake would answer that project's server, so lk can only fail by refusing.
# Today it does fall back; the checks before the skip record that.
# In API-key mode, a --project or --subdomain that names no configured
# project is an error listing the configured ones. lk never falls back to the
# only project: the fake would answer that project's server, so lk can only
# fail by refusing.
respond POST http://one.test/twirp/livekit.RoomService/ListRooms 200 ''

exec lk --project onee room list
stderr '^Using project \[one\]$'
! exec lk --project onee room list
stderr 'project not found\. Available projects: one'
requests
stdout '^POST http://one.test/twirp/livekit.RoomService/ListRooms apikey=APIone$'
! stdout .

skip 'known bug: a --project that names no configured project falls back to the only one'
! exec lk --project onee room list
! exec lk --subdomain onee room list
stderr 'project not found\. Available projects: one'
requests
! stdout .
Expand Down
5 changes: 3 additions & 2 deletions cmd/lk/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,8 +392,9 @@ func (rp *resolvedProject) announce() {
// 5. livekit.toml in the working directory
// 6. the configured default project
//
// It performs no prompting and no printing: callers print rp.notice via out.Status, and
// handle the no-project case (a returned error) by offering interactive selection.
// It performs no prompting and no printing: callers print rp.notice via out.Status.
// requireProjectWithOpts handles a returned error by offering interactive
// selection, unless --project or --subdomain named a project (see namedProject).
func resolveProject(c *cli.Command, p loadParams) (*resolvedProject, error) {
// 1. explicit project
if c.String("project") != "" {
Expand Down
44 changes: 44 additions & 0 deletions cmd/lk/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v3"

"github.com/livekit/livekit-cli/v2/pkg/config"
"github.com/livekit/livekit-cli/v2/pkg/util"
)

Expand Down Expand Up @@ -160,6 +161,49 @@ func TestResolveProjectSource(t *testing.T) {
})
}

// TestRequireProjectNamedProjectIsAnError checks that a --project or
// --subdomain naming no configured project fails instead of falling back to
// the only configured one, even when prompts are skipped.
func TestRequireProjectNamedProjectIsAnError(t *testing.T) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think we need this since we have the e2e test too

t.Setenv("LIVEKIT_URL", "")
t.Setenv("LIVEKIT_API_KEY", "")
t.Setenv("LIVEKIT_API_SECRET", "")
setHome(t, t.TempDir())
conf := &config.CLIConfig{Projects: []config.ProjectConfig{
{Name: "one", URL: "https://one.livekit.cloud", APIKey: "APIone", APISecret: "secretone"},
}}
require.NoError(t, conf.PersistQuietly())
prevProject, prevConfig := project, cliConfig
t.Cleanup(func() { project, cliConfig = prevProject, prevConfig })

for _, args := range [][]string{{"--project", "onee"}, {"--subdomain", "onee"}} {
t.Run(args[0], func(t *testing.T) {
project = nil
var err error
app := &cli.Command{
Name: "lk",
Flags: []cli.Flag{
&cli.StringFlag{Name: "url", Value: "http://localhost:7880"},
&cli.StringFlag{Name: "api-key"},
&cli.StringFlag{Name: "api-secret"},
&cli.BoolFlag{Name: "dev"},
&cli.StringFlag{Name: "project"},
&cli.StringFlag{Name: "subdomain"},
&cli.BoolFlag{Name: "user-auth"},
&cli.BoolFlag{Name: "yes"},
},
Action: func(ctx context.Context, cmd *cli.Command) error {
_, err = requireProjectWithOpts(ctx, cmd)
return nil
},
}
require.NoError(t, app.Run(context.Background(), append([]string{"lk", "--yes"}, args...)))
assert.ErrorContains(t, err, "project not found")
assert.Nil(t, project, "no project is chosen for a name that doesn't resolve")
})
}
}

func TestOptionalFlag(t *testing.T) {
requiredFlag := &cli.StringFlag{
Name: "test",
Expand Down
Loading