Skip to content

Commit cc65d94

Browse files
authored
feat(internal/detect): add helper to detect projectID from env (#4582)
This is meant to be used by certain veneers so that we can detect the projectID in a consistent manner. Future PRs will use this logic. Fixes: #1294
1 parent e98262e commit cc65d94

File tree

2 files changed

+147
-0
lines changed

2 files changed

+147
-0
lines changed

internal/detect/detect.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package detect is used find information from the environment.
16+
package detect
17+
18+
import (
19+
"context"
20+
"errors"
21+
"fmt"
22+
"os"
23+
24+
"golang.org/x/oauth2/google"
25+
"google.golang.org/api/option"
26+
"google.golang.org/api/transport"
27+
)
28+
29+
const (
30+
projectIDSentinel = "*detect-project-id*"
31+
envProjectID = "GOOGLE_CLOUD_PROJECT"
32+
)
33+
34+
var (
35+
adcLookupFunc func(context.Context, ...option.ClientOption) (*google.Credentials, error) = transport.Creds
36+
envLookupFunc func(string) string = os.Getenv
37+
)
38+
39+
// ProjectID tries to detect the project ID from the environment if the sentinel
40+
// value, "*detect-project-id*", is sent. It looks in the following order:
41+
// 1. GOOGLE_CLOUD_PROJECT envvar
42+
// 2. ADC creds.ProjectID
43+
// 3. A static value if the environment is emulated.
44+
func ProjectID(ctx context.Context, projectID string, emulatorEnvVar string, opts ...option.ClientOption) (string, error) {
45+
if projectID != projectIDSentinel {
46+
return projectID, nil
47+
}
48+
// 1. Try a well known environment variable
49+
if id := envLookupFunc(envProjectID); id != "" {
50+
return id, nil
51+
}
52+
// 2. Try ADC
53+
creds, err := adcLookupFunc(ctx, opts...)
54+
if err != nil {
55+
return "", fmt.Errorf("fetching creds: %v", err)
56+
}
57+
// 3. If ADC does not work, and the environment is emulated, return a const value.
58+
if creds.ProjectID == "" && emulatorEnvVar != "" && envLookupFunc(emulatorEnvVar) != "" {
59+
return "emulated-project", nil
60+
}
61+
// 4. If 1-3 don't work, error out
62+
if creds.ProjectID == "" {
63+
return "", errors.New("unable to detect projectID, please refer to docs for DetectProjectID")
64+
}
65+
// Success from ADC
66+
return creds.ProjectID, nil
67+
}

internal/detect/detect_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright 2021 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package detect
16+
17+
import (
18+
"context"
19+
"testing"
20+
21+
"golang.org/x/oauth2/google"
22+
"google.golang.org/api/option"
23+
)
24+
25+
func TestIt(t *testing.T) {
26+
tests := []struct {
27+
name string
28+
projectID string
29+
env map[string]string
30+
adcProjectID string
31+
want string
32+
}{
33+
{
34+
name: "noop",
35+
projectID: "noop",
36+
want: "noop",
37+
},
38+
{
39+
name: "environment project id",
40+
projectID: projectIDSentinel,
41+
env: map[string]string{envProjectID: "environment-project-id"},
42+
want: "environment-project-id",
43+
},
44+
{
45+
name: "adc project id",
46+
projectID: projectIDSentinel,
47+
adcProjectID: "adc-project-id",
48+
want: "adc-project-id",
49+
},
50+
{
51+
name: "emulator project id",
52+
projectID: projectIDSentinel,
53+
env: map[string]string{"EMULATOR_HOST": "something"},
54+
want: "emulated-project",
55+
},
56+
}
57+
58+
for _, tc := range tests {
59+
t.Run(tc.name, func(t *testing.T) {
60+
envLookupFunc = func(k string) string {
61+
if tc.env == nil {
62+
return ""
63+
}
64+
return tc.env[k]
65+
}
66+
adcLookupFunc = func(context.Context, ...option.ClientOption) (*google.Credentials, error) {
67+
return &google.Credentials{ProjectID: tc.adcProjectID}, nil
68+
}
69+
70+
got, err := ProjectID(context.Background(), tc.projectID, "EMULATOR_HOST")
71+
if err != nil {
72+
t.Fatalf("unexpected error from ProjectID(): %v", err)
73+
}
74+
if got != tc.want {
75+
t.Fatalf("ProjectID() = %q, want %q", got, tc.want)
76+
}
77+
})
78+
}
79+
80+
}

0 commit comments

Comments
 (0)