Skip to content

Commit 725d0ab

Browse files
committed
Merge remote-tracking branch 'origin/main' into raja_v2_start
2 parents d082f70 + 8188873 commit 725d0ab

File tree

17 files changed

+946
-158
lines changed

17 files changed

+946
-158
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,7 @@ bin/
1717
.idea/
1818
dist/
1919

20-
.DS_Store
20+
# Local artifacts
21+
.task/
22+
23+
.DS_Store

cmd/lk/agent.go

Lines changed: 73 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,10 @@ var (
9999
silentFlag,
100100
regionFlag,
101101
},
102-
ArgsUsage: "[working-dir]",
102+
// NOTE: since secrets may contain commas, or indeed any special character we might want to treat as a flag separator,
103+
// we disable it entirely here and require multiple --secrets flags to be used.
104+
DisableSliceFlagSeparator: true,
105+
ArgsUsage: "[working-dir]",
103106
},
104107
{
105108
Name: "config",
@@ -120,7 +123,10 @@ var (
120123
secretsFlag,
121124
secretsFileFlag,
122125
},
123-
ArgsUsage: "[working-dir]",
126+
// NOTE: since secrets may contain commas, or indeed any special character we might want to treat as a flag separator,
127+
// we disable it entirely here and require multiple --secrets flags to be used.
128+
DisableSliceFlagSeparator: true,
129+
ArgsUsage: "[working-dir]",
124130
},
125131
{
126132
Name: "status",
@@ -141,7 +147,10 @@ var (
141147
secretsFlag,
142148
secretsFileFlag,
143149
},
144-
ArgsUsage: "[working-dir]",
150+
// NOTE: since secrets may contain commas, or indeed any special character we might want to treat as a flag separator,
151+
// we disable it entirely here and require multiple --secrets flags to be used.
152+
DisableSliceFlagSeparator: true,
153+
ArgsUsage: "[working-dir]",
145154
},
146155
{
147156
Name: "restart",
@@ -237,7 +246,10 @@ var (
237246
Value: false,
238247
},
239248
},
240-
ArgsUsage: "[working-dir]",
249+
// NOTE: since secrets may contain commas, or indeed any special character we might want to treat as a flag separator,
250+
// we disable it entirely here and require multiple --secrets flags to be used.
251+
DisableSliceFlagSeparator: true,
252+
ArgsUsage: "[working-dir]",
241253
},
242254
},
243255
},
@@ -293,7 +305,7 @@ func createAgent(ctx context.Context, cmd *cli.Command) error {
293305
}
294306

