diff --git a/autocomplete/fish_autocomplete b/autocomplete/fish_autocomplete index 1161da58..e9370e1d 100644 --- a/autocomplete/fish_autocomplete +++ b/autocomplete/fish_autocomplete @@ -80,6 +80,8 @@ complete -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcomma complete -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from config' -f -l help -s h -d 'show help' complete -x -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from config; and not __fish_seen_subcommand_from help h' -a 'help' -d 'Shows a list of commands or help for one command' complete -x -c lk -n '__fish_seen_subcommand_from agent a; and not __fish_seen_subcommand_from init create dockerfile config deploy status update restart rollback logs tail delete destroy versions list secrets update-secrets private-link start dev console daemon simulate help h' -a 'deploy' -d 'Deploy a new version of the agent' +complete -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from deploy' -f -l attributes -r -d '`JSON` literal or file path containing an object of string key-value pairs. Use "-" to read from stdin.' +complete -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from deploy' -f -l attribute -r -d '`KEY=VALUE` attribute pair, may be repeated. Merged with --attributes, taking precedence on conflicting keys.' complete -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from deploy' -f -l secrets -r -d 'KEY=VALUE comma separated secrets. These will be injected as environment variables into the agent. These take precedence over secrets-file.' complete -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from deploy' -l secrets-file -r -d '`FILE` containing secret KEY=VALUE pairs, one per line. These will be injected as environment variables into the agent.' complete -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from deploy' -f -l secret-mount -r -d 'Local path to a secret file to be mounted on agent environment' @@ -91,6 +93,7 @@ complete -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcomma complete -x -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from deploy; and not __fish_seen_subcommand_from help h' -a 'help' -d 'Shows a list of commands or help for one command' complete -x -c lk -n '__fish_seen_subcommand_from agent a; and not __fish_seen_subcommand_from init create dockerfile config deploy status update restart rollback logs tail delete destroy versions list secrets update-secrets private-link start dev console daemon simulate help h' -a 'status' -d 'Get the status of an agent' complete -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from status' -f -l id -r -d '`ID` of the agent. If unset, and the livekit.toml file is present, will use the id found there.' +complete -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from status' -f -l json -s j -d 'Output as JSON' complete -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from status' -f -l help -s h -d 'show help' complete -x -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from status; and not __fish_seen_subcommand_from help h' -a 'help' -d 'Shows a list of commands or help for one command' complete -x -c lk -n '__fish_seen_subcommand_from agent a; and not __fish_seen_subcommand_from init create dockerfile config deploy status update restart rollback logs tail delete destroy versions list secrets update-secrets private-link start dev console daemon simulate help h' -a 'update' -d 'Update an agent metadata and secrets. This will restart the agent.' @@ -120,14 +123,19 @@ complete -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcomma complete -x -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from delete destroy; and not __fish_seen_subcommand_from help h' -a 'help' -d 'Shows a list of commands or help for one command' complete -x -c lk -n '__fish_seen_subcommand_from agent a; and not __fish_seen_subcommand_from init create dockerfile config deploy status update restart rollback logs tail delete destroy versions list secrets update-secrets private-link start dev console daemon simulate help h' -a 'versions' -d 'List versions of an agent' complete -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from versions' -f -l id -r -d '`ID` of the agent. If unset, and the livekit.toml file is present, will use the id found there.' +complete -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from versions' -f -l attribute -r -d '`KEY=VALUE` attribute pair, may be repeated. Merged with --attributes, taking precedence on conflicting keys.' +complete -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from versions' -f -l attributes -r -d '`JSON` literal or file path containing an object of string key-value pairs. Use "-" to read from stdin.' +complete -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from versions' -f -l json -s j -d 'Output as JSON' complete -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from versions' -f -l help -s h -d 'show help' complete -x -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from versions; and not __fish_seen_subcommand_from help h' -a 'help' -d 'Shows a list of commands or help for one command' complete -x -c lk -n '__fish_seen_subcommand_from agent a; and not __fish_seen_subcommand_from init create dockerfile config deploy status update restart rollback logs tail delete destroy versions list secrets update-secrets private-link start dev console daemon simulate help h' -a 'list' -d 'List all LiveKit Cloud Agents' complete -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from list' -f -l id -r -d '`IDs` of agent(s)' +complete -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from list' -f -l json -s j -d 'Output as JSON' complete -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from list' -f -l help -s h -d 'show help' complete -x -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from list; and not __fish_seen_subcommand_from help h' -a 'help' -d 'Shows a list of commands or help for one command' complete -x -c lk -n '__fish_seen_subcommand_from agent a; and not __fish_seen_subcommand_from init create dockerfile config deploy status update restart rollback logs tail delete destroy versions list secrets update-secrets private-link start dev console daemon simulate help h' -a 'secrets' -d 'List secrets for an agent' complete -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from secrets' -f -l id -r -d '`ID` of the agent. If unset, and the livekit.toml file is present, will use the id found there.' +complete -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from secrets' -f -l json -s j -d 'Output as JSON' complete -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from secrets' -f -l help -s h -d 'show help' complete -x -c lk -n '__fish_seen_subcommand_from agent a; and __fish_seen_subcommand_from secrets; and not __fish_seen_subcommand_from help h' -a 'help' -d 'Shows a list of commands or help for one command' complete -x -c lk -n '__fish_seen_subcommand_from agent a; and not __fish_seen_subcommand_from init create dockerfile config deploy status update restart rollback logs tail delete destroy versions list secrets update-secrets private-link start dev console daemon simulate help h' -a 'update-secrets' -d 'Update secrets for an agent, will cause a re-start of the agent.' diff --git a/cmd/lk/agent.go b/cmd/lk/agent.go index a61f6cb5..1f7f70b6 100644 --- a/cmd/lk/agent.go +++ b/cmd/lk/agent.go @@ -16,9 +16,11 @@ package main import ( "context" + "encoding/json" "errors" "fmt" "io" + "maps" "os" "path/filepath" "regexp" @@ -62,6 +64,18 @@ var ( Required: false, } + attributesFlag = &cli.StringFlag{ + Name: "attributes", + Usage: "`JSON` literal or file path containing an object of string key-value pairs. Use \"-\" to read from stdin.", + Required: false, + } + + attributeFlag = &cli.StringSliceFlag{ + Name: "attribute", + Usage: "`KEY=VALUE` attribute pair, may be repeated. Merged with --attributes, taking precedence on conflicting keys.", + Required: false, + } + secretsFileFlag = &cli.StringFlag{ Name: "secrets-file", Usage: "`FILE` containing secret KEY=VALUE pairs, one per line. These will be injected as environment variables into the agent.", @@ -221,6 +235,8 @@ var ( Before: createAgentClient, Action: deployAgent, Flags: []cli.Flag{ + attributesFlag, + attributeFlag, secretsFlag, secretsFileFlag, secretsMountFlag, @@ -242,6 +258,7 @@ var ( Action: getAgentStatus, Flags: []cli.Flag{ idFlag(false), + jsonFlag, }, ArgsUsage: "[working-dir]", }, @@ -317,6 +334,9 @@ var ( Action: listAgentVersions, Flags: []cli.Flag{ idFlag(false), + attributeFlag, + attributesFlag, + jsonFlag, }, ArgsUsage: "[working-dir]", }, @@ -327,6 +347,7 @@ var ( Before: createAgentClient, Flags: []cli.Flag{ idSliceFlag, + jsonFlag, }, }, { @@ -336,6 +357,7 @@ var ( Action: listAgentSecrets, Flags: []cli.Flag{ idFlag(false), + jsonFlag, }, ArgsUsage: "[working-dir]", }, @@ -755,6 +777,11 @@ func deployAgent(ctx context.Context, cmd *cli.Command) error { buildContext, cancel := context.WithTimeout(ctx, buildTimeout) defer cancel() + attrs, err := resolveAttributes(cmd) + if err != nil { + return err + } + secrets, err := requireSecrets(ctx, cmd, false, true) if err != nil { return err @@ -806,7 +833,7 @@ func deployAgent(ctx context.Context, cmd *cli.Command) error { } excludeFiles := []string{fmt.Sprintf("**/%s", config.LiveKitTOMLFile)} - if err := agentsClient.DeployAgent(buildContext, agentId, os.DirFS(workingDir), secrets, excludeFiles, os.Stderr); err != nil { + if err := agentsClient.DeployAgent(buildContext, agentId, os.DirFS(workingDir), secrets, attrs, excludeFiles, os.Stderr); err != nil { if twerr, ok := err.(twirp.Error); ok { return fmt.Errorf("unable to deploy agent: %s", twerr.Msg()) } @@ -876,6 +903,11 @@ func getAgentStatus(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("no agents found") } + if cmd.Bool("json") { + util.PrintJSON(res) + return nil + } + var rows [][]string for _, agent := range res.Agents { for _, regionalAgent := range agent.AgentDeployments { @@ -1085,6 +1117,39 @@ func deleteAgent(ctx context.Context, cmd *cli.Command) error { return nil } +// resolveAttributes merges attribute inputs from the --attributes JSON flag +// (literal, file path, or "-" for stdin) and the repeatable --attribute +// key=value flag. The key=value pairs take precedence over the JSON object on +// conflicting keys. Returns nil when neither flag is set. +func resolveAttributes(cmd *cli.Command) (map[string]string, error) { + attrs := map[string]string{} + if cmd.IsSet(attributesFlag.Name) { + if _, err := ReadJSONFileOrLiteral(cmd.String(attributesFlag.Name), &attrs); err != nil { + return nil, err + } + } + pairs, err := parseKeyValuePairs(cmd, attributeFlag.Name) + if err != nil { + return nil, err + } + maps.Copy(attrs, pairs) + if len(attrs) == 0 { + return nil, nil + } + return attrs, nil +} + +// attributesMatch reports whether attrs contains every key-value pair in +// filter. Extra keys in attrs are allowed, so the match is inclusive. +func attributesMatch(attrs, filter map[string]string) bool { + for k, want := range filter { + if got, ok := attrs[k]; !ok || got != want { + return false + } + } + return true +} + func listAgentVersions(ctx context.Context, cmd *cli.Command) error { agentID, err := getAgentID(ctx, cmd, workingDir, tomlFilename, false) if err != nil { @@ -1103,11 +1168,28 @@ func listAgentVersions(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("unable to list agent versions: %w", err) } + // Filter to versions containing all requested attributes. Extra attributes + // on a version are allowed; the filter is inclusive, not exclusive. + attrFilter, err := resolveAttributes(cmd) + if err != nil { + return err + } + if len(attrFilter) > 0 { + versions.Versions = slices.DeleteFunc(versions.Versions, func(v *lkproto.AgentVersion) bool { + return !attributesMatch(v.Attributes, attrFilter) + }) + } + // Sort versions by created date descending slices.SortFunc(versions.Versions, func(a, b *lkproto.AgentVersion) int { return b.CreatedAt.AsTime().Compare(a.CreatedAt.AsTime()) }) + if cmd.Bool("json") { + util.PrintJSON(versions) + return nil + } + showDigest := false for _, v := range versions.Versions { if v.Attributes["image_digest"] != "" { @@ -1116,17 +1198,22 @@ func listAgentVersions(ctx context.Context, cmd *cli.Command) error { } } - headers := []string{"Version", "Current", "Status", "Created At", "Deployed At"} + headers := []string{"Version", "Current", "Status", "Attributes", "Created At", "Deployed At"} if showDigest { headers = append(headers, "Digest") } table := util.CreateTable().Headers(headers...) for _, version := range versions.Versions { + attrs, err := json.Marshal(version.Attributes) + if err != nil || len(version.Attributes) == 0 { + attrs = []byte("--") + } row := []string{ version.Version, fmt.Sprintf("%t", version.Current), version.Status, + string(attrs), version.CreatedAt.AsTime().Format(time.RFC3339), version.DeployedAt.AsTime().Format(time.RFC3339), } @@ -1169,15 +1256,20 @@ func listAgents(ctx context.Context, cmd *cli.Command) error { items = agents.Agents } + slices.SortFunc(items, func(a, b *lkproto.AgentInfo) int { + return b.DeployedAt.AsTime().Compare(a.DeployedAt.AsTime()) + }) + + if cmd.Bool("json") { + util.PrintJSON(&lkproto.ListAgentsResponse{Agents: items}) + return nil + } + if len(items) == 0 { out.Status("No agents found") return nil } - slices.SortFunc(items, func(a, b *lkproto.AgentInfo) int { - return b.DeployedAt.AsTime().Compare(a.DeployedAt.AsTime()) - }) - var rows [][]string for _, agent := range items { var regions []string @@ -1219,15 +1311,25 @@ func listAgentSecrets(ctx context.Context, cmd *cli.Command) error { return fmt.Errorf("unable to list agent secrets: %w", err) } - // TODO (steveyoon): show secret.Kind.String() once cloud-agents is released - table := util.CreateTable(). - Headers("Name", "Created At", "Updated At") - + // NOTE: Maybe these should be omitted on the server side? + visible := make([]*lkproto.AgentSecret, 0, len(secrets.Secrets)) for _, secret := range secrets.Secrets { - // NOTE: Maybe these should be omitted on the server side? if slices.Contains(ignoredSecrets, secret.Name) { continue } + visible = append(visible, secret) + } + + if cmd.Bool("json") { + util.PrintJSON(&lkproto.ListAgentSecretsResponse{Secrets: visible}) + return nil + } + + // TODO (steveyoon): show secret.Kind.String() once cloud-agents is released + table := util.CreateTable(). + Headers("Name", "Created At", "Updated At") + + for _, secret := range visible { table.Row(secret.Name, secret.CreatedAt.AsTime().Format(time.RFC3339), secret.UpdatedAt.AsTime().Format(time.RFC3339)) } diff --git a/cmd/lk/agent_test.go b/cmd/lk/agent_test.go index 1d2551f6..1bfe40cd 100644 --- a/cmd/lk/agent_test.go +++ b/cmd/lk/agent_test.go @@ -456,3 +456,149 @@ func TestRequireSecrets_QuietSuppressesStatus(t *testing.T) { }) } } + +func TestAttributesMatch(t *testing.T) { + tests := []struct { + name string + attrs map[string]string + filter map[string]string + want bool + }{ + { + name: "empty filter matches anything", + attrs: map[string]string{"env": "prod"}, + filter: nil, + want: true, + }, + { + name: "empty filter matches empty attrs", + attrs: nil, + filter: map[string]string{}, + want: true, + }, + { + name: "single matching pair", + attrs: map[string]string{"env": "prod"}, + filter: map[string]string{"env": "prod"}, + want: true, + }, + { + name: "extra attributes on version are allowed", + attrs: map[string]string{"env": "prod", "region": "us-east", "team": "core"}, + filter: map[string]string{"env": "prod"}, + want: true, + }, + { + name: "all filter pairs must match", + attrs: map[string]string{"env": "prod", "region": "us-east"}, + filter: map[string]string{"env": "prod", "region": "us-east"}, + want: true, + }, + { + name: "missing key fails", + attrs: map[string]string{"env": "prod"}, + filter: map[string]string{"region": "us-east"}, + want: false, + }, + { + name: "mismatched value fails", + attrs: map[string]string{"env": "staging"}, + filter: map[string]string{"env": "prod"}, + want: false, + }, + { + name: "partial match fails when one pair differs", + attrs: map[string]string{"env": "prod", "region": "eu-west"}, + filter: map[string]string{"env": "prod", "region": "us-east"}, + want: false, + }, + { + name: "filter against nil attrs fails", + attrs: nil, + filter: map[string]string{"env": "prod"}, + want: false, + }, + { + name: "empty string value must match exactly", + attrs: map[string]string{"env": "prod"}, + filter: map[string]string{"env": ""}, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, attributesMatch(tt.attrs, tt.filter)) + }) + } +} + +func TestResolveAttributes(t *testing.T) { + tests := []struct { + name string + args []string + want map[string]string + wantErr bool + }{ + { + name: "neither flag set returns nil", + args: nil, + want: nil, + }, + { + name: "json only", + args: []string{"--attributes", `{"env":"prod","region":"us-east"}`}, + want: map[string]string{"env": "prod", "region": "us-east"}, + }, + { + name: "pairs only", + args: []string{"--attribute", "env=prod", "--attribute", "region=us-east"}, + want: map[string]string{"env": "prod", "region": "us-east"}, + }, + { + name: "merged, disjoint keys", + args: []string{"--attributes", `{"env":"prod"}`, "--attribute", "region=us-east"}, + want: map[string]string{"env": "prod", "region": "us-east"}, + }, + { + name: "pairs take precedence on conflict", + args: []string{"--attributes", `{"env":"prod","region":"us-east"}`, "--attribute", "env=staging"}, + want: map[string]string{"env": "staging", "region": "us-east"}, + }, + { + name: "invalid json surfaces error", + args: []string{"--attributes", `not json`}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var got map[string]string + var gotErr error + // Fresh flag instances per subtest: the package-level flag vars + // retain parsed state across app.Run calls, which would leak + // between subtests. Names must match what resolveAttributes reads. + app := &cli.Command{ + Name: "lk", + Flags: []cli.Flag{ + &cli.StringFlag{Name: attributesFlag.Name}, + &cli.StringSliceFlag{Name: attributeFlag.Name}, + }, + Action: func(_ context.Context, cmd *cli.Command) error { + got, gotErr = resolveAttributes(cmd) + return nil + }, + } + + require.NoError(t, app.Run(context.Background(), append([]string{"lk"}, tt.args...))) + + if tt.wantErr { + require.Error(t, gotErr) + return + } + require.NoError(t, gotErr) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/cmd/lk/proto.go b/cmd/lk/proto.go index 8e3f9705..3f698c3d 100644 --- a/cmd/lk/proto.go +++ b/cmd/lk/proto.go @@ -16,6 +16,7 @@ package main import ( "context" + "encoding/json" "errors" "fmt" "io" @@ -65,27 +66,49 @@ func ReadRequestArgOrFlag[T any, P protoType[T]](cmd *cli.Command) (*T, error) { return ReadRequestFileOrLiteral[T, P](reqFile) } -func ReadRequestFileOrLiteral[T any, P protoType[T]](pathOrLiteral string) (P, error) { - var reqBytes []byte - var err error - - // This allows us to read JSON from either CLI arg or FS +// readFileOrLiteral resolves the input to raw bytes, accepting a literal +// value, a filesystem path, or "-" to read from stdin. +func readFileOrLiteral(pathOrLiteral string) ([]byte, error) { if pathOrLiteral == "-" { - reqBytes, err = io.ReadAll(os.Stdin) - } else if _, err = os.Stat(pathOrLiteral); err == nil { - reqBytes, err = os.ReadFile(pathOrLiteral) - } else { - reqBytes = []byte(pathOrLiteral) + return io.ReadAll(os.Stdin) + } + if _, err := os.Stat(pathOrLiteral); err == nil { + return os.ReadFile(pathOrLiteral) } + return []byte(pathOrLiteral), nil +} + +// ReadJSONFileOrLiteral reads JSON from a literal string, a file path, or +// stdin (when pathOrLiteral is "-"). If a non-nil target pointer is provided, +// the JSON is decoded into it. The raw, validated JSON is always returned. +func ReadJSONFileOrLiteral(pathOrLiteral string, target ...any) (json.RawMessage, error) { + b, err := readFileOrLiteral(pathOrLiteral) if err != nil { return nil, err } - var req P = new(T) - err = unmarshaller.Unmarshal(reqBytes, req) + if len(target) > 0 && target[0] != nil { + if err := json.Unmarshal(b, target[0]); err != nil { + return nil, err + } + } else if !json.Valid(b) { + return nil, errors.New("invalid JSON input") + } + + return json.RawMessage(b), nil +} + +func ReadRequestFileOrLiteral[T any, P protoType[T]](pathOrLiteral string) (P, error) { + // This allows us to read JSON from either CLI arg or FS + reqBytes, err := readFileOrLiteral(pathOrLiteral) if err != nil { return nil, err } + + var req P = new(T) + if err := unmarshaller.Unmarshal(reqBytes, req); err != nil { + return nil, err + } return req, nil } diff --git a/cmd/lk/proto_test.go b/cmd/lk/proto_test.go new file mode 100644 index 00000000..4c535b9a --- /dev/null +++ b/cmd/lk/proto_test.go @@ -0,0 +1,146 @@ +// Copyright 2024 LiveKit, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// withStdin replaces os.Stdin with a pipe carrying the given content for the +// duration of the test, restoring the original afterwards. +func withStdin(t *testing.T, content string) { + t.Helper() + r, w, err := os.Pipe() + require.NoError(t, err) + + _, err = w.WriteString(content) + require.NoError(t, err) + require.NoError(t, w.Close()) + + prev := os.Stdin + os.Stdin = r + t.Cleanup(func() { + os.Stdin = prev + _ = r.Close() + }) +} + +func TestReadFileOrLiteral(t *testing.T) { + t.Run("literal", func(t *testing.T) { + b, err := readFileOrLiteral(`{"a":1}`) + require.NoError(t, err) + assert.JSONEq(t, `{"a":1}`, string(b)) + }) + + t.Run("file path", func(t *testing.T) { + path := filepath.Join(t.TempDir(), "input.json") + require.NoError(t, os.WriteFile(path, []byte(`{"from":"file"}`), 0o600)) + + b, err := readFileOrLiteral(path) + require.NoError(t, err) + assert.JSONEq(t, `{"from":"file"}`, string(b)) + }) + + t.Run("stdin", func(t *testing.T) { + withStdin(t, `{"from":"stdin"}`) + + b, err := readFileOrLiteral("-") + require.NoError(t, err) + assert.JSONEq(t, `{"from":"stdin"}`, string(b)) + }) + + t.Run("missing file falls back to literal", func(t *testing.T) { + // A path that does not exist is treated as a literal value, not an error. + b, err := readFileOrLiteral("/no/such/file/here.json") + require.NoError(t, err) + assert.Equal(t, "/no/such/file/here.json", string(b)) + }) +} + +func TestReadJSONFileOrLiteral(t *testing.T) { + t.Run("raw literal", func(t *testing.T) { + raw, err := ReadJSONFileOrLiteral(`{"a":1,"b":"two"}`) + require.NoError(t, err) + assert.JSONEq(t, `{"a":1,"b":"two"}`, string(raw)) + }) + + t.Run("raw from file", func(t *testing.T) { + path := filepath.Join(t.TempDir(), "input.json") + require.NoError(t, os.WriteFile(path, []byte(`{"a":1}`), 0o600)) + + raw, err := ReadJSONFileOrLiteral(path) + require.NoError(t, err) + assert.JSONEq(t, `{"a":1}`, string(raw)) + }) + + t.Run("raw from stdin", func(t *testing.T) { + withStdin(t, `{"a":1}`) + + raw, err := ReadJSONFileOrLiteral("-") + require.NoError(t, err) + assert.JSONEq(t, `{"a":1}`, string(raw)) + }) + + t.Run("invalid JSON without target", func(t *testing.T) { + _, err := ReadJSONFileOrLiteral(`not json`) + require.Error(t, err) + }) + + t.Run("decode into target", func(t *testing.T) { + type shape struct { + Name string `json:"name"` + Count int `json:"count"` + } + var got shape + raw, err := ReadJSONFileOrLiteral(`{"name":"widget","count":3}`, &got) + require.NoError(t, err) + + assert.Equal(t, shape{Name: "widget", Count: 3}, got) + // The raw bytes are still returned alongside the decoded value. + assert.JSONEq(t, `{"name":"widget","count":3}`, string(raw)) + }) + + t.Run("decode error surfaces", func(t *testing.T) { + var got struct { + Count int `json:"count"` + } + _, err := ReadJSONFileOrLiteral(`{"count":"not-a-number"}`, &got) + require.Error(t, err) + }) + + t.Run("nil target falls back to validation", func(t *testing.T) { + // An explicit nil target behaves like no target: raw bytes, validated. + var target any + raw, err := ReadJSONFileOrLiteral(`{"a":1}`, target) + require.NoError(t, err) + assert.JSONEq(t, `{"a":1}`, string(raw)) + }) + + t.Run("returns json.RawMessage", func(t *testing.T) { + raw, err := ReadJSONFileOrLiteral(`[1,2,3]`) + require.NoError(t, err) + + // The result round-trips as a json.RawMessage. + var nums []int + require.NoError(t, json.Unmarshal(raw, &nums)) + assert.Equal(t, []int{1, 2, 3}, nums) + }) +} diff --git a/go.mod b/go.mod index 680f91ea..52535c07 100644 --- a/go.mod +++ b/go.mod @@ -19,21 +19,21 @@ require ( github.com/google/go-containerregistry v0.20.7 github.com/google/go-querystring v1.2.0 github.com/joho/godotenv v1.5.1 - github.com/livekit/protocol v1.48.1-0.20260625001142-39fc751df610 - github.com/livekit/server-sdk-go/v2 v2.16.7 + github.com/livekit/protocol v1.49.0 + github.com/livekit/server-sdk-go/v2 v2.16.8-0.20260702164219-6126610d4e22 github.com/mattn/go-isatty v0.0.22 github.com/moby/patternmatcher v0.6.1 github.com/modelcontextprotocol/go-sdk v1.6.1 github.com/pelletier/go-toml v1.9.5 github.com/pion/rtcp v1.2.16 github.com/pion/rtp v1.10.2 - github.com/pion/webrtc/v4 v4.2.15 + github.com/pion/webrtc/v4 v4.2.14 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/stretchr/testify v1.11.1 github.com/twitchtv/twirp v8.1.3+incompatible github.com/urfave/cli/v3 v3.9.0 go.uber.org/atomic v1.11.0 - golang.org/x/sync v0.21.0 + golang.org/x/sync v0.20.0 golang.org/x/time v0.15.0 google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af gopkg.in/yaml.v3 v3.0.1 @@ -101,7 +101,7 @@ require ( github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 // indirect github.com/containerd/console v1.0.5 // indirect github.com/containerd/containerd/api v1.10.0 // indirect - github.com/containerd/containerd/v2 v2.2.5 // indirect + github.com/containerd/containerd/v2 v2.2.4 // indirect github.com/containerd/continuity v0.4.5 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect @@ -160,7 +160,7 @@ require ( github.com/klauspost/pgzip v1.2.6 // indirect github.com/lithammer/shortuuid/v4 v4.2.0 // indirect github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731 // indirect - github.com/livekit/mediatransportutil v0.0.0-20260608063931-a3417d38cda0 // indirect + github.com/livekit/mediatransportutil v0.0.0-20260605212259-862d4a7bcb1e // indirect github.com/livekit/psrpc v0.7.2 // indirect github.com/lucasb-eyer/go-colorful v1.4.0 // indirect github.com/magefile/mage v1.17.2 // indirect @@ -185,28 +185,28 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pierrec/lz4/v4 v4.1.26 // indirect - github.com/pion/datachannel v1.6.2 // indirect + github.com/pion/datachannel v1.6.0 // indirect github.com/pion/dtls/v3 v3.1.4 // indirect github.com/pion/ice/v4 v4.2.7 // indirect github.com/pion/interceptor v0.1.45 // indirect github.com/pion/logging v0.2.4 // indirect github.com/pion/mdns/v2 v2.1.0 // indirect github.com/pion/randutil v0.1.0 // indirect - github.com/pion/sctp v1.10.3 // indirect - github.com/pion/sdp/v3 v3.0.19 // indirect - github.com/pion/srtp/v3 v3.0.12 // indirect - github.com/pion/stun/v3 v3.1.6 // indirect + github.com/pion/sctp v1.10.0 // indirect + github.com/pion/sdp/v3 v3.0.18 // indirect + github.com/pion/srtp/v3 v3.0.11 // indirect + github.com/pion/stun/v3 v3.1.4 // indirect github.com/pion/transport/v4 v4.0.2 // indirect - github.com/pion/turn/v5 v5.0.10 // indirect + github.com/pion/turn/v5 v5.0.8 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.23.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.69.0 // indirect - github.com/prometheus/procfs v0.21.0 // indirect + github.com/prometheus/common v0.68.1 // indirect + github.com/prometheus/procfs v0.20.1 // indirect github.com/puzpuzpuz/xsync/v4 v4.5.0 // indirect - github.com/redis/go-redis/v9 v9.21.0 // indirect + github.com/redis/go-redis/v9 v9.20.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/sajari/fuzzy v1.0.0 // indirect github.com/secure-systems-lab/go-securesystemslib v0.10.0 // indirect @@ -246,17 +246,17 @@ require ( go.uber.org/zap v1.28.0 // indirect go.uber.org/zap/exp v0.3.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.53.0 // indirect - golang.org/x/exp v0.0.0-20260611194520-c48552f49976 // indirect - golang.org/x/net v0.56.0 // indirect + golang.org/x/crypto v0.52.0 // indirect + golang.org/x/exp v0.0.0-20260603202125-055de637280b // indirect + golang.org/x/net v0.55.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sys v0.46.0 // indirect - golang.org/x/term v0.44.0 // indirect - golang.org/x/text v0.38.0 // indirect + golang.org/x/sys v0.45.0 // indirect + golang.org/x/term v0.43.0 // indirect + golang.org/x/text v0.37.0 // indirect google.golang.org/api v0.275.0 // indirect google.golang.org/genproto v0.0.0-20260406210006-6f92a3bedf2d // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260622175928-b703f567277d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260622175928-b703f567277d // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/grpc v1.81.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gotest.tools/v3 v3.5.2 // indirect diff --git a/go.sum b/go.sum index 3bcf3bd8..61ab2ecd 100644 --- a/go.sum +++ b/go.sum @@ -184,8 +184,8 @@ github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/q github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= github.com/containerd/containerd/api v1.10.0 h1:5n0oHYVBwN4VhoX9fFykCV9dF1/BvAXeg2F8W6UYq1o= github.com/containerd/containerd/api v1.10.0/go.mod h1:NBm1OAk8ZL+LG8R0ceObGxT5hbUYj7CzTmR3xh0DlMM= -github.com/containerd/containerd/v2 v2.2.5 h1:KTFzB02LviYmmfRmz8r9UFd+n6YlddVFK+5lbgQXUTU= -github.com/containerd/containerd/v2 v2.2.5/go.mod h1:5t2+xFv2dGd/iDYp9Z8DXB4cmWrWQi1XqxGJPS2gBzU= +github.com/containerd/containerd/v2 v2.2.4 h1:8x2UdXqww7NYqGNabQ7i1nAgB5LegzjC9KQzO/900iA= +github.com/containerd/containerd/v2 v2.2.4/go.mod h1:YBcTO8D9149QY9zNmUjy04Mhuc4DlrZQ8FIOwKZEM7o= github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= @@ -355,14 +355,14 @@ github.com/lithammer/shortuuid/v4 v4.2.0 h1:LMFOzVB3996a7b8aBuEXxqOBflbfPQAiVzkI github.com/lithammer/shortuuid/v4 v4.2.0/go.mod h1:D5noHZ2oFw/YaKCfGy0YxyE7M0wMbezmMjPdhyEFe6Y= github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731 h1:9x+U2HGLrSw5ATTo469PQPkqzdoU7be46ryiCDO3boc= github.com/livekit/mageutil v0.0.0-20250511045019-0f1ff63f7731/go.mod h1:Rs3MhFwutWhGwmY1VQsygw28z5bWcnEYmS1OG9OxjOQ= -github.com/livekit/mediatransportutil v0.0.0-20260608063931-a3417d38cda0 h1:XHNNzebIKZRkLimla/hFGrAIX5EMWHctrgt3hLw7s+I= -github.com/livekit/mediatransportutil v0.0.0-20260608063931-a3417d38cda0/go.mod h1:o8CFmAdrVwzJNOCsQCLUzXRjokkufNshnQHOe4fRaqU= -github.com/livekit/protocol v1.48.1-0.20260625001142-39fc751df610 h1:1xZzJYR6hFuZmKuaELtjDdKeEs6SFWL+I6fb7b6fLYk= -github.com/livekit/protocol v1.48.1-0.20260625001142-39fc751df610/go.mod h1:jO+y05AU9Ec4JswDyuzKCZ4bhziOS0CzMqgnbj60Dzs= +github.com/livekit/mediatransportutil v0.0.0-20260605212259-862d4a7bcb1e h1:SkgQRcG2VYEhh80Qb/zYZo8rWKJzNfJcfUQnXe6su2M= +github.com/livekit/mediatransportutil v0.0.0-20260605212259-862d4a7bcb1e/go.mod h1:o8CFmAdrVwzJNOCsQCLUzXRjokkufNshnQHOe4fRaqU= +github.com/livekit/protocol v1.49.0 h1:Q5nthDO1v7c0JHiWjMhgUQTlsKmCsBL/KCKxdHVaz00= +github.com/livekit/protocol v1.49.0/go.mod h1:jO+y05AU9Ec4JswDyuzKCZ4bhziOS0CzMqgnbj60Dzs= github.com/livekit/psrpc v0.7.2 h1:6oZ+NODJ2pLyaT6VqDq1F4Qc/3TpDUSpyphj/P9MhQc= github.com/livekit/psrpc v0.7.2/go.mod h1:rAI+m2+/cb4x9RXhLRtUx5ZwdfjjXOl4zi46IjEetaw= -github.com/livekit/server-sdk-go/v2 v2.16.7 h1:oYnp2o3YTBdL4xVkq5NpLwqnCijZVfVJU9ddFl+BW/Y= -github.com/livekit/server-sdk-go/v2 v2.16.7/go.mod h1:B3qlhVBZ4olBWRN/KxokLZE2d4LujNk4n2yYDL3+u2s= +github.com/livekit/server-sdk-go/v2 v2.16.8-0.20260702164219-6126610d4e22 h1:xPko28MMS2QCbx9mWAUS5MKgO07AyouADfKCLU+Be5E= +github.com/livekit/server-sdk-go/v2 v2.16.8-0.20260702164219-6126610d4e22/go.mod h1:5nzTfVBH2Jz+TW1SrfpqC7wrbcD1lT94KZCJ9hOMyvk= github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4= github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magefile/mage v1.17.2 h1:fyXVu1eadI8Ap1HCCNgEhJ5McIWiYhLR8uol64ZZc40= @@ -443,8 +443,8 @@ github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0 github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pierrec/lz4/v4 v4.1.26 h1:GrpZw1gZttORinvzBdXPUXATeqlJjqUG/D87TKMnhjY= github.com/pierrec/lz4/v4 v4.1.26/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= -github.com/pion/datachannel v1.6.2 h1:7EXQ8TH3vTouBUdRWYbcX2edSx9Yj6k5zl5P+qyxEPc= -github.com/pion/datachannel v1.6.2/go.mod h1:pzbdAZvyGtXbcHM1hBbsFaOTf40lZizU/dNlvVOak6E= +github.com/pion/datachannel v1.6.0 h1:XecBlj+cvsxhAMZWFfFcPyUaDZtd7IJvrXqlXD/53i0= +github.com/pion/datachannel v1.6.0/go.mod h1:ur+wzYF8mWdC+Mkis5Thosk+u/VOL287apDNEbFpsIk= github.com/pion/dtls/v3 v3.1.4 h1:QhvtMflMfu9Kf0RcDC5BJBle4caPskByrKQR6uuYqpY= github.com/pion/dtls/v3 v3.1.4/go.mod h1:cr/qotLISUw/9C1m83ZPNZtj9WnXkYLpfCptPqbkInc= github.com/pion/ice/v4 v4.2.7 h1:zDEbC6MiEdhQpF8TxBOTws+NU6ZgGpveHrQq4Lc1kao= @@ -461,22 +461,22 @@ github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo= github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo= github.com/pion/rtp v1.10.2 h1:l+f6tTDcAH6xwepaAoW791ddhuYsJlqRATOzirO04Mo= github.com/pion/rtp v1.10.2/go.mod h1:Au8fc6cEByy8RLTwKTQTEeQqDB/SJDxwL4mZuxYA5Pk= -github.com/pion/sctp v1.10.3 h1:1gBtLMA9lmwNuJkZSZJCdD5/Hz4yJs+7dAqi6ZY97QI= -github.com/pion/sctp v1.10.3/go.mod h1:7KFmTwLcoYgJs/Z+99nJvsWL0qDpuyloSI0RbAqlrz0= -github.com/pion/sdp/v3 v3.0.19 h1:1VMKs3gIkTQV5M3hNKfTAPrDXSNrYtOlmOD8+mSZUGQ= -github.com/pion/sdp/v3 v3.0.19/go.mod h1:dE5WOSlzXrtiE/iuZqe9n+AcEbOjtAd3k5m5NtlV/qU= -github.com/pion/srtp/v3 v3.0.12 h1:U7V17bckl7sI4mb3sepiojByDuBY0wNCqQE+6IlQBbc= -github.com/pion/srtp/v3 v3.0.12/go.mod h1:EeZOi/sd6glM1EXapg051gdNWO9yWT1YSsgQ4SlJkns= -github.com/pion/stun/v3 v3.1.6 h1:WnhsD0eHCiwCfKNkVx0VJJwr2Y3eV4Ueih3KJ+dfZy8= -github.com/pion/stun/v3 v3.1.6/go.mod h1:zRUghXSQU32Lx5orJsz3uYMkIihweXb3mu5gIns02fs= +github.com/pion/sctp v1.10.0 h1:qeoD6swF/2M5bYRcAGayqSbTKX3m4AW29CiQxG1+Pfg= +github.com/pion/sctp v1.10.0/go.mod h1:N20Dq6LY+JvJDAh9VVh1JELngb2rQ8dPgds5yBWiPgw= +github.com/pion/sdp/v3 v3.0.18 h1:l0bAXazKHpepazVdp+tPYnrsy9dfh7ZbT8DxesH5ZnI= +github.com/pion/sdp/v3 v3.0.18/go.mod h1:ZREGo6A9ZygQ9XkqAj5xYCQtQpif0i6Pa81HOiAdqQ8= +github.com/pion/srtp/v3 v3.0.11 h1:GiESUr54/K4UuPigfq/CvWUed80JenQAHXn0C2MQQIQ= +github.com/pion/srtp/v3 v3.0.11/go.mod h1:EeZOi/sd6glM1EXapg051gdNWO9yWT1YSsgQ4SlJkns= +github.com/pion/stun/v3 v3.1.4 h1:/7ZL0j0dmLroKOq4GfkyKQ6asByYqntwyHSp5sYLcGY= +github.com/pion/stun/v3 v3.1.4/go.mod h1:ET7PFiXo1nrD2ZNVpbEHDuT0kCPVXhKmyWdiePNMw/U= github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM= github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ= github.com/pion/transport/v4 v4.0.2 h1:ifYlPqNwsy6aKQ9y8yzxXlHae5431ZrH2avkD/Rn6Tk= github.com/pion/transport/v4 v4.0.2/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM= -github.com/pion/turn/v5 v5.0.10 h1:mOMZjudflXpte5OsCnXztpUKwNXcpXIAzMBnq9TXOSQ= -github.com/pion/turn/v5 v5.0.10/go.mod h1:u3XjBqy2Z4+NhCUpDoOSsNuQDrPLvKStlCGWk6sTQ1E= -github.com/pion/webrtc/v4 v4.2.15 h1:Ir/MauNFCfg+kgyBYPQLiGdVWFlzEcLxqtuzAkYkky0= -github.com/pion/webrtc/v4 v4.2.15/go.mod h1:CPTcyLfIzC4scOkQ4UY4pj6WvbUGhcNLIpK28cP5h6M= +github.com/pion/turn/v5 v5.0.8 h1:pZUCtmwWCMkrRKqh/8pL3WoGADXBe0/lOPkN7oqFjK8= +github.com/pion/turn/v5 v5.0.8/go.mod h1:1VwvxElZaOdJU0liJ/WUSm/Tsh+n2OxS5ISSDxgOWxU= +github.com/pion/webrtc/v4 v4.2.14 h1:Q6zMs+fSDsYuhZcNlvFGBxCOMHVV9oYcDa6O9/HIGTc= +github.com/pion/webrtc/v4 v4.2.14/go.mod h1:87NVKP86+g4OMrRxWhjWfUjeXP4JrV6RTlUrIW+/Jak= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -490,14 +490,14 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.69.0 h1:OA85nJQS/T/MaYh/Q2CcgDKSGWqNIgrBDvDH85CuiNk= -github.com/prometheus/common v0.69.0/go.mod h1:ZzL3f6u94qUxh9p+tJTrF+FvBS1XXbbRAZCQkytAL0Y= -github.com/prometheus/procfs v0.21.0 h1:Qh/e6TlBjZf+XLLqNCqFGmCU6Kj/2Bu7kj3oAc0UnXc= -github.com/prometheus/procfs v0.21.0/go.mod h1:aB55Cww9pdSJVHk0hUf0inxWyyjPogFIjmHKYgMKmtY= +github.com/prometheus/common v0.68.1 h1:omjRRl4QP4komogpXuhfeOiisQg7xdy8VM1UY+pStaY= +github.com/prometheus/common v0.68.1/go.mod h1:ZzL3f6u94qUxh9p+tJTrF+FvBS1XXbbRAZCQkytAL0Y= +github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= +github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= github.com/puzpuzpuz/xsync/v4 v4.5.0 h1:vOSWu6b57/emh+L/Cw0BeQfvxa/cogFywXHeGUxQxAg= github.com/puzpuzpuz/xsync/v4 v4.5.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo= -github.com/redis/go-redis/v9 v9.21.0 h1:FPBE4hhbAke+TLmcY3WkpbDffJEomdqPn3HYiqAtL9E= -github.com/redis/go-redis/v9 v9.21.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA= +github.com/redis/go-redis/v9 v9.20.0 h1:WnQYxLkgO2xiXTCJY0ldIiI8dNqCDlQAG+AtaH7a2a0= +github.com/redis/go-redis/v9 v9.20.0/go.mod h1:v/M13XI1PVCDcm01VtPFOADfZtHf8YW3baQf57KlIkA= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= @@ -622,48 +622,48 @@ go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= -golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= -golang.org/x/exp v0.0.0-20260611194520-c48552f49976 h1:X8Hz2ImujgbmetVuW+w2YkyZChE3cBpZi2P158rTG9M= -golang.org/x/exp v0.0.0-20260611194520-c48552f49976/go.mod h1:vnf4pv9iKZXY58sQE1L86zmNWJ4159e1RkcWiLCkeEY= +golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= +golang.org/x/exp v0.0.0-20260603202125-055de637280b h1:v1uXiEBHo8QA0LiGCo7UgHMzHT4Kdfpl2zmtH5vaP1Q= +golang.org/x/exp v0.0.0-20260603202125-055de637280b/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= -golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= -golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= -golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= -golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= -golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= +golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= +golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= -golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk= -golang.org/x/tools v0.46.0/go.mod h1:FrD85F8l+NWL+9XWBSyVSHO6Ne4jutsfIFba7AWQ5Ys= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -674,10 +674,10 @@ google.golang.org/api v0.275.0 h1:vfY5d9vFVJeWEZT65QDd9hbndr7FyZ2+6mIzGAh71NI= google.golang.org/api v0.275.0/go.mod h1:Fnag/EWUPIcJXuIkP1pjoTgS5vdxlk3eeemL7Do6bvw= google.golang.org/genproto v0.0.0-20260406210006-6f92a3bedf2d h1:N1Ec54vZnIPd7MnxRiYLW+oY4fDR4BOS/LrssdD9+ek= google.golang.org/genproto v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:c2hJ1grtnH0xUiEKGDGkjGNTJ1Hy2LrblyKOHF0sqRM= -google.golang.org/genproto/googleapis/api v0.0.0-20260622175928-b703f567277d h1:xr2lwHI91bn3UiXcnyzRMQjp2LRiM8wEHzwUaE0YhTs= -google.golang.org/genproto/googleapis/api v0.0.0-20260622175928-b703f567277d/go.mod h1:O0ZOWSrfWfJ+Z5HbwZ+wNtHsg/vk1k2C/w67eww8PfQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260622175928-b703f567277d h1:mpAgMyM9vQHxycBlDq50y1VHpfSfVwzXvrQKtYbXuUY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260622175928-b703f567277d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= +google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= diff --git a/version.go b/version.go index 08ace50e..f0add2b6 100644 --- a/version.go +++ b/version.go @@ -15,5 +15,5 @@ package livekitcli const ( - Version = "2.16.7" + Version = "2.17.0" )