Skip to content

Commit

Permalink
region hacking generalization
Browse files Browse the repository at this point in the history
  • Loading branch information
kevmoo committed Jul 6, 2023
1 parent 8838d5b commit 1a36004
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 170 deletions.
7 changes: 1 addition & 6 deletions gcp/lib/gcp.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,13 @@ export 'src/gcp_project.dart'
gcpProjectIdEnvironmentVariables,
projectIdFromEnvironment,
projectIdFromMetadataServer;
export 'src/gpc_region.dart'
show
computeRegion,
gcpRegionEnvironmentVariables,
regionFromEnvironment,
regionFromMetadataServer;
export 'src/log_severity.dart' show LogSeverity, RequestLogger;
export 'src/logging.dart'
show
badRequestMiddleware,
cloudLoggingMiddleware,
createLoggingMiddleware,
currentLogger;
export 'src/metadata.dart' show MetadataValue;
export 'src/serve.dart' show listenPort, serveHandler;
export 'src/terminate.dart' show waitForTerminate;
65 changes: 8 additions & 57 deletions gcp/lib/src/gcp_project.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import 'dart:io';

import 'package:http/http.dart' as http;

import 'bad_configuration_exception.dart';
import 'metadata.dart';

/// A convenience wrapper that first tries [projectIdFromEnvironment]
/// then (if the value is `null`) tries [projectIdFromMetadataServer]
///
/// Like [projectIdFromMetadataServer], if no value is found, a
/// [BadConfigurationException] is thrown.
Future<String> computeProjectId() async {
final localValue = projectIdFromEnvironment();
if (localValue != null) {
return localValue;
}
final result = await projectIdFromMetadataServer();

return result;
}
Future<String> computeProjectId() =>
MetadataValue.project.fromEnvironmentOrMetadata();

