Skip to content

Google Cloud

terradart_google wraps the whole GA hashicorp/google catalog — every resource and data source — and terradart_google_beta adds the types that exist only in hashicorp/google-beta (Firebase app registration, preview services). A type that is also in GA stays in terradart_google. Both are generated from the provider schema and the Magic Modules definitions Google publishes, so enums, exactly-one-of groups and resource references carry over as Dart types.

pubspec.yaml
dependencies:
terradart_core: ^0.35.0
terradart_google: ^0.35.0
terradart_google_beta: ^0.35.0 # only for beta-only types

GoogleProvider takes no key, so credentials never enter the synthesized JSON, and synth needs none. terradart plan and terradart apply authenticate with Application Default Credentials (gcloud auth application-default login), or a service account in CI. With them in place, terradart apply synthesizes the Stack and applies it.

Each Google service is its own barrel, so IDE completion stays scoped to what a file uses: package:terradart_google/cloud_run.dart, pubsub.dart, bigquery.dart, compute.dart, and so on, plus provider.dart for GoogleProvider. The Google Cloud coverage page lists every factory with its barrel and a runnable example. package:terradart_google/terradart_google.dart still re-exports everything.

A fresh project has most APIs off. enableApis registers a google_project_service for every API the factories of the given barrels need, plus a propagation wait (TimeSleep from terradart_time) that resources depend on:

lib/events_stack.dart
import 'package:terradart_google/project.dart';
import 'package:terradart_google/provider.dart';
import 'package:terradart_google/pubsub.dart';
import 'package:terradart_time/terradart_time.dart';
final class EventsStack extends Stack {
EventsStack({required String projectId})
: super(providers: [GoogleProvider(project: projectId), const TimeProvider()]) {
final apis = enableApis( [.pubsub]);
add(GooglePubsubTopic(
'events',
name: .literal('events'),
dependsOn: apis,
));
}
}

An IAM adjunct takes its parent as a reference (bucket: uploads.ref) and who the grant is for as an IamPrincipal. A service account, a service agent or a default service account data source hands out its own as principal; anyone else takes the constructor of their kind: .user(email), .group(email), .serviceAccount(email), .domain(domain), .allUsers, .allAuthenticatedUsers, or .principalSet(pool, attribute) for Workload Identity Federation.

final runtime = add(
GoogleServiceAccount('runtime', accountId: .literal('runtime')),
);
final uploads = add(
GoogleStorageBucket(
'uploads',
name: .literal('uploads'),
location: .literal('US'),
),
);
add(
GoogleStorageBucketIamMember(
'runtime_writer',
bucket: uploads.ref,
role: .literal('roles/storage.objectCreator'),
member: runtime.principal,
),
);
add(
GoogleStorageBucketIamBinding(
'admins',
bucket: uploads.ref,
role: .literal('roles/storage.admin'),
members: .literal([.group('[email protected]'), .user('[email protected]')]),
),
);

GA and beta in one Stack (Firebase + Google Cloud)

Section titled “GA and beta in one Stack (Firebase + Google Cloud)”

You can seamlessly combine Google Cloud GA resources (terradart_google) with beta-only resources (terradart_google_beta, such as Firebase project configuration and Web App registration) in a single Stack:

graph TB
  subgraph Client["Firebase App (Beta)"]
    WebApp["GoogleFirebaseWebApp<br/>(Frontend Client)"]
  end

  subgraph GCP["Google Cloud Infrastructure (GA)"]
    CloudRun["GoogleCloudRunV2Service<br/>(Backend API)"]
    Firestore["GoogleFirestoreDatabase<br/>(Native Mode / (default))"]
    Storage["GoogleStorageBucket<br/>(User Uploads)"]

    CloudRun -->|Read/Write Data| Firestore
    CloudRun -->|Store Assets| Storage
  end

  WebApp -.REST API Calls.-> CloudRun
pubspec.yaml
dependencies:
terradart_core: ^0.35.0
terradart_google: ^0.35.0
terradart_google_beta: ^0.35.0
// GA: Google Cloud backend infrastructure
import 'package:terradart_google/cloud_run.dart';
import 'package:terradart_google/firestore.dart';
import 'package:terradart_google/provider.dart';
import 'package:terradart_google/storage.dart';
// Beta: Firebase project and app registration
import 'package:terradart_google_beta/firebase.dart';
import 'package:terradart_google_beta/provider.dart';
final class MobileAppBackendStack extends Stack {
MobileAppBackendStack({required String projectId})
: super(
providers: [
GoogleProvider(project: projectId, region: 'asia-northeast1'),
GoogleBetaProvider(project: projectId, region: 'asia-northeast1'),
],
) {
// 1. [Beta] Enable Firebase on the project
final fb = add(GoogleFirebaseProject(
'firebase',
project: .literal(projectId),
));
// 2. [Beta] Register Firebase client app
add(GoogleFirebaseWebApp(
'web_client',
displayName: .literal('Web Client'),
project: .literal(projectId),
dependsOn: [fb],
));
// 3. [GA] Firestore Database (Native mode)
final db = add(GoogleFirestoreDatabase(
'db',
name: .literal('(default)'),
locationId: .literal('asia-northeast1'),
type: .firestoreNative,
dependsOn: [fb],
));
// 4. [GA] Cloud Storage for user uploads
final uploadsBucket = add(GoogleStorageBucket(
'uploads',
name: .literal('$projectId-uploads'),
location: .literal('ASIA-NORTHEAST1'),
storageClass: .standard,
uniformBucketLevelAccess: .literal(true),
));
// 5. [GA] Cloud Run v2 backend service
add(GoogleCloudRunV2Service(
'api',
name: .literal('api-server'),
location: .literal('asia-northeast1'),
template: CloudRunV2ServiceTemplate(
containers: [
.new(
name: .literal('server'),
image: .literal(
'us-docker.pkg.dev/cloudrun/container/hello',
),
env: [
.new(
name: .literal('UPLOAD_BUCKET'),
source: .value(uploadsBucket.name),
),
],
),
],
),
dependsOn: [db],
));
}
}

Wrappers from terradart_google_beta automatically attach provider = "google-beta" in the synthesized Terraform JSON. See the complete runnable recipe in cookbook/firebase-app-backend.

Every factory also takes a provider: parameter — Terraform’s provider meta-argument. Register a second configuration of a provider with alias: through addProvider, which returns the instance, and pass that instance per resource; everything else keeps using the default configuration:

final class MultiRegionStack extends Stack {
MultiRegionStack({required String projectId})
: super(providers: [
GoogleProvider(project: projectId, region: 'asia-northeast1'),
]) {
final eu = addProvider(
GoogleProvider(alias: 'eu', project: projectId, region: 'europe-west1'),
);
add(GoogleStorageBucket(
'assets_eu',
name: .literal('my-app-assets-eu'),
location: .literal('EUROPE-WEST1'),
provider: eu, // provider = google.eu
));
}
}

Synth emits provider.google as a list when a name has more than one configuration, and rejects a provider: instance the Stack does not register (an equal-looking copy included, since its settings may differ). A registered GoogleBetaProvider passed as provider: on a GA-catalog factory puts that one resource on the beta provider.

Every Google quickstart is synthesized and validated against the provider in CI:

Full applications, infrastructure and app together, are in the cookbook: single-project-app (Cloud Run + Cloud SQL), firebase-app-backend, lunch-concierge.