Skip to content

Commit fd3df71

Browse files
authored
chore: addressing PR #6736 comemtns (#6741)
* chore: go 1.27 upgrade * chore : Pr commetns * chore: PR cleanup * chore: go mod tidy * chore: add venv integration * chore: PR simplification * cleanup * chore: tests cleanup * chore: addressing PR comments
1 parent c8211d9 commit fd3df71

3 files changed

Lines changed: 90 additions & 34 deletions

File tree

internal/gcphelper/config.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,12 +241,12 @@ func credentialsJSONOption(v *venv.Venv, data []byte) (option.ClientOption, erro
241241
}
242242

243243
if err := json.Unmarshal(data, &metadata); err != nil {
244-
return nil, fmt.Errorf("%w: %w", ErrParsingCredentials, err)
244+
return nil, ParsingCredentialsError{Err: err}
245245
}
246246

247247
// The SDK reports a missing type as an unsupported filetype, which does not say what is wrong.
248248
if metadata.Type == "" {
249-
return nil, fmt.Errorf("%w: the payload has no \"type\" field", ErrParsingCredentials)
249+
return nil, ParsingCredentialsError{Err: ErrMissingCredentialsType}
250250
}
251251

252252
// Every credential type the SDK accepts is passed through; it rejects the rest itself.
@@ -255,7 +255,7 @@ func credentialsJSONOption(v *venv.Venv, data []byte) (option.ClientOption, erro
255255
Client: v.HTTP,
256256
})
257257
if err != nil {
258-
return nil, fmt.Errorf("%w of type %q: %w", ErrBuildingCredentials, metadata.Type, err)
258+
return nil, BuildingCredentialsError{CredType: metadata.Type, Err: err}
259259
}
260260

261261
return option.WithAuthCredentials(creds), nil
@@ -283,7 +283,7 @@ func createGCPCredentialsFromGoogleCredentialsEnv(
283283
}
284284

285285
if err := json.Unmarshal([]byte(contents), &account); err != nil {
286-
return nil, fmt.Errorf("%w from GOOGLE_CREDENTIALS: %w", ErrParsingCredentials, err)
286+
return nil, ParsingCredentialsError{Err: err}
287287
}
288288