295307
// We have a configured project, but don't need to double-confirm if it was
296-
// set via a command line flag, because intent it clear.
308+
// set via a command line flag, because intent is clear.
297309
if !cmd.IsSet("project") {
298310
useProject := true
299311
if err := huh.NewForm(huh.NewGroup(huh.NewConfirm().
@@ -352,10 +364,21 @@ func createAgent(ctx context.Context, cmd *cli.Command) error {
352364
return err
353365
}
354366

355-
if err := requireDockerfile(ctx, cmd, workingDir); err != nil {
367+
settingsMap, err := getClientSettings(ctx, cmd.Bool("silent"))
368+
if err != nil {
369+
return err
370+
}
371+
372+
if err := requireDockerfile(ctx, cmd, workingDir, settingsMap); err != nil {
356373
return err
357374
}
358375

376+
// TODO (steveyoon): disable check SDK version until we add support for uv and lockfile generation
377+
// https://github.com/livekit/livekit-cli/pull/618/files
378+
// if err := agentfs.CheckSDKVersion(workingDir, settingsMap); err != nil {
379+
// return err
380+
// }
381+
359382
req := &lkproto.CreateAgentRequest{
360383
Secrets: secrets,
361384
Regions: lkConfig.Agent.Regions,
@@ -1003,13 +1026,18 @@ func selectAgent(ctx context.Context, _ *cli.Command) (string, error) {
10031026
func requireSecrets(_ context.Context, cmd *cli.Command, required, lazy bool) ([]*lkproto.AgentSecret, error) {
10041027
silent := cmd.Bool("silent")
10051028
secrets := make(map[string]*lkproto.AgentSecret)
1006-
for _, secret := range cmd.StringSlice("secrets") {
1007-
secret := strings.Split(secret, "=")
1008-
agentSecret := &lkproto.AgentSecret{
1009-
Name: secret[0],
1010-
Value: []byte(secret[1]),
1029+
1030+
if values, err := parseKeyValuePairs(cmd, "secrets"); err != nil {
1031+
return nil, fmt.Errorf("failed to parse secrets: %w", err)
1032+
} else {
1033+
for key, val := range values {
1034+
agentSecret := &lkproto.AgentSecret{
1035+
Name: key,
1036+
Value: []byte(val),
1037+
}
1038+
secrets[key] = agentSecret
10111039
}
1012-
secrets[secret[0]] = agentSecret
1040+
10131041
}
10141042

10151043
shouldReadFromDisk := cmd.IsSet("secrets-file") || !lazy || (required && len(secrets) == 0)
@@ -1056,42 +1084,13 @@ func requireSecrets(_ context.Context, cmd *cli.Command, required, lazy bool) ([
10561084
return secretsSlice, nil
10571085
}
10581086

1059-
func requireDockerfile(ctx context.Context, cmd *cli.Command, workingDir string) error {
1087+
func requireDockerfile(ctx context.Context, cmd *cli.Command, workingDir string, settingsMap map[string]string) error {
10601088
dockerfileExists, err := agentfs.HasDockerfile(workingDir)
10611089
if err != nil {
10621090
return err
10631091
}
10641092

10651093
if !dockerfileExists {
1066-
var clientSettingsResponse *lkproto.ClientSettingsResponse
1067-
1068-
if !cmd.Bool("silent") {
1069-
if err := util.Await(
1070-
"Loading client settings...",
1071-
func() {
1072-
clientSettingsResponse, err = agentsClient.GetClientSettings(ctx, &lkproto.ClientSettingsRequest{})
1073-
},
1074-
); err != nil {
1075-
return err
1076-
}
1077-
} else {
1078-
clientSettingsResponse, err = agentsClient.GetClientSettings(ctx, &lkproto.ClientSettingsRequest{})
1079-
}
1080-
1081-
if err != nil {
1082-
if twerr, ok := err.(twirp.Error); ok {
1083-
if twerr.Code() == twirp.PermissionDenied {
1084-
return fmt.Errorf("agent hosting is disabled for this project -- join the beta program here [%s]", cloudAgentsBetaSignupURL)
1085-
}
1086-
}
1087-
return err
1088-
}
1089-
1090-
settingsMap := make(map[string]string)
1091-
for _, setting := range clientSettingsResponse.Params {
1092-
settingsMap[setting.Name] = setting.Value
1093-
}
1094-
10951094
if !cmd.Bool("silent") {
10961095
var innerErr error
10971096
if err := util.Await(
@@ -1120,6 +1119,38 @@ func requireDockerfile(ctx context.Context, cmd *cli.Command, workingDir string)
11201119
return nil
11211120
}
11221121

1122+
func getClientSettings(ctx context.Context, silent bool) (map[string]string, error) {
1123+
var clientSettingsResponse *lkproto.ClientSettingsResponse
1124+
var err error
1125+
1126+
if !silent {
1127+
err = util.Await(
1128+
"Loading client settings...",
1129+
func() {
1130+
clientSettingsResponse, err = agentsClient.GetClientSettings(ctx, &lkproto.ClientSettingsRequest{})
1131+
},
1132+
)
1133+
} else {
1134+
clientSettingsResponse, err = agentsClient.GetClientSettings(ctx, &lkproto.ClientSettingsRequest{})
1135+
}
1136+
1137+
if err != nil {
1138+
if twerr, ok := err.(twirp.Error); ok {
1139+
if twerr.Code() == twirp.PermissionDenied {
1140+
return nil, fmt.Errorf("agent hosting is disabled for this project -- join the beta program here [%s]", cloudAgentsBetaSignupURL)
1141+
}
1142+
}
1143+
return nil, err
1144+
}
1145+
1146+
settingsMap := make(map[string]string)
1147+
for _, setting := range clientSettingsResponse.Params {
1148+
settingsMap[setting.Name] = setting.Value
1149+
}
1150+
1151+
return settingsMap, nil
1152+
}
1153+
11231154
func requireConfig(workingDir, tomlFilename string) (bool, error) {
11241155
if lkConfig != nil {
11251156
return true, nil

cmd/lk/app.go

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"context"
1919
"errors"
2020
"fmt"
21+
"maps"
2122
"os"
2223
"regexp"
2324
"strings"
@@ -453,9 +454,7 @@ func instantiateEnv(ctx context.Context, cmd *cli.Command, rootPath string, addl
453454
}
454455

455456
if addlEnv != nil {
456-
for k, v := range *addlEnv {
457-
env[k] = v
458-
}
457+
maps.Copy(env, *addlEnv)
459458
}
460459

461460
prompt := func(key, oldValue string) (string, error) {
@@ -526,16 +525,42 @@ func doInstall(ctx context.Context, task bootstrap.KnownTask, rootPath string, v
526525
func runTask(ctx context.Context, cmd *cli.Command) error {
527526
verbose := cmd.Bool("verbose")
528527
rootDir := "."
529-
tf, err := bootstrap.ParseTaskfile(rootDir)
528+
529+
args := cmd.Args().Tail()
530+
if len(args) > 0 {
531+
dirPath := args[0]
532+
if stat, err := os.Stat(dirPath); os.IsNotExist(err) {
533+
return fmt.Errorf("directory does not exist: %s", dirPath)
534+
} else if err != nil {
535+
return fmt.Errorf("error accessing directory %s: %v", dirPath, err)
536+
} else {
537+
if !stat.IsDir() {
538+
return fmt.Errorf("path is not a directory: %s", dirPath)
539+
}
540+
rootDir = dirPath
541+
}
542+
}
543+
544+
_, err := bootstrap.ParseTaskfile(rootDir)
530545
if err != nil {
531546
return err
532547
}
533548

549+
exe := bootstrap.NewTaskExecutor(rootDir, verbose)
550+
if err := exe.Setup(); err != nil {
551+
return fmt.Errorf("could not initialize task executor: %w", err)
552+
}
553+
534554
taskName := cmd.Args().First()
535555
if taskName == "" {
556+
tasks, err := exe.GetTaskList()
557+
if err != nil {
558+
return err
559+
}
560+
536561
var options []huh.Option[string]
537-
for name := range tf.Tasks.Keys(nil) {
538-
options = append(options, huh.NewOption(name, name))
562+
for _, t := range tasks {
563+
options = append(options, huh.NewOption(t.Name(), t.Name()))
539564
}
540565

541566
if err := huh.NewForm(
@@ -549,16 +574,9 @@ func runTask(ctx context.Context, cmd *cli.Command) error {
549574
}
550575
}
551576

552-
task, err := bootstrap.NewTask(ctx, tf, rootDir, taskName, verbose)
577+
task, err := bootstrap.NewTaskWithExecutor(ctx, exe, taskName, verbose)
553578
if err != nil {
554579
return err
555580
}
556-
var cmdErr error
557-
if err := util.Await(
558-
"Running task "+taskName+"...",
559-
func() { cmdErr = task() },
560-
); err != nil {
561-
return err
562-
}
563-
return cmdErr
581+
return task()
564582
}

cmd/lk/room.go

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"context"
1919
"encoding/json"
2020
"fmt"
21+
"maps"
2122
"os"
2223
"os/signal"
2324
"regexp"
@@ -939,14 +940,9 @@ func joinRoom(ctx context.Context, cmd *cli.Command) error {
939940
},
940941
}
941942

942-
participantAttributes := make(map[string]string)
943-
944-
attrs := cmd.StringSlice("attribute")
945-
for _, attr := range attrs {
946-
kv := strings.Split(attr, "=")
947-
if len(kv) == 2 {
948-
participantAttributes[kv[0]] = kv[1]
949-
}
943+
participantAttributes, err := parseKeyValuePairs(cmd, "attribute")
944+
if err != nil {
945+
return fmt.Errorf("failed to parse participant attributes: %w", err)
950946
}
951947

952948
// Read attributes from JSON file if specified
@@ -962,9 +958,7 @@ func joinRoom(ctx context.Context, cmd *cli.Command) error {
962958
}
963959

964960
// Add attributes from file to the existing ones
965-
for key, value := range fileAttrs {
966-
participantAttributes[key] = value
967-
}
961+
maps.Copy(participantAttributes, fileAttrs)
968962
}
969963

970964
room, err := lksdk.ConnectToRoom(project.URL, lksdk.ConnectInfo{

cmd/lk/utils.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ package main
1717
import (
1818
"errors"
1919
"fmt"
20+
"maps"
2021
"os"
2122
"strings"
2223

24+
"github.com/joho/godotenv"
2325
"github.com/twitchtv/twirp"
2426
"github.com/urfave/cli/v3"
2527

@@ -160,6 +162,24 @@ func extractFlagOrArg(c *cli.Command, flag string) (string, error) {
160162
return value, nil
161163
}
162164

165+
func parseKeyValuePairs(c *cli.Command, flag string) (map[string]string, error) {
166+
pairs := c.StringSlice(flag)
167+
if len(pairs) == 0 {
168+
return nil, nil
169+
}
170+
171+
result := make(map[string]string, len(pairs))
172+
173+
for _, pair := range pairs {
174+
if m, err := godotenv.Unmarshal(pair); err != nil {
175+
return nil, fmt.Errorf("invalid key-value pair: %s: %w", pair, err)
176+
} else {
177+
maps.Copy(result, m)
178+
}
179+
}
180+
return result, nil
181+
}
182+
163183
type loadParams struct {
164184
requireURL bool
165185
}

0 commit comments

Comments
 (0)