mirror of
https://github.com/go-task/task.git
synced 2026-09-01 19:50:16 +02:00
refactor(remote): rename HostHeaders to HeadersByHost
The type is a map of host to headers, not a flat header set. Name it after that shape, and rename the fields carrying it to authHeadersByHost so the lookup step is visible at every call site. Claude-Session: https://claude.ai/code/session_01KNPMznEzkRpxFZMLisjdqL
This commit is contained in:
@@ -37,7 +37,7 @@ type (
|
||||
Download bool
|
||||
Offline bool
|
||||
TrustedHosts []string
|
||||
RemoteAuth taskfile.HostHeaders
|
||||
RemoteAuth taskfile.HeadersByHost
|
||||
Timeout time.Duration
|
||||
CacheExpiryDuration time.Duration
|
||||
RemoteCacheDir string
|
||||
@@ -281,12 +281,12 @@ func (o *trustedHostsOption) ApplyToExecutor(e *Executor) {
|
||||
|
||||
// WithRemoteAuth configures the [Executor] with the HTTP headers to send when
|
||||
// fetching a remote Taskfile, keyed by host.
|
||||
func WithRemoteAuth(remoteAuth taskfile.HostHeaders) ExecutorOption {
|
||||
func WithRemoteAuth(remoteAuth taskfile.HeadersByHost) ExecutorOption {
|
||||
return &remoteAuthOption{remoteAuth}
|
||||
}
|
||||
|
||||
type remoteAuthOption struct {
|
||||
remoteAuth taskfile.HostHeaders
|
||||
remoteAuth taskfile.HeadersByHost
|
||||
}
|
||||
|
||||
func (o *remoteAuthOption) ApplyToExecutor(e *Executor) {
|
||||
|
||||
@@ -80,7 +80,7 @@ var (
|
||||
Download bool
|
||||
Offline bool
|
||||
TrustedHosts []string
|
||||
RemoteAuth taskfile.HostHeaders
|
||||
RemoteAuth taskfile.HeadersByHost
|
||||
ClearCache bool
|
||||
Timeout time.Duration
|
||||
CacheExpiryDuration time.Duration
|
||||
@@ -318,11 +318,11 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
|
||||
|
||||
// remoteAuth flattens the configured entries into a lookup by host, the last
|
||||
// entry winning as it does when configuration files are merged.
|
||||
func remoteAuth(config *taskrcast.TaskRC) taskfile.HostHeaders {
|
||||
func remoteAuth(config *taskrcast.TaskRC) taskfile.HeadersByHost {
|
||||
if config == nil || len(config.Remote.Auth) == 0 {
|
||||
return nil
|
||||
}
|
||||
byHost := make(taskfile.HostHeaders, len(config.Remote.Auth))
|
||||
byHost := make(taskfile.HeadersByHost, len(config.Remote.Auth))
|
||||
for _, auth := range config.Remote.Auth {
|
||||
byHost[auth.Host] = auth.Headers
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ import (
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
)
|
||||
|
||||
// HostHeaders maps a host to the HTTP headers to send when fetching a remote
|
||||
// HeadersByHost maps a host to the HTTP headers to send when fetching a remote
|
||||
// Taskfile from it. Values are templated, but no variables are available.
|
||||
type HostHeaders map[string]map[string]string
|
||||
type HeadersByHost map[string]map[string]string
|
||||
|
||||
type authTransport struct {
|
||||
base http.RoundTripper
|
||||
@@ -38,7 +38,7 @@ func (t *authTransport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
// authenticatedClient resolves on each read, not at build time, so a cached
|
||||
// run needs no credentials.
|
||||
func (node *HTTPNode) authenticatedClient() (*http.Client, error) {
|
||||
headers, err := resolveAuthHeaders(node.authHeaders, node.url.Host)
|
||||
headers, err := resolveAuthHeaders(node.authHeadersByHost, node.url.Host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -61,9 +61,9 @@ func withAuthHeaders(client *http.Client, host string, headers map[string]string
|
||||
}
|
||||
|
||||
// resolveAuthHeaders returns the expanded headers for host, or nil if none.
|
||||
func resolveAuthHeaders(hostHeaders HostHeaders, host string) (map[string]string, error) {
|
||||
func resolveAuthHeaders(headersByHost HeadersByHost, host string) (map[string]string, error) {
|
||||
var headers map[string]string
|
||||
for pattern, patternHeaders := range hostHeaders {
|
||||
for pattern, patternHeaders := range headersByHost {
|
||||
if hostMatches(pattern, host) {
|
||||
headers = patternHeaders
|
||||
break
|
||||
|
||||
@@ -12,98 +12,98 @@ import (
|
||||
|
||||
func TestResolveAuthHeaders(t *testing.T) { //nolint:paralleltest // t.Setenv cannot be used in parallel tests
|
||||
tests := []struct {
|
||||
name string
|
||||
hostHeaders HostHeaders
|
||||
host string
|
||||
env map[string]string
|
||||
want map[string]string
|
||||
wantErr string
|
||||
name string
|
||||
headersByHost HeadersByHost
|
||||
host string
|
||||
env map[string]string
|
||||
want map[string]string
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "no configuration",
|
||||
hostHeaders: nil,
|
||||
host: "gitlab.com",
|
||||
name: "no configuration",
|
||||
headersByHost: nil,
|
||||
host: "gitlab.com",
|
||||
},
|
||||
{
|
||||
name: "host does not match",
|
||||
hostHeaders: HostHeaders{"gitlab.com": {"PRIVATE-TOKEN": "token"}},
|
||||
host: "example.com",
|
||||
name: "host does not match",
|
||||
headersByHost: HeadersByHost{"gitlab.com": {"PRIVATE-TOKEN": "token"}},
|
||||
host: "example.com",
|
||||
},
|
||||
{
|
||||
name: "port is part of the host",
|
||||
hostHeaders: HostHeaders{"example.com": {"PRIVATE-TOKEN": "token"}},
|
||||
host: "example.com:8080",
|
||||
name: "port is part of the host",
|
||||
headersByHost: HeadersByHost{"example.com": {"PRIVATE-TOKEN": "token"}},
|
||||
host: "example.com:8080",
|
||||
},
|
||||
{
|
||||
name: "literal value",
|
||||
hostHeaders: HostHeaders{"gitlab.com": {"PRIVATE-TOKEN": "token"}},
|
||||
host: "gitlab.com",
|
||||
want: map[string]string{"PRIVATE-TOKEN": "token"},
|
||||
name: "literal value",
|
||||
headersByHost: HeadersByHost{"gitlab.com": {"PRIVATE-TOKEN": "token"}},
|
||||
host: "gitlab.com",
|
||||
want: map[string]string{"PRIVATE-TOKEN": "token"},
|
||||
},
|
||||
{
|
||||
name: "environment variable",
|
||||
hostHeaders: HostHeaders{"gitlab.com": {"PRIVATE-TOKEN": `{{env "TASK_TEST_TOKEN"}}`}}, //nolint:gosec // an env var reference, not a credential
|
||||
host: "gitlab.com",
|
||||
env: map[string]string{"TASK_TEST_TOKEN": "s3cret"},
|
||||
want: map[string]string{"PRIVATE-TOKEN": "s3cret"},
|
||||
name: "environment variable",
|
||||
headersByHost: HeadersByHost{"gitlab.com": {"PRIVATE-TOKEN": `{{env "TASK_TEST_TOKEN"}}`}}, //nolint:gosec // an env var reference, not a credential
|
||||
host: "gitlab.com",
|
||||
env: map[string]string{"TASK_TEST_TOKEN": "s3cret"},
|
||||
want: map[string]string{"PRIVATE-TOKEN": "s3cret"},
|
||||
},
|
||||
{
|
||||
name: "environment variable inside a longer value",
|
||||
hostHeaders: HostHeaders{"gitlab.com": {"Authorization": `Bearer {{env "TASK_TEST_TOKEN"}}`}},
|
||||
host: "gitlab.com",
|
||||
env: map[string]string{"TASK_TEST_TOKEN": "s3cret"},
|
||||
want: map[string]string{"Authorization": "Bearer s3cret"},
|
||||
name: "environment variable inside a longer value",
|
||||
headersByHost: HeadersByHost{"gitlab.com": {"Authorization": `Bearer {{env "TASK_TEST_TOKEN"}}`}},
|
||||
host: "gitlab.com",
|
||||
env: map[string]string{"TASK_TEST_TOKEN": "s3cret"},
|
||||
want: map[string]string{"Authorization": "Bearer s3cret"},
|
||||
},
|
||||
{
|
||||
name: "undefined environment variable expands to nothing",
|
||||
hostHeaders: HostHeaders{"gitlab.com": {"PRIVATE-TOKEN": `{{env "TASK_TEST_UNSET"}}`}}, //nolint:gosec // an env var reference, not a credential
|
||||
host: "gitlab.com",
|
||||
want: map[string]string{"PRIVATE-TOKEN": ""},
|
||||
name: "undefined environment variable expands to nothing",
|
||||
headersByHost: HeadersByHost{"gitlab.com": {"PRIVATE-TOKEN": `{{env "TASK_TEST_UNSET"}}`}}, //nolint:gosec // an env var reference, not a credential
|
||||
host: "gitlab.com",
|
||||
want: map[string]string{"PRIVATE-TOKEN": ""},
|
||||
},
|
||||
{
|
||||
name: "functions compose, so Basic auth needs no manual base64",
|
||||
hostHeaders: HostHeaders{"gitlab.com": {"Authorization": `Basic {{ printf "%s:%s" (env "TASK_TEST_USER") (env "TASK_TEST_TOKEN") | b64enc }}`}},
|
||||
host: "gitlab.com",
|
||||
env: map[string]string{"TASK_TEST_USER": "alice", "TASK_TEST_TOKEN": "s3cret"},
|
||||
want: map[string]string{"Authorization": "Basic YWxpY2U6czNjcmV0"},
|
||||
name: "functions compose, so Basic auth needs no manual base64",
|
||||
headersByHost: HeadersByHost{"gitlab.com": {"Authorization": `Basic {{ printf "%s:%s" (env "TASK_TEST_USER") (env "TASK_TEST_TOKEN") | b64enc }}`}},
|
||||
host: "gitlab.com",
|
||||
env: map[string]string{"TASK_TEST_USER": "alice", "TASK_TEST_TOKEN": "s3cret"},
|
||||
want: map[string]string{"Authorization": "Basic YWxpY2U6czNjcmV0"},
|
||||
},
|
||||
{
|
||||
// The .taskrc is read before any Taskfile, so no variable exists.
|
||||
name: "a variable reference resolves to nothing",
|
||||
hostHeaders: HostHeaders{"gitlab.com": {"PRIVATE-TOKEN": "{{.TASK_TEST_TOKEN}}"}}, //nolint:gosec // a template, not a credential
|
||||
host: "gitlab.com",
|
||||
env: map[string]string{"TASK_TEST_TOKEN": "s3cret"},
|
||||
want: map[string]string{"PRIVATE-TOKEN": ""},
|
||||
name: "a variable reference resolves to nothing",
|
||||
headersByHost: HeadersByHost{"gitlab.com": {"PRIVATE-TOKEN": "{{.TASK_TEST_TOKEN}}"}}, //nolint:gosec // a template, not a credential
|
||||
host: "gitlab.com",
|
||||
env: map[string]string{"TASK_TEST_TOKEN": "s3cret"},
|
||||
want: map[string]string{"PRIVATE-TOKEN": ""},
|
||||
},
|
||||
{
|
||||
name: "a literal value is left untouched",
|
||||
hostHeaders: HostHeaders{"gitlab.com": {"PRIVATE-TOKEN": "p$ssw0rd"}}, //nolint:gosec // a test fixture
|
||||
host: "gitlab.com",
|
||||
want: map[string]string{"PRIVATE-TOKEN": "p$ssw0rd"},
|
||||
name: "a literal value is left untouched",
|
||||
headersByHost: HeadersByHost{"gitlab.com": {"PRIVATE-TOKEN": "p$ssw0rd"}}, //nolint:gosec // a test fixture
|
||||
host: "gitlab.com",
|
||||
want: map[string]string{"PRIVATE-TOKEN": "p$ssw0rd"},
|
||||
},
|
||||
{
|
||||
name: "malformed template",
|
||||
hostHeaders: HostHeaders{"gitlab.com": {"PRIVATE-TOKEN": `{{env "TASK_TEST_TOKEN"`}}, //nolint:gosec // a template, not a credential
|
||||
host: "gitlab.com",
|
||||
wantErr: `remote auth for host "gitlab.com": template: :1: unclosed action`,
|
||||
name: "malformed template",
|
||||
headersByHost: HeadersByHost{"gitlab.com": {"PRIVATE-TOKEN": `{{env "TASK_TEST_TOKEN"`}}, //nolint:gosec // a template, not a credential
|
||||
host: "gitlab.com",
|
||||
wantErr: `remote auth for host "gitlab.com": template: :1: unclosed action`,
|
||||
},
|
||||
{
|
||||
name: "header name with a space",
|
||||
hostHeaders: HostHeaders{"gitlab.com": {"PRIVATE TOKEN": "token"}},
|
||||
host: "gitlab.com",
|
||||
wantErr: `remote auth for host "gitlab.com": invalid header name "PRIVATE TOKEN"`,
|
||||
name: "header name with a space",
|
||||
headersByHost: HeadersByHost{"gitlab.com": {"PRIVATE TOKEN": "token"}},
|
||||
host: "gitlab.com",
|
||||
wantErr: `remote auth for host "gitlab.com": invalid header name "PRIVATE TOKEN"`,
|
||||
},
|
||||
{
|
||||
name: "header name outside the HTTP token grammar",
|
||||
hostHeaders: HostHeaders{"gitlab.com": {"X-Foo(bar)": "token"}},
|
||||
host: "gitlab.com",
|
||||
wantErr: `remote auth for host "gitlab.com": invalid header name "X-Foo(bar)"`,
|
||||
name: "header name outside the HTTP token grammar",
|
||||
headersByHost: HeadersByHost{"gitlab.com": {"X-Foo(bar)": "token"}},
|
||||
host: "gitlab.com",
|
||||
wantErr: `remote auth for host "gitlab.com": invalid header name "X-Foo(bar)"`,
|
||||
},
|
||||
{
|
||||
name: "empty header name",
|
||||
hostHeaders: HostHeaders{"gitlab.com": {"": "token"}},
|
||||
host: "gitlab.com",
|
||||
wantErr: `remote auth for host "gitlab.com": invalid header name ""`,
|
||||
name: "empty header name",
|
||||
headersByHost: HeadersByHost{"gitlab.com": {"": "token"}},
|
||||
host: "gitlab.com",
|
||||
wantErr: `remote auth for host "gitlab.com": invalid header name ""`,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ func TestResolveAuthHeaders(t *testing.T) { //nolint:paralleltest // t.Setenv ca
|
||||
for name, value := range test.env {
|
||||
t.Setenv(name, value)
|
||||
}
|
||||
headers, err := resolveAuthHeaders(test.hostHeaders, test.host)
|
||||
headers, err := resolveAuthHeaders(test.headersByHost, test.host)
|
||||
if test.wantErr != "" {
|
||||
require.EqualError(t, err, test.wantErr)
|
||||
return
|
||||
@@ -178,7 +178,7 @@ func TestHTTPNodeAuthHeaders(t *testing.T) { //nolint:paralleltest // t.Setenv c
|
||||
|
||||
t.Setenv("TASK_TEST_TOKEN", "s3cret")
|
||||
node, err := NewHTTPNode(srv.URL+"/Taskfile.yml", "", true,
|
||||
WithAuthHeaders(HostHeaders{
|
||||
WithAuthHeaders(HeadersByHost{
|
||||
mustHost(t, srv.URL): {"PRIVATE-TOKEN": `{{env "TASK_TEST_TOKEN"}}`}, //nolint:gosec // an env var reference, not a credential
|
||||
}),
|
||||
)
|
||||
@@ -208,7 +208,7 @@ func TestHTTPNodeAuthHeadersNotSentOnRedirect(t *testing.T) {
|
||||
defer srv.Close()
|
||||
|
||||
node, err := NewHTTPNode(srv.URL+"/Taskfile.yml", "", true,
|
||||
WithAuthHeaders(HostHeaders{
|
||||
WithAuthHeaders(HeadersByHost{
|
||||
mustHost(t, srv.URL): {"PRIVATE-TOKEN": "s3cret"},
|
||||
}),
|
||||
)
|
||||
@@ -226,7 +226,7 @@ func TestHTTPNodeAuthHeadersNotSentOnRedirect(t *testing.T) {
|
||||
// cached and offline runs do not require them.
|
||||
func TestHTTPNodeAuthHeadersResolvedLazily(t *testing.T) { //nolint:paralleltest // t.Setenv cannot be used in parallel tests
|
||||
node, err := NewHTTPNode("https://gitlab.com/Taskfile.yml", "", false,
|
||||
WithAuthHeaders(HostHeaders{
|
||||
WithAuthHeaders(HeadersByHost{
|
||||
"gitlab.com": {"PRIVATE-TOKEN": `{{env "TASK_TEST_LAZY"}}`}, //nolint:gosec // an env var reference, not a credential
|
||||
}),
|
||||
)
|
||||
@@ -235,7 +235,7 @@ func TestHTTPNodeAuthHeadersResolvedLazily(t *testing.T) { //nolint:paralleltest
|
||||
// Defined only after the node was built: the value must still be picked up.
|
||||
t.Setenv("TASK_TEST_LAZY", "s3cret")
|
||||
|
||||
headers, err := resolveAuthHeaders(node.authHeaders, node.url.Host)
|
||||
headers, err := resolveAuthHeaders(node.authHeadersByHost, node.url.Host)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, map[string]string{"PRIVATE-TOKEN": "s3cret"}, headers)
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@ type (
|
||||
// designed to be embedded in other node types so that this boilerplate code
|
||||
// does not need to be repeated.
|
||||
baseNode struct {
|
||||
parent Node
|
||||
dir string
|
||||
checksum string
|
||||
caCert string
|
||||
cert string
|
||||
certKey string
|
||||
authHeaders HostHeaders
|
||||
parent Node
|
||||
dir string
|
||||
checksum string
|
||||
caCert string
|
||||
cert string
|
||||
certKey string
|
||||
authHeadersByHost HeadersByHost
|
||||
}
|
||||
)
|
||||
|
||||
@@ -78,8 +78,8 @@ func WithCertKey(certKey string) NodeOption {
|
||||
}
|
||||
|
||||
// WithAuthHeaders sets the HTTP headers to send, keyed by host.
|
||||
func WithAuthHeaders(authHeaders HostHeaders) NodeOption {
|
||||
func WithAuthHeaders(authHeadersByHost HeadersByHost) NodeOption {
|
||||
return func(node *baseNode) {
|
||||
node.authHeaders = authHeaders
|
||||
node.authHeadersByHost = authHeadersByHost
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ type (
|
||||
caCert string
|
||||
cert string
|
||||
certKey string
|
||||
authHeaders HostHeaders
|
||||
authHeadersByHost HeadersByHost
|
||||
debugFunc DebugFunc
|
||||
promptFunc PromptFunc
|
||||
promptMutex sync.Mutex
|
||||
@@ -244,16 +244,16 @@ func (o *readerCertKeyOption) ApplyToReader(r *Reader) {
|
||||
}
|
||||
|
||||
// WithReaderAuthHeaders sets the HTTP headers to send to each configured host.
|
||||
func WithReaderAuthHeaders(authHeaders HostHeaders) ReaderOption {
|
||||
return &readerAuthHeadersOption{authHeaders: authHeaders}
|
||||
func WithReaderAuthHeaders(authHeadersByHost HeadersByHost) ReaderOption {
|
||||
return &readerAuthHeadersOption{authHeadersByHost: authHeadersByHost}
|
||||
}
|
||||
|
||||
type readerAuthHeadersOption struct {
|
||||
authHeaders HostHeaders
|
||||
authHeadersByHost HeadersByHost
|
||||
}
|
||||
|
||||
func (o *readerAuthHeadersOption) ApplyToReader(r *Reader) {
|
||||
r.authHeaders = o.authHeaders
|
||||
r.authHeadersByHost = o.authHeadersByHost
|
||||
}
|
||||
|
||||
// Read will read the Taskfile defined by the [Reader]'s [Node] and recurse
|
||||
@@ -371,7 +371,7 @@ func (r *Reader) include(ctx context.Context, node Node) error {
|
||||
WithCACert(r.caCert),
|
||||
WithCert(r.cert),
|
||||
WithCertKey(r.certKey),
|
||||
WithAuthHeaders(r.authHeaders),
|
||||
WithAuthHeaders(r.authHeadersByHost),
|
||||
)
|
||||
if err != nil {
|
||||
if include.Optional {
|
||||
|
||||
Reference in New Issue
Block a user