Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pkg/api/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"

"charm.land/lipgloss/v2"
"github.com/depot/cli/pkg/runtoken"
"github.com/pkg/errors"
)

Expand All @@ -34,7 +35,7 @@ func apiRequest[Response interface{}](method, url, token string, payload interfa
requestBody = nil
}

client := &http.Client{}
client := runtoken.WrapClient(&http.Client{})
req, err := http.NewRequest(method, url, requestBody)
if err != nil {
return nil, err
Expand Down
9 changes: 5 additions & 4 deletions pkg/api/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/depot/cli/pkg/proto/depot/ci/v1/civ1connect"
"github.com/depot/cli/pkg/proto/depot/cli/v1/cliv1connect"
"github.com/depot/cli/pkg/proto/depot/cli/v1beta1/cliv1beta1connect"
"github.com/depot/cli/pkg/runtoken"
"golang.org/x/net/http2"
)

Expand Down Expand Up @@ -110,19 +111,19 @@ func WithAuthenticationAndOrg[T any](req *connect.Request[T], token, orgID strin
func getHTTPClient(baseURL string) *http.Client {
if strings.HasPrefix(baseURL, "http://") {
// Configure h2c (HTTP/2 over cleartext) for non-TLS connections
return &http.Client{
return runtoken.WrapClient(&http.Client{
Transport: &http2.Transport{
AllowHTTP: true,
DialTLS: func(network, addr string, _ *tls.Config) (net.Conn, error) {
return net.Dial(network, addr)
},
},
}
})
}

t, ok := http.DefaultTransport.(*http.Transport)
if !ok {
return http.DefaultClient
return runtoken.WrapClient(http.DefaultClient)
}

transport := t.Clone()
Expand All @@ -135,7 +136,7 @@ func getHTTPClient(baseURL string) *http.Client {
Transport: transport,
}

return racingClient
return runtoken.WrapClient(racingClient)
}

func getBaseURL() string {
Expand Down
7 changes: 6 additions & 1 deletion pkg/cmd/cargo/cargo.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/depot/cli/pkg/cmd/gocache"
"github.com/depot/cli/pkg/config"
"github.com/depot/cli/pkg/helpers"
"github.com/depot/cli/pkg/runtoken"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -52,10 +53,14 @@ func NewCmdCargo() *cobra.Command {
return fmt.Errorf("sccache not found in PATH: %w\n\nPlease install sccache: cargo install sccache", err)
}

token, err := helpers.ResolveOrgAuth(ctx, "")
token, err := helpers.ResolveCacheAuth(ctx, "")
if err != nil {
return fmt.Errorf("failed to resolve token: %w", err)
}
// sccache keeps the token for the whole build, and a managed run's own expires in minutes.
if source := runtoken.Default(); source != nil && source.Minted(token) {
return fmt.Errorf("depot cargo needs a long-lived token: pass DEPOT_TOKEN or DEPOT_CACHE_TOKEN")
}

// Create cargo command
cargoCmd := exec.CommandContext(ctx, "cargo", cargoArgs...)
Expand Down
5 changes: 5 additions & 0 deletions pkg/cmd/docker/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
depotdockerclient "github.com/depot/cli/pkg/dockerclient"
"github.com/depot/cli/pkg/helpers"
"github.com/depot/cli/pkg/retry"
"github.com/depot/cli/pkg/runtoken"
"github.com/docker/buildx/store"
"github.com/docker/buildx/store/storeutil"
"github.com/docker/buildx/util/confutil"
Expand Down Expand Up @@ -186,6 +187,10 @@ func runConfigureBuildx(ctx context.Context, dockerCli command.Cli, project, tok
if token == "" {
return fmt.Errorf("missing API token, please run `depot login`")
}
// Buildx saves the token, and a managed run's own expires in minutes.
if source := runtoken.Default(); source != nil && source.Minted(token) {
return fmt.Errorf("configure-docker needs a long-lived token: pass --token or set DEPOT_TOKEN")
}
projectName := helpers.ResolveProjectID(project)
if projectName == "" {
return errors.Errorf("unknown project ID (run `depot init` or use --project or $DEPOT_PROJECT_ID)")
Expand Down
5 changes: 3 additions & 2 deletions pkg/cmd/gocache/gocache.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/depot/cli/pkg/cmd/gocache/wire"
"github.com/depot/cli/pkg/config"
"github.com/depot/cli/pkg/helpers"
"github.com/depot/cli/pkg/runtoken"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -49,7 +50,7 @@ func NewCmdGoCache() *cobra.Command {
return err
}

token, err = helpers.ResolveOrgAuth(ctx, token)
token, err = helpers.ResolveCacheAuth(ctx, token)
if err != nil {
return err
}
Expand Down Expand Up @@ -336,7 +337,7 @@ func (c *RemoteCache) httpClient() *http.Client {
if c.HTTPClient != nil {
return c.HTTPClient
}
return http.DefaultClient
return runtoken.WrapClient(http.DefaultClient)
}

func (c *RemoteCache) Get(ctx context.Context, actionID string) (outputID, diskPath string, err error) {
Expand Down
3 changes: 2 additions & 1 deletion pkg/cmd/push/blobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"strings"
"time"

"github.com/depot/cli/pkg/runtoken"
"github.com/opencontainers/go-digest"
)

Expand Down Expand Up @@ -48,7 +49,7 @@ func PushBlob(ctx context.Context, depotToken string, blob *BlobToPush) error {
}
req.Header.Add("Authorization", "Bearer "+depotToken)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
resp, err := runtoken.WrapClient(http.DefaultClient).Do(req)
if err != nil {
return err
}
Expand Down
36 changes: 36 additions & 0 deletions pkg/helpers/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/depot/cli/pkg/api"
"github.com/depot/cli/pkg/config"
"github.com/depot/cli/pkg/oidc"
"github.com/depot/cli/pkg/runtoken"
)

func ResolveOrgAuth(ctx context.Context, tok string) (string, error) {
Expand All @@ -22,6 +23,17 @@ func ResolveOrgAuth(ctx context.Context, tok string) (string, error) {
return "", nil
}

// ResolveCacheAuth is ResolveOrgAuth for cache tools, which keep a cache token over a run token that expires mid-build.
func ResolveCacheAuth(ctx context.Context, tok string) (string, error) {
token, err := ResolveOrgAuth(ctx, tok)
if source := runTokenSource(); err == nil && source != nil && source.Minted(token) {
if cacheToken := resolveJITToken(); cacheToken != "" {
return cacheToken, nil
}
}
return token, err
}

func ResolveStaticOrgAuth(tok string) string {
if tok != "" {
return tok
Expand All @@ -35,6 +47,10 @@ func ResolveStaticOrgAuth(tok string) string {
return token
}

if token := resolveRunToken(context.Background()); token != "" {
return token
}

return resolveJITToken()
}

Expand All @@ -43,6 +59,10 @@ func ResolveProjectAuth(ctx context.Context, tok string) (string, error) {
return token, nil
}

if token := resolveRunToken(ctx); token != "" {
return token, nil
}

if token := resolveOIDCToken(ctx); token != "" {
return token, nil
}
Expand Down Expand Up @@ -126,6 +146,22 @@ func resolveOIDCToken(ctx context.Context) string {
return ""
}

var runTokenSource = runtoken.Default

// resolveRunToken mints a managed run's own token; the API client refreshes it per request.
func resolveRunToken(ctx context.Context) string {
source := runTokenSource()
if source == nil {
return ""
}
token, err := source.Token(ctx)
if err != nil {
fmt.Fprintf(os.Stderr, "warning: %v\n", err)
return ""
}
return token
}

func resolveJITToken() string {
if token := os.Getenv("DEPOT_JIT_TOKEN"); token != "" {
return token
Expand Down
81 changes: 81 additions & 0 deletions pkg/helpers/token_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package helpers

import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"testing"

"github.com/depot/cli/pkg/runtoken"
"github.com/spf13/viper"
)

func withRunTokenServer(t *testing.T) {
t.Helper()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprint(w, `{"value":"run-svid"}`)
}))
t.Cleanup(server.Close)
source := runtoken.New(server.URL)
previous := runTokenSource
runTokenSource = func() *runtoken.Source { return source }
t.Cleanup(func() { runTokenSource = previous })
}

func TestRunTokenPrecedence(t *testing.T) {
for _, tc := range []struct {
name, flag, env, config, cache string
runToken bool
want string
}{
{name: "flag wins", flag: "flag-token", env: "env-token", runToken: true, want: "flag-token"},
{name: "DEPOT_TOKEN wins", env: "env-token", runToken: true, want: "env-token"},
{name: "config wins", config: "config-token", runToken: true, want: "config-token"},
{name: "run token beats cache token", cache: "cache-token", runToken: true, want: "run-svid"},
{name: "no endpoint keeps old behaviour", cache: "cache-token", want: "cache-token"},
} {
t.Run(tc.name, func(t *testing.T) {
t.Setenv("DEPOT_TOKEN", tc.env)
t.Setenv("DEPOT_JIT_TOKEN", "")
t.Setenv("DEPOT_CACHE_TOKEN", tc.cache)
viper.Set("api_token", tc.config)
t.Cleanup(func() { viper.Set("api_token", "") })
previous := runTokenSource
runTokenSource = func() *runtoken.Source { return nil }
t.Cleanup(func() { runTokenSource = previous })
if tc.runToken {
withRunTokenServer(t)
}

if got := ResolveStaticOrgAuth(tc.flag); got != tc.want {
t.Fatalf("ResolveStaticOrgAuth = %q, want %q", got, tc.want)
}
if tc.cache == "" {
if got, _ := ResolveProjectAuth(context.Background(), tc.flag); got != tc.want {
t.Fatalf("ResolveProjectAuth = %q, want %q", got, tc.want)
}
}
})
}
}

func TestResolveCacheAuthKeepsCacheToken(t *testing.T) {
t.Setenv("DEPOT_TOKEN", "")
t.Setenv("DEPOT_JIT_TOKEN", "")
t.Setenv("DEPOT_CACHE_TOKEN", "cache-token")
viper.Set("api_token", "")
withRunTokenServer(t)

if got, _ := ResolveOrgAuth(context.Background(), ""); got != "run-svid" {
t.Fatalf("ResolveOrgAuth = %q, want the run token", got)
}
if got, _ := ResolveCacheAuth(context.Background(), ""); got != "cache-token" {
t.Fatalf("ResolveCacheAuth = %q, want the cache token", got)
}

t.Setenv("DEPOT_CACHE_TOKEN", "")
if got, _ := ResolveCacheAuth(context.Background(), ""); got != "run-svid" {
t.Fatalf("ResolveCacheAuth without a cache token = %q, want the run token", got)
}
}
Loading
Loading