/// Returns the
/// [Project ID](https://cloud.google.com/resource-manager/docs/creating-managing-projects#identifying_projects)
Expand All @@ -41,14 +31,7 @@ Future<String> computeProjectId() async {
/// The list is checked in order. This is useful for local development.
///
/// If no matching variable is found, `null` is returned.
String? projectIdFromEnvironment() {
for (var envKey in gcpProjectIdEnvironmentVariables) {
final value = Platform.environment[envKey];
if (value != null) return value;
}

return null;
}
String? projectIdFromEnvironment() => MetadataValue.project.fromEnvironment();

/// Returns a [Future] that completes with the
/// [Project ID](https://cloud.google.com/resource-manager/docs/creating-managing-projects#identifying_projects)
Expand All @@ -57,36 +40,8 @@ String? projectIdFromEnvironment() {
///
/// If the metadata server cannot be contacted, a [BadConfigurationException] is
/// thrown.
Future<String> projectIdFromMetadataServer() async {
const host = 'http://metadata.google.internal/';
final url = Uri.parse('$host/computeMetadata/v1/project/project-id');

try {
final response = await http.get(
url,
headers: {'Metadata-Flavor': 'Google'},
);

if (response.statusCode != 200) {
throw HttpException(
'${response.body} (${response.statusCode})',
uri: url,
);
}

return response.body;
} on SocketException catch (e) {
throw BadConfigurationException(
'''
Could not connect to $host.
If not running on Google Cloud, one of these environment variables must be set
to the target Google Project ID:
${gcpProjectIdEnvironmentVariables.join('\n')}
''',
details: e.toString(),
);
}
}
Future<String> projectIdFromMetadataServer() =>
MetadataValue.project.fromMetadataServer();

/// A set of typical environment variables that are likely to represent the
/// current Google Cloud project ID.
Expand All @@ -98,9 +53,5 @@ ${gcpProjectIdEnvironmentVariables.join('\n')}
///
/// Note: these are ordered starting from the most current/canonical to least.
/// (At least as could be determined at the time of writing.)
const gcpProjectIdEnvironmentVariables = {
'GCP_PROJECT',
'GCLOUD_PROJECT',
'CLOUDSDK_CORE_PROJECT',
'GOOGLE_CLOUD_PROJECT',
};
Set<String> get gcpProjectIdEnvironmentVariables =>
MetadataValue.project.environmentValues;
104 changes: 0 additions & 104 deletions gcp/lib/src/gpc_region.dart

This file was deleted.

138 changes: 138 additions & 0 deletions gcp/lib/src/metadata.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import 'dart:io';

import 'package:http/http.dart' as http;

import 'bad_configuration_exception.dart';

enum MetadataValue {
/// A set of typical environment variables that are likely to represent the
/// current Google Cloud instance region.
///
/// For context, see:
/// * https://cloud.google.com/functions/docs/env-var
/// * https://cloud.google.com/compute/docs/gcloud-compute#default_project
/// * https://github.com/GoogleContainerTools/gcp-auth-webhook/blob/08136ca171fe5713cc70ef822c911fbd3a1707f5/server.go#L38-L44
///
/// Note: these are ordered starting from the most current/canonical to least.
/// (At least as could be determined at the time of writing.)
project(
path: 'project/project-id',
environmentValues: {
'GCP_PROJECT',
'GCLOUD_PROJECT',
'CLOUDSDK_CORE_PROJECT',
'GOOGLE_CLOUD_PROJECT',
},
),

/// A set of typical environment variables that are likely to represent the
/// current Google Cloud instance region.
///
/// For context, see:
/// * https://cloud.google.com/functions/docs/env-var
/// * https://cloud.google.com/compute/docs/gcloud-compute#default_project
/// * https://github.com/GoogleContainerTools/gcp-auth-webhook/blob/08136ca171fe5713cc70ef822c911fbd3a1707f5/server.go#L38-L44
///
/// Note: these are ordered starting from the most current/canonical to least.
/// (At least as could be determined at the time of writing.)
region(
path: 'instance/region',
environmentValues: {
'FUNCTION_REGION',
'CLOUDSDK_COMPUTE_REGION',
},
);

const MetadataValue({
required this.path,
required this.environmentValues,
});

final String path;

final Set<String> environmentValues;

/// A convenience wrapper that first tries [fromEnvironment]
/// then (if the value is `null`) tries [fromMetadataServer]
///
/// Like [fromMetadataServer], if no value is found, a
/// [BadConfigurationException] is thrown.
Future<String> fromEnvironmentOrMetadata() async {
final localValue = fromEnvironment();
if (localValue != null) {
return localValue;
}
final result = await fromMetadataServer();

return result;
}

/// Returns the
/// [Region](https://cloud.google.com/compute/docs/regions-zones#identifying_a_region_or_zone)
/// for the current instance by checking the environment variables in
/// [environmentValues].
///
/// The list is checked in order. This is useful for local development.
///
/// If no matching variable is found, `null` is returned.
String? fromEnvironment() {
for (var envKey in environmentValues) {
final value = Platform.environment[envKey];
if (value != null) return value;
}

return null;
}

/// Returns a [Future] that completes with the
/// [Region](https://cloud.google.com/compute/docs/regions-zones#identifying_a_region_or_zone)
/// for the current instance by checking
/// [instance metadata](https://cloud.google.com/compute/docs/metadata/default-metadata-values#vm_instance_metadata).
///
/// If the metadata server cannot be contacted, a [BadConfigurationException]
/// is thrown.
Future<String> fromMetadataServer() async {
const host = 'http://metadata.google.internal/';
final url = Uri.parse('$host/computeMetadata/v1/$path');

try {
final response = await http.get(
url,
headers: {'Metadata-Flavor': 'Google'},
);

if (response.statusCode != 200) {
throw HttpException(
'${response.body} (${response.statusCode})',
uri: url,
);
}

return response.body;
} on SocketException catch (e) {
throw BadConfigurationException(
'''
Could not connect to $host.
If not running on Google Cloud, one of these environment variables must be set
to the target region:
${environmentValues.join('\n')}
''',
details: e.toString(),
);
}
}
}
6 changes: 4 additions & 2 deletions gcp/test/gcp_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ void main() {

await expectLater(
errorOut,
containsAll(gcpRegionEnvironmentVariables),
containsAll(MetadataValue.region.environmentValues),
);
await expectLater(proc.stdout, emitsDone);

Expand All @@ -152,7 +152,9 @@ void main() {
test('environment set', () async {
final proc = await _run(
regionPrint,
environment: {gcpRegionEnvironmentVariables.first: 'us-central1'},
environment: {
MetadataValue.region.environmentValues.first: 'us-central1',
},
);

await expectLater(proc.stdout, emits('us-central1'));
Expand Down
2 changes: 1 addition & 1 deletion gcp/test/src/region_print.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@
import 'package:gcp/gcp.dart';

Future<void> main() async {
print(await computeRegion());
print(await MetadataValue.region.fromEnvironmentOrMetadata());
}

0 comments on commit 1a36004

Please sign in to comment.