Skip to content

Commit 929fc86

Browse files
add private-link subcommand under agent
1 parent b1b0369 commit 929fc86

File tree

5 files changed

+406
-6
lines changed

5 files changed

+406
-6
lines changed

cmd/lk/agent.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ var (
345345
DisableSliceFlagSeparator: true,
346346
ArgsUsage: "[working-dir]",
347347
},
348+
privateLinkCommands,
348349
},
349350
},
350351
}
@@ -363,6 +364,7 @@ func createAgentClient(ctx context.Context, cmd *cli.Command) (context.Context,
363364

364365
func createAgentClientWithOpts(ctx context.Context, cmd *cli.Command, opts ...loadOption) (context.Context, error) {
365366
var err error
367+
fmt.Printf("creating agent client")
366368

367369
if _, err := requireProjectWithOpts(ctx, cmd, opts...); err != nil {
368370
return ctx, err

cmd/lk/agent_private_link.go

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strconv"
7+
8+
"github.com/livekit/livekit-cli/v2/pkg/util"
9+
lkproto "github.com/livekit/protocol/livekit"
10+
"github.com/twitchtv/twirp"
11+
"github.com/urfave/cli/v3"
12+
)
13+
14+
var privateLinkCommands = &cli.Command{
15+
Name: "private-link",
16+
Usage: "Manage private links for agents",
17+
Commands: []*cli.Command{
18+
{
19+
Name: "create",
20+
Usage: "Create a private link",
21+
Before: createAgentClient,
22+
Action: createPrivateLink,
23+
Flags: []cli.Flag{
24+
&cli.StringFlag{
25+
Name: "name",
26+
Usage: "Private link name",
27+
Required: true,
28+
},
29+
&cli.StringFlag{
30+
Name: "region",
31+
Usage: "LiveKit region",
32+
Required: true,
33+
},
34+
&cli.UintFlag{
35+
Name: "port",
36+
Usage: "Destination port",
37+
Required: true,
38+
},
39+
&cli.StringFlag{
40+
Name: "aws-endpoint",
41+
Usage: "AWS VPC endpoint service name",
42+
Required: true,
43+
},
44+
jsonFlag,
45+
},
46+
},
47+
{
48+
Name: "list",
49+
Usage: "List private links with health",
50+
Before: createAgentClient,
51+
Action: listPrivateLinks,
52+
Flags: []cli.Flag{
53+
jsonFlag,
54+
},
55+
},
56+
{
57+
Name: "delete",
58+
Usage: "Delete a private link",
59+
Before: createAgentClient,
60+
Action: deletePrivateLink,
61+
Flags: []cli.Flag{
62+
&cli.StringFlag{
63+
Name: "id",
64+
Usage: "Private link ID",
65+
Required: true,
66+
},
67+
jsonFlag,
68+
},
69+
},
70+
{
71+
Name: "health-status",
72+
Usage: "Get private link health status",
73+
Before: createAgentClient,
74+
Action: getPrivateLinkHealthStatus,
75+
Flags: []cli.Flag{
76+
&cli.StringFlag{
77+
Name: "id",
78+
Usage: "Private link ID",
79+
Required: true,
80+
},
81+
jsonFlag,
82+
},
83+
},
84+
},
85+
}
86+
87+
func buildCreatePrivateLinkRequest(name, region string, port uint32, awsEndpoint string) *lkproto.CreatePrivateLinkRequest {
88+
return &lkproto.CreatePrivateLinkRequest{
89+
Name: name,
90+
Region: region,
91+
Port: port,
92+
Config: &lkproto.CreatePrivateLinkRequest_Aws{
93+
Aws: &lkproto.CreatePrivateLinkRequest_AWSCreateConfig{
94+
Endpoint: awsEndpoint,
95+
},
96+
},
97+
}
98+
}
99+
100+
func privateLinkServiceDNS(name, projectID string) string {
101+
return fmt.Sprintf("%s-%s.plg.svc", name, projectID)
102+
}
103+
104+
func buildPrivateLinkListRows(links []*lkproto.PrivateLink, healthByID map[string]*lkproto.PrivateLinkHealthStatus, healthErrByID map[string]error) [][]string {
105+
var rows [][]string
106+
for _, link := range links {
107+
if link == nil {
108+
continue
109+
}
110+
111+
status := lkproto.PrivateLinkHealthStatus_PRIVATE_LINK_ATTACHMENT_HEALTH_STATUS_UNKNOWN.String()
112+
updatedAt := "-"
113+
114+
if err, ok := healthErrByID[link.PrivateLinkId]; ok && err != nil {
115+
status = "ERROR"
116+
updatedAt = err.Error()
117+
} else if health, ok := healthByID[link.PrivateLinkId]; ok && health != nil {
118+
status = health.Status.String()
119+
if health.UpdatedAt != nil {
120+
updatedAt = health.UpdatedAt.AsTime().UTC().Format("2006-01-02T15:04:05Z07:00")
121+
}
122+
}
123+
124+
rows = append(rows, []string{
125+
link.PrivateLinkId,
126+
link.Name,
127+
link.Region,
128+
strconv.FormatUint(uint64(link.Port), 10),
129+
status,
130+
updatedAt,
131+
})
132+
}
133+
return rows
134+
}
135+
136+
func formatPrivateLinkClientError(action string, err error) error {
137+
if twerr, ok := err.(twirp.Error); ok {
138+
return fmt.Errorf("unable to %s private link: %s", action, twerr.Msg())
139+
}
140+
return fmt.Errorf("unable to %s private link: %w", action, err)
141+
}
142+
143+
func createPrivateLink(ctx context.Context, cmd *cli.Command) error {
144+
req := buildCreatePrivateLinkRequest(cmd.String("name"), cmd.String("region"), uint32(cmd.Uint("port")), cmd.String("aws-endpoint"))
145+
resp, err := agentsClient.CreatePrivateLink(ctx, req)
146+
if err != nil {
147+
return formatPrivateLinkClientError("create", err)
148+
}
149+
150+
if cmd.Bool("json") {
151+
util.PrintJSON(resp)
152+
return nil
153+
}
154+
155+
if resp.PrivateLink == nil {
156+
fmt.Println("Private link created")
157+
return nil
158+
}
159+
160+
fmt.Printf("Created private link [%s]\n", util.Accented(resp.PrivateLink.PrivateLinkId))
161+
if project != nil && project.ProjectId != "" {
162+
fmt.Printf("Gateway DNS [%s]\n", util.Accented(privateLinkServiceDNS(req.Name, project.ProjectId)))
163+
}
164+
return nil
165+
}
166+
167+
func listPrivateLinks(ctx context.Context, cmd *cli.Command) error {
168+
resp, err := agentsClient.ListPrivateLinks(ctx, &lkproto.ListPrivateLinksRequest{})
169+
if err != nil {
170+
return formatPrivateLinkClientError("list", err)
171+
}
172+
173+
healthByID := make(map[string]*lkproto.PrivateLinkHealthStatus, len(resp.Items))
174+
healthErrByID := make(map[string]error)
175+
for _, link := range resp.Items {
176+
if link == nil || link.PrivateLinkId == "" {
177+
continue
178+
}
179+
health, healthErr := agentsClient.GetPrivateLinkHealthStatus(ctx, &lkproto.GetPrivateLinkHealthStatusRequest{
180+
PrivateLinkId: link.PrivateLinkId,
181+
})
182+
if healthErr != nil {
183+
healthErrByID[link.PrivateLinkId] = healthErr
184+
continue
185+
}
186+
if health != nil {
187+
healthByID[link.PrivateLinkId] = health.Value
188+
}
189+
}
190+
191+
if cmd.Bool("json") {
192+
type privateLinkWithHealth struct {
193+
PrivateLink *lkproto.PrivateLink `json:"private_link"`
194+
Health *lkproto.PrivateLinkHealthStatus `json:"health"`
195+
HealthError string `json:"health_error,omitempty"`
196+
}
197+
items := make([]privateLinkWithHealth, 0, len(resp.Items))
198+
for _, link := range resp.Items {
199+
if link == nil {
200+
continue
201+
}
202+
entry := privateLinkWithHealth{
203+
PrivateLink: link,
204+
Health: healthByID[link.PrivateLinkId],
205+
}
206+
if err := healthErrByID[link.PrivateLinkId]; err != nil {
207+
entry.HealthError = err.Error()
208+
}
209+
items = append(items, entry)
210+
}
211+
util.PrintJSON(map[string]any{"items": items})
212+
return nil
213+
}
214+
215+
if len(resp.Items) == 0 {
216+
fmt.Println("No private links found")
217+
return nil
218+
}
219+
220+
rows := buildPrivateLinkListRows(resp.Items, healthByID, healthErrByID)
221+
table := util.CreateTable().Headers("ID", "Name", "Region", "Port", "Health", "Updated At").Rows(rows...)
222+
fmt.Println(table)
223+
return nil
224+
}
225+
226+
func deletePrivateLink(ctx context.Context, cmd *cli.Command) error {
227+
privateLinkID := cmd.String("id")
228+
resp, err := agentsClient.DestroyPrivateLink(ctx, &lkproto.DestroyPrivateLinkRequest{
229+
PrivateLinkId: privateLinkID,
230+
})
231+
if err != nil {
232+
return formatPrivateLinkClientError("delete", err)
233+
}
234+
235+
if cmd.Bool("json") {
236+
util.PrintJSON(resp)
237+
return nil
238+
}
239+
fmt.Printf("Deleted private link [%s]\n", util.Accented(privateLinkID))
240+
return nil
241+
}
242+
243+
func getPrivateLinkHealthStatus(ctx context.Context, cmd *cli.Command) error {
244+
privateLinkID := cmd.String("id")
245+
resp, err := agentsClient.GetPrivateLinkHealthStatus(ctx, &lkproto.GetPrivateLinkHealthStatusRequest{
246+
PrivateLinkId: privateLinkID,
247+
})
248+
if err != nil {
249+
return formatPrivateLinkClientError("get health status for", err)
250+
}
251+
if cmd.Bool("json") {
252+
util.PrintJSON(resp)
253+
return nil
254+
}
255+
if resp == nil || resp.Value == nil {
256+
return fmt.Errorf("health status unavailable for private link [%s]", privateLinkID)
257+
}
258+
updatedAt := "-"
259+
if resp.Value.UpdatedAt != nil {
260+
updatedAt = resp.Value.UpdatedAt.AsTime().UTC().Format("2006-01-02T15:04:05Z07:00")
261+
}
262+
table := util.CreateTable().
263+
Headers("ID", "Health", "Updated At").
264+
Row(privateLinkID, resp.Value.Status.String(), updatedAt)
265+
fmt.Println(table)
266+
return nil
267+
}
268+

0 commit comments

Comments
 (0)