diff --git a/agent/agentssh/agentssh.go b/agent/agentssh/agentssh.go index 5903220975b8c..0cf2055ebb2dd 100644 --- a/agent/agentssh/agentssh.go +++ b/agent/agentssh/agentssh.go @@ -52,6 +52,9 @@ const ( // MagicProcessCmdlineJetBrains is a string in a process's command line that // uniquely identifies it as JetBrains software. MagicProcessCmdlineJetBrains = "idea.vendor.name=JetBrains" + // MagicProcessCmdlineJetBrainsGateway is used to tell the agent not to report + // session stats because it's now being handled by the CLI. + MagicDisableUsageTrackingEnvironmentVariable = "CODER_SSH_DISABLE_USAGE_TRACKING" // BlockedFileTransferErrorCode indicates that SSH server restricted the raw command from performing // the file transfer. @@ -380,26 +383,41 @@ func (s *Server) sessionStart(logger slog.Logger, session ssh.Session, extraEnv env := append(session.Environ(), extraEnv...) var magicType string for index, kv := range env { - if !strings.HasPrefix(kv, MagicSessionTypeEnvironmentVariable) { + if !strings.HasPrefix(kv, MagicSessionTypeEnvironmentVariable+"=") { continue } magicType = strings.ToLower(strings.TrimPrefix(kv, MagicSessionTypeEnvironmentVariable+"=")) env = append(env[:index], env[index+1:]...) } + disableUsageTracking := false + for index, kv := range env { + if !strings.HasPrefix(kv, MagicDisableUsageTrackingEnvironmentVariable+"=") { + continue + } + if strings.ToLower(strings.TrimPrefix(kv, MagicDisableUsageTrackingEnvironmentVariable+"=")) == "true" { + disableUsageTracking = true + } + env = append(env[:index], env[index+1:]...) + } - // Always force lowercase checking to be case-insensitive. - switch magicType { - case MagicSessionTypeVSCode: - s.connCountVSCode.Add(1) - defer s.connCountVSCode.Add(-1) - case MagicSessionTypeJetBrains: - // Do nothing here because JetBrains launches hundreds of ssh sessions. - // We instead track JetBrains in the single persistent tcp forwarding channel. - case "": - s.connCountSSHSession.Add(1) - defer s.connCountSSHSession.Add(-1) - default: - logger.Warn(ctx, "invalid magic ssh session type specified", slog.F("type", magicType)) + // We only want to track the session counts if they are from + // older clients that haven't migrating to reporting these stats + // from the CLI. This ensures we don't double count sessions. + if !disableUsageTracking { + // Always force lowercase checking to be case-insensitive. + switch magicType { + case MagicSessionTypeVSCode: + s.connCountVSCode.Add(1) + defer s.connCountVSCode.Add(-1) + case MagicSessionTypeJetBrains: + // Do nothing here because JetBrains launches hundreds of ssh sessions. + // We instead track JetBrains in the single persistent tcp forwarding channel. + case "": + s.connCountSSHSession.Add(1) + defer s.connCountSSHSession.Add(-1) + default: + logger.Warn(ctx, "invalid magic ssh session type specified", slog.F("type", magicType)) + } } magicTypeLabel := magicTypeMetricLabel(magicType) diff --git a/cli/ssh.go b/cli/ssh.go index ac849649b9184..e950d9d649742 100644 --- a/cli/ssh.go +++ b/cli/ssh.go @@ -12,6 +12,7 @@ import ( "os" "os/exec" "path/filepath" + "slices" "strings" "sync" "time" @@ -28,6 +29,8 @@ import ( "cdr.dev/slog" "cdr.dev/slog/sloggers/sloghuman" + "github.com/coder/coder/v2/agent/agentssh" + "github.com/coder/coder/v2/apiversion" "github.com/coder/coder/v2/cli/cliui" "github.com/coder/coder/v2/cli/cliutil" "github.com/coder/coder/v2/coderd/autobuild/notify" @@ -57,6 +60,7 @@ func (r *RootCmd) ssh() *serpent.Command { logDirPath string remoteForwards []string env []string + usageApp string disableAutostart bool ) client := new(codersdk.Client) @@ -196,6 +200,11 @@ func (r *RootCmd) ssh() *serpent.Command { wait = false } + experiments, err := client.Experiments(ctx) + if err != nil { + return err + } + templateVersion, err := client.TemplateVersion(ctx, workspace.LatestBuild.TemplateVersionID) if err != nil { return err @@ -251,6 +260,18 @@ func (r *RootCmd) ssh() *serpent.Command { stopPolling := tryPollWorkspaceAutostop(ctx, client, workspace) defer stopPolling() + usageAppName := getUsageAppName(usageApp, stdio, experiments, workspaceAgent.APIVersion) + if usageAppName != "" { + closeUsage := client.UpdateWorkspaceUsageWithBodyContext(ctx, workspace.ID, codersdk.PostWorkspaceUsageRequest{ + AgentID: workspaceAgent.ID, + AppName: usageAppName, + }) + defer closeUsage() + + // signal to the agent that we are handling the usage tracking + parsedEnv = append(parsedEnv, [2]string{agentssh.MagicDisableUsageTrackingEnvironmentVariable, "true"}) + } + if stdio { rawSSH, err := conn.SSH(ctx) if err != nil { @@ -509,6 +530,12 @@ func (r *RootCmd) ssh() *serpent.Command { FlagShorthand: "e", Value: serpent.StringArrayOf(&env), }, + { + Flag: "usage-app", + Description: "Specifies the usage app to use for workspace activity tracking.", + Env: "CODER_SSH_USAGE_APP", + Value: serpent.StringOf(&usageApp), + }, sshDisableAutostartOption(serpent.BoolOf(&disableAutostart)), } return cmd @@ -1044,3 +1071,35 @@ func (r stdioErrLogReader) Read(_ []byte) (int, error) { r.l.Error(context.Background(), "reading from stdin in stdio mode is not allowed") return 0, io.EOF } + +func getUsageAppName(usageApp string, stdio bool, experiments codersdk.Experiments, agentAPIVersion string) codersdk.UsageAppName { + // if experiment is not enabled do not report usage + if !slices.Contains(experiments, codersdk.ExperimentWorkspaceUsage) { + return "" + } + + // need agent version to be at or after 2.2 + major, minor, err := apiversion.Parse(agentAPIVersion) + if err != nil { + return "" + } + err = apiversion.New(major, minor).Validate("2.2") + if err != nil { + return "" + } + + // if usageApp is empty and not stdio, default to ssh + if usageApp == "" && !stdio { + usageApp = string(codersdk.UsageAppNameSSH) + } + allowedUsageApps := []string{ + string(codersdk.UsageAppNameJetbrains), + string(codersdk.UsageAppNameVscode), + string(codersdk.UsageAppNameSSH), + } + if slices.Contains(allowedUsageApps, usageApp) { + return codersdk.UsageAppName(usageApp) + } + + return "" +} diff --git a/tailnet/proto/version.go b/tailnet/proto/version.go index 16f324f74fa33..e069b2d2f95f1 100644 --- a/tailnet/proto/version.go +++ b/tailnet/proto/version.go @@ -6,7 +6,7 @@ import ( const ( CurrentMajor = 2 - CurrentMinor = 1 + CurrentMinor = 2 ) var CurrentVersion = apiversion.New(CurrentMajor, CurrentMinor).WithBackwardCompat(1) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy