Skip to content

Commit 9ad98da

Browse files
authored
auth: improve error handling (#109)
- return the result of a login failure - use the correct user-agent for auth http requests Signed-off-by: Nick Santos <[email protected]>
1 parent 50e5f50 commit 9ad98da

4 files changed

Lines changed: 76 additions & 38 deletions

File tree

internal/auth/token.go

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"context"
2222
"encoding/json"
2323
"fmt"
24+
"io"
2425
"net/http"
2526
"sync"
2627
"time"
@@ -40,12 +41,15 @@ type LoginTokenProvider struct {
4041
}
4142

4243
// NewLoginTokenProvider creates a token provider that uses username/password
43-
func NewLoginTokenProvider(username, password, baseURL string) *LoginTokenProvider {
44+
func NewLoginTokenProvider(username, password, baseURL string, transport http.RoundTripper) *LoginTokenProvider {
4445
return &LoginTokenProvider{
45-
username: username,
46-
password: password,
47-
baseURL: baseURL,
48-
httpClient: http.DefaultClient,
46+
username: username,
47+
password: password,
48+
baseURL: baseURL,
49+
httpClient: &http.Client{
50+
Timeout: 10 * time.Second,
51+
Transport: transport,
52+
},
4953
}
5054
}
5155

@@ -87,6 +91,12 @@ func (p *LoginTokenProvider) EnsureToken(ctx context.Context) (string, error) {
8791
defer res.Body.Close()
8892

8993
if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest {
94+
// Read the response body to get more details on the error
95+
body, _ := io.ReadAll(res.Body)
96+
if len(body) > 0 {
97+
return "", fmt.Errorf("login failed: %s - %s", res.Status, string(body))
98+
}
99+
90100
return "", fmt.Errorf("login failed: %s", res.Status)
91101
}
92102

@@ -170,7 +180,7 @@ func NewAccessTokenProviderFromStore(configStore *ConfigStore, configKey string)
170180
}
171181

172182
// NewLoginTokenProviderFromStore creates a LoginTokenProvider from pull credentials in the ConfigStore
173-
func NewLoginTokenProviderFromStore(configStore *ConfigStore, configKey, baseURL string) (*LoginTokenProvider, error) {
183+
func NewLoginTokenProviderFromStore(configStore *ConfigStore, configKey, baseURL string, transport http.RoundTripper) (*LoginTokenProvider, error) {
174184
username, password, err := configStore.GetCredentialStorePullTokens(configKey)
175185
if err != nil {
176186
return nil, fmt.Errorf("no pull credentials available: %v", err)
@@ -184,5 +194,5 @@ func NewLoginTokenProviderFromStore(configStore *ConfigStore, configKey, baseURL
184194
return nil, fmt.Errorf("empty password found in store")
185195
}
186196

187-
return NewLoginTokenProvider(username, password, baseURL), nil
197+
return NewLoginTokenProvider(username, password, baseURL, transport), nil
188198
}

internal/hubclient/client.go

Lines changed: 5 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -44,37 +44,17 @@ type Client struct {
4444
maxPageResults int64
4545
}
4646

47-
type userAgentTransport struct {
48-
userAgent string
49-
transport http.RoundTripper
50-
}
51-
52-
func (uat *userAgentTransport) RoundTrip(req *http.Request) (*http.Response, error) {
53-
req.Header.Set("User-Agent", uat.userAgent)
54-
return uat.transport.RoundTrip(req)
55-
}
56-
5747
type Config struct {
58-
BaseURL string
59-
TokenProvider TokenProvider
60-
UserAgentVersion string
61-
MaxPageResults int64
48+
BaseURL string
49+
TokenProvider TokenProvider
50+
Transport http.RoundTripper
51+
MaxPageResults int64
6252
}
6353

6454
func NewClient(config Config) *Client {
65-
version := config.UserAgentVersion
66-
if version == "" {
67-
version = "dev"
68-
}
69-
70-
userAgentTransport := &userAgentTransport{
71-
userAgent: fmt.Sprintf("terraform-provider-docker/%s", version),
72-
transport: http.DefaultTransport,
73-
}
74-
7555
baseClient := &http.Client{
7656
Timeout: time.Minute,
77-
Transport: userAgentTransport,
57+
Transport: config.Transport,
7858
}
7959
retryClient := retryablehttp.NewClient()
8060
retryClient.HTTPClient = baseClient

internal/hubhttp/transport.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
Copyright 2024 Docker Terraform Provider authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package hubhttp
18+
19+
import (
20+
"fmt"
21+
"net/http"
22+
)
23+
24+
type userAgentTransport struct {
25+
userAgent string
26+
transport http.RoundTripper
27+
}
28+
29+
func (uat *userAgentTransport) RoundTrip(req *http.Request) (*http.Response, error) {
30+
req.Header.Set("User-Agent", uat.userAgent)
31+
return uat.transport.RoundTrip(req)
32+
}
33+
34+
// NewUserAgentTransport creates a RoundTripper that adds a User-Agent header
35+
func NewUserAgentTransport(version string) http.RoundTripper {
36+
if version == "" {
37+
version = "dev"
38+
}
39+
40+
return &userAgentTransport{
41+
userAgent: fmt.Sprintf("terraform-provider-docker/%s", version),
42+
transport: http.DefaultTransport,
43+
}
44+
}

internal/provider/provider.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424

2525
"github.com/docker/terraform-provider-docker/internal/auth"
2626
"github.com/docker/terraform-provider-docker/internal/hubclient"
27+
"github.com/docker/terraform-provider-docker/internal/hubhttp"
2728
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
2829
"github.com/hashicorp/terraform-plugin-framework/datasource"
2930
"github.com/hashicorp/terraform-plugin-framework/function"
@@ -289,6 +290,9 @@ func (p *DockerProvider) Configure(ctx context.Context, req provider.ConfigureRe
289290
})
290291
}
291292

293+
// Create a shared transport with user agent
294+
sharedTransport := hubhttp.NewUserAgentTransport(p.version)
295+
292296
// Determine the authentication method
293297
var tokenProvider hubclient.TokenProvider
294298
var err error
@@ -297,7 +301,7 @@ func (p *DockerProvider) Configure(ctx context.Context, req provider.ConfigureRe
297301
// If username and password are provided, use login auth
298302
if username != "" && password != "" {
299303
tflog.Info(ctx, "Using login authentication from configuration")
300-
tokenProvider = auth.NewLoginTokenProvider(username, password, baseURL)
304+
tokenProvider = auth.NewLoginTokenProvider(username, password, baseURL, sharedTransport)
301305
} else {
302306
// Try credential store - prefer access tokens, fallback to pull credentials
303307
configfileKey := getConfigfileKey(host)
@@ -307,7 +311,7 @@ func (p *DockerProvider) Configure(ctx context.Context, req provider.ConfigureRe
307311
tokenProvider, err = auth.NewAccessTokenProviderFromStore(configStore, configfileKey)
308312
if err != nil {
309313
// Fallback to pull credentials (username/password or PAT)
310-
tokenProvider, err = auth.NewLoginTokenProviderFromStore(configStore, configfileKey, baseURL)
314+
tokenProvider, err = auth.NewLoginTokenProviderFromStore(configStore, configfileKey, baseURL, sharedTransport)
311315
if err != nil {
312316
resp.Diagnostics.AddError("Credential Store Error",
313317
fmt.Sprintf("Failed to retrieve valid credentials from the Docker config file: %v", err))
@@ -353,10 +357,10 @@ func (p *DockerProvider) Configure(ctx context.Context, req provider.ConfigureRe
353357
tflog.Debug(ctx, "Creating Docker Hub client")
354358

355359
client := hubclient.NewClient(hubclient.Config{
356-
BaseURL: baseURL,
357-
TokenProvider: tokenProvider,
358-
UserAgentVersion: p.version,
359-
MaxPageResults: maxPageResults,
360+
BaseURL: baseURL,
361+
TokenProvider: tokenProvider,
362+
Transport: sharedTransport,
363+
MaxPageResults: maxPageResults,
360364
})
361365

362366
resp.DataSourceData = client

0 commit comments

Comments
 (0)