Skip to content

Commit 76912df

Browse files
committed
Send command invocation info in CLI
1 parent a1853f2 commit 76912df

File tree

5 files changed

+69
-14
lines changed

5 files changed

+69
-14
lines changed

cli/clibase/cmd.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,16 @@ func (c *Cmd) FullUsage() string {
145145
return strings.Join(uses, " ")
146146
}
147147

148+
// FullOptions returns the options of the command and its parents.
149+
func (c *Cmd) FullOptions() OptionSet {
150+
var opts OptionSet
151+
if c.Parent != nil {
152+
opts = append(opts, c.Parent.FullOptions()...)
153+
}
154+
opts = append(opts, c.Options...)
155+
return opts
156+
}
157+
148158
// Invoke creates a new invocation of the command, with
149159
// stdio discarded.
150160
//

cli/root.go

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package cli
22

33
import (
44
"context"
5+
"encoding/base64"
6+
"encoding/json"
57
"errors"
68
"flag"
79
"fmt"
@@ -33,6 +35,7 @@ import (
3335
"github.com/coder/coder/cli/config"
3436
"github.com/coder/coder/coderd"
3537
"github.com/coder/coder/coderd/gitauth"
38+
"github.com/coder/coder/coderd/telemetry"
3639
"github.com/coder/coder/codersdk"
3740
"github.com/coder/coder/codersdk/agentsdk"
3841
)
@@ -425,6 +428,23 @@ type RootCmd struct {
425428
noFeatureWarning bool
426429
}
427430

431+
func telemetryInvocation(i *clibase.Invocation) telemetry.CLIInvocation {
432+
var topts []telemetry.CLIOption
433+
for _, opt := range i.Command.FullOptions() {
434+
if opt.Value.String() == opt.Default {
435+
continue
436+
}
437+
topts = append(topts, telemetry.CLIOption{
438+
Name: opt.Name,
439+
ValueSource: string(opt.ValueSource),
440+
})
441+
}
442+
return telemetry.CLIInvocation{
443+
Command: i.Command.FullName(),
444+
Options: topts,
445+
}
446+
}
447+
428448
// InitClient sets client to a new client.
429449
// It reads from global configuration files if flags are not set.
430450
func (r *RootCmd) InitClient(client *codersdk.Client) clibase.MiddlewareFunc {
@@ -465,7 +485,18 @@ func (r *RootCmd) InitClient(client *codersdk.Client) clibase.MiddlewareFunc {
465485
}
466486
}
467487

468-
err = r.setClient(client, r.clientURL)
488+
telemInv := telemetryInvocation(i)
489+
byt, err := json.Marshal(telemInv)
490+
if err != nil {
491+
// Should be impossible
492+
panic(err)
493+
}
494+
err = r.setClient(
495+
client, r.clientURL,
496+
append(r.header, "Coder-CLI-Invokation="+
497+
base64.StdEncoding.EncodeToString(byt),
498+
),
499+
)
469500
if err != nil {
470501
return err
471502
}
@@ -512,12 +543,12 @@ func (r *RootCmd) InitClient(client *codersdk.Client) clibase.MiddlewareFunc {
512543
}
513544
}
514545

515-
func (r *RootCmd) setClient(client *codersdk.Client, serverURL *url.URL) error {
546+
func (*RootCmd) setClient(client *codersdk.Client, serverURL *url.URL, headers []string) error {
516547
transport := &headerTransport{
517548
transport: http.DefaultTransport,
518549
header: http.Header{},
519550
}
520-
for _, header := range r.header {
551+
for _, header := range headers {
521552
parts := strings.SplitN(header, "=", 2)
522553
if len(parts) < 2 {
523554
return xerrors.Errorf("split header %q had less than two parts", header)
@@ -533,7 +564,7 @@ func (r *RootCmd) setClient(client *codersdk.Client, serverURL *url.URL) error {
533564

534565
func (r *RootCmd) createUnauthenticatedClient(serverURL *url.URL) (*codersdk.Client, error) {
535566
var client codersdk.Client
536-
err := r.setClient(&client, serverURL)
567+
err := r.setClient(&client, serverURL, r.header)
537568
return &client, err
538569
}
539570

cli/vscodessh.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func (r *RootCmd) vscodeSSH() *clibase.Cmd {
8383
client.SetSessionToken(string(sessionToken))
8484

8585
// This adds custom headers to the request!
86-
err = r.setClient(client, serverURL)
86+
err = r.setClient(client, serverURL, r.header)
8787
if err != nil {
8888
return xerrors.Errorf("set client: %w", err)
8989
}

coderd/telemetry/telemetry.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,7 @@ type Snapshot struct {
700700
WorkspaceBuilds []WorkspaceBuild `json:"workspace_build"`
701701
WorkspaceResources []WorkspaceResource `json:"workspace_resources"`
702702
WorkspaceResourceMetadata []WorkspaceResourceMetadata `json:"workspace_resource_metadata"`
703+
CLIInvokations []CLIInvocation `json:"cli_invocations"`
703704
}
704705

705706
// Deployment contains information about the host running Coder.
@@ -874,6 +875,16 @@ type License struct {
874875
UUID uuid.UUID `json:"uuid"`
875876
}
876877

878+
type CLIOption struct {
879+
Name string `json:"name"`
880+
ValueSource string `json:"set_via"`
881+
}
882+
883+
type CLIInvocation struct {
884+
Command string `json:"command"`
885+
Options []CLIOption
886+
}
887+
877888
type noopReporter struct{}
878889

879890
func (*noopReporter) Report(_ *Snapshot) {}

codersdk/client.go

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -179,15 +179,6 @@ func (c *Client) Request(ctx context.Context, method, path string, body interfac
179179
return nil, xerrors.Errorf("create request: %w", err)
180180
}
181181

182-
if c.PlainLogger != nil {
183-
out, err := httputil.DumpRequest(req, c.LogBodies)
184-
if err != nil {
185-
return nil, xerrors.Errorf("dump request: %w", err)
186-
}
187-
out = prefixLines([]byte("http --> "), out)
188-
_, _ = c.PlainLogger.Write(out)
189-
}
190-
191182
tokenHeader := c.SessionTokenHeader
192183
if tokenHeader == "" {
193184
tokenHeader = SessionTokenHeader
@@ -221,6 +212,18 @@ func (c *Client) Request(ctx context.Context, method, path string, body interfac
221212
})
222213

223214
resp, err := c.HTTPClient.Do(req)
215+
216+
// We log after sending the request because the HTTP Transport may modify
217+
// the request within Do, e.g. by adding headers.
218+
if resp != nil && c.PlainLogger != nil {
219+
out, err := httputil.DumpRequest(resp.Request, c.LogBodies)
220+
if err != nil {
221+
return nil, xerrors.Errorf("dump request: %w", err)
222+
}
223+
out = prefixLines([]byte("http --> "), out)
224+
_, _ = c.PlainLogger.Write(out)
225+
}
226+
224227
if err != nil {
225228
return nil, err
226229
}

0 commit comments

Comments
 (0)