289289
conf := jwt.Config{

internal/gcphelper/config_test.go

Lines changed: 52 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ import (
1313
"path/filepath"
1414
"testing"
1515

16+
"cloud.google.com/go/auth/credentials"
1617
"github.com/gruntwork-io/terragrunt/internal/gcphelper"
18+
"github.com/gruntwork-io/terragrunt/internal/venv"
19+
"github.com/gruntwork-io/terragrunt/internal/vfs"
1720
"github.com/gruntwork-io/terragrunt/test/helpers/venvtest"
1821
"github.com/stretchr/testify/assert"
1922
"github.com/stretchr/testify/require"
@@ -253,42 +256,56 @@ func TestGcpConfigCredentialsPayloads(t *testing.T) {
253256
t.Parallel()
254257

255258
testCases := []struct {
256-
expected error
257-
name string
258-
payload string
259+
name string
260+
payload string
259261
}{
260-
{name: "unsupported type", payload: `{"type":"gce_metadata"}`, expected: gcphelper.ErrBuildingCredentials},
261-
{name: "missing type", payload: `{"client_email":"a@b.com"}`, expected: gcphelper.ErrParsingCredentials},
262-
{name: "not json", payload: `not-json`, expected: gcphelper.ErrParsingCredentials},
263-
{name: "json array", payload: `["a"]`, expected: gcphelper.ErrParsingCredentials},
262+
{name: "missing type", payload: `{"client_email":"a@b.com"}`},
263+
{name: "not json", payload: `not-json`},
264+
{name: "json array", payload: `["a"]`},
264265
}
265266

266267
for _, tc := range testCases {
267268
t.Run(tc.name, func(t *testing.T) {
268269
t.Parallel()
269270

270-
credsFile := filepath.Join(t.TempDir(), "credentials.json")
271-
require.NoError(t, os.WriteFile(credsFile, []byte(tc.payload), 0o600))
271+
v := gcpCredentialsVenv(t, []byte(tc.payload))
272272

273273
_, err := gcphelper.NewGCPConfigBuilder().
274-
WithSessionConfig(&gcphelper.GCPSessionConfig{Credentials: credsFile}).
275-
Build(context.Background(), venvtest.NewWithOSFS().WithEnv(map[string]string{}))
276-
require.ErrorIs(t, err, tc.expected)
274+
WithSessionConfig(&gcphelper.GCPSessionConfig{Credentials: virtualCredentialsPath}).
275+
Build(t.Context(), v)
276+
277+
var parseErr gcphelper.ParsingCredentialsError
278+
require.ErrorAs(t, err, &parseErr)
277279
})
278280
}
279281
}
280282

283+
// TestGcpConfigUnsupportedCredentialsType pins that a type the SDK does not accept is
284+
// reported as a build failure naming the type, not as a parse failure.
285+
func TestGcpConfigUnsupportedCredentialsType(t *testing.T) {
286+
t.Parallel()
287+
288+
v := gcpCredentialsVenv(t, []byte(`{"type":"gce_metadata"}`))
289+
290+
_, err := gcphelper.NewGCPConfigBuilder().
291+
WithSessionConfig(&gcphelper.GCPSessionConfig{Credentials: virtualCredentialsPath}).
292+
Build(t.Context(), v)
293+
294+
var buildErr gcphelper.BuildingCredentialsError
295+
require.ErrorAs(t, err, &buildErr)
296+
assert.Equal(t, credentials.CredType("gce_metadata"), buildErr.CredType)
297+
}
298+
281299
// TestGcpConfigEmptyCredentialsFileFallsBackToADC pins the behaviour an unpopulated secret
282300
// volume depends on: an empty file contributes no option rather than failing the run.
283301
func TestGcpConfigEmptyCredentialsFileFallsBackToADC(t *testing.T) {
284302
t.Parallel()
285303

286-
credsFile := filepath.Join(t.TempDir(), "credentials.json")
287-
require.NoError(t, os.WriteFile(credsFile, nil, 0o600))
304+
v := gcpCredentialsVenv(t, nil)
288305

289306
clientOpts, err := gcphelper.NewGCPConfigBuilder().
290-
WithSessionConfig(&gcphelper.GCPSessionConfig{Credentials: credsFile}).
291-
Build(context.Background(), venvtest.NewWithOSFS().WithEnv(map[string]string{}))
307+
WithSessionConfig(&gcphelper.GCPSessionConfig{Credentials: virtualCredentialsPath}).
308+
Build(t.Context(), v)
292309
require.NoError(t, err)
293310
assert.Empty(t, clientOpts)
294311
}
@@ -299,16 +316,27 @@ func TestGcpConfigEmptyCredentialsFileFallsBackToADC(t *testing.T) {
299316
func TestGcpConfigEmptyGACDoesNotFallBackToGoogleCredentials(t *testing.T) {
300317
t.Parallel()
301318

302-
gacFile := filepath.Join(t.TempDir(), "gac.json")
303-
require.NoError(t, os.WriteFile(gacFile, nil, 0o600))
304-
305-
env := map[string]string{
306-
"GOOGLE_APPLICATION_CREDENTIALS": gacFile,
319+
v := gcpCredentialsVenv(t, nil).WithEnv(map[string]string{
320+
"GOOGLE_APPLICATION_CREDENTIALS": virtualCredentialsPath,
307321
"GOOGLE_CREDENTIALS": string(serviceAccountJSON(t)),
308-
}
322+
})
309323

310-
clientOpts, err := gcphelper.NewGCPConfigBuilder().
311-
Build(context.Background(), venvtest.NewWithOSFS().WithEnv(env))
324+
clientOpts, err := gcphelper.NewGCPConfigBuilder().Build(t.Context(), v)
312325
require.NoError(t, err)
313326
assert.Empty(t, clientOpts, "leftover GOOGLE_CREDENTIALS must not win over an empty GAC file")
314327
}
328+
329+
// virtualCredentialsPath is where gcpCredentialsVenv writes the payload under test.
330+
const virtualCredentialsPath = "/virtual/gcp/credentials.json"
331+
332+
// gcpCredentialsVenv returns an in-memory venv holding payload at [virtualCredentialsPath].
333+
func gcpCredentialsVenv(t *testing.T, payload []byte) *venv.Venv {
334+
t.Helper()
335+
336+
v := venvtest.New().WithEnv(map[string]string{})
337+
338+
require.NoError(t, v.FS.MkdirAll(filepath.Dir(virtualCredentialsPath), 0o755))
339+
require.NoError(t, vfs.WriteFile(v.FS, virtualCredentialsPath, payload, 0o600))
340+
341+
return v
342+
}

internal/gcphelper/errors.go

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,38 @@
11
package gcphelper
22

3-
import "errors"
3+
import (
4+
"errors"
5+
"fmt"
46

5-
var (
6-
// ErrParsingCredentials reports a GCP credentials payload that is not usable JSON.
7-
ErrParsingCredentials = errors.New("error parsing GCP credentials")
8-
// ErrBuildingCredentials reports a credentials payload the SDK refused to turn into credentials.
9-
ErrBuildingCredentials = errors.New("error building GCP credentials")
7+
"cloud.google.com/go/auth/credentials"
108
)
9+
10+
// ErrMissingCredentialsType reports a credentials payload with no "type" field. Match with errors.Is.
11+
var ErrMissingCredentialsType = errors.New("the payload has no \"type\" field")
12+
13+
// ParsingCredentialsError reports a GCP credentials payload that cannot be read as JSON. Match with errors.As.
14+
type ParsingCredentialsError struct {
15+
Err error
16+
}
17+
18+
func (err ParsingCredentialsError) Error() string {
19+
return fmt.Sprintf("error parsing GCP credentials: %s", err.Err)
20+
}
21+
22+
func (err ParsingCredentialsError) Unwrap() error {
23+
return err.Err
24+
}
25+
26+
// BuildingCredentialsError reports a credentials payload the SDK refused to accept. Match with errors.As.
27+
type BuildingCredentialsError struct {
28+
Err error
29+
CredType credentials.CredType
30+
}
31+
32+
func (err BuildingCredentialsError) Error() string {
33+
return fmt.Sprintf("error building GCP credentials of type %q: %s", err.CredType, err.Err)
34+
}
35+
36+
func (err BuildingCredentialsError) Unwrap() error {
37+
return err.Err
38+
}

0 commit comments

Comments
 (0)