Skip to content

Commit 8351112

Browse files
Generate Terraform (#16)
* Generate terraform * Refactor to Terraform Configurations * Allow configuring the provider version * Mandate PROJECT_ID on workspaces * Feature flag terraform * Add some README * with_terraform instead generate_terraform * bump tf * consistently case variable * Bump minimum ruby * Capitalize Dimensions
1 parent d40e849 commit 8351112

15 files changed

+375
-12
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ jobs:
1414
strategy:
1515
matrix:
1616
ruby:
17-
- '3.1.6'
1817
- '3.2.6'
1918
- '3.3.6'
19+
- '3.4.1'
2020

2121
steps:
2222
- uses: actions/checkout@v4

.rubocop.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ require: rubocop-rspec
44

55
AllCops:
66
NewCops: enable
7-
TargetRubyVersion: 3.1
7+
TargetRubyVersion: 3.2
88

99
Style/StringLiterals:
1010
EnforcedStyle: double_quotes

README.md

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,29 @@ manifold add <data_project_name>
6161
After you fill out the manifold.yml file, this command generates the necessary BigQuery schema files based on the specified dimensions and metrics.
6262

6363
```bash
64-
manifold generate <data_project_name> bq
64+
manifold generate
65+
```
66+
67+
4. **Generate Terraform Configuration (Optional)**
68+
69+
Manifold can optionally generate Terraform configurations for managing your BigQuery resources. To generate both BigQuery schemas and Terraform configurations, use the `--tf` flag:
70+
71+
```bash
72+
manifold generate --tf
73+
```
74+
75+
This will create:
76+
77+
- A root `main.tf.json` file that sets up the Google Cloud provider and workspace modules
78+
- Individual workspace configurations in `workspaces/<workspace_name>/main.tf.json`
79+
- Dataset and table definitions that reference your generated BigQuery schemas
80+
81+
The generated Terraform configurations use the Google Cloud provider and expect a `project_id` variable to be set. You can apply these configurations using standard Terraform commands:
82+
83+
```bash
84+
terraform init
85+
terraform plan -var="project_id=your-project-id"
86+
terraform apply -var="project_id=your-project-id"
6587
```
6688

6789
## Manifold Configuration

lib/manifold/api/project.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ def workspaces
2222
@workspaces ||= workspace_directories.map { |dir| Workspace.from_directory(dir, logger:) }
2323
end
2424

25-
def generate
26-
workspaces.each(&:generate)
25+
def generate(with_terraform: false)
26+
workspaces.each { |w| w.generate(with_terraform:) }
27+
generate_terraform_entrypoint if with_terraform
2728
end
2829

2930
def workspaces_directory
@@ -39,6 +40,11 @@ def vectors_directory
3940
def workspace_directories
4041
workspaces_directory.children.select(&:directory?)
4142
end
43+
44+
def generate_terraform_entrypoint
45+
config = Terraform::ProjectConfiguration.new(workspaces)
46+
config.write(directory.join("main.tf.json"))
47+
end
4248
end
4349
end
4450
end

lib/manifold/api/workspace.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,11 @@ def add
2626
FileUtils.cp(template_path, manifold_path)
2727
end
2828

29-
def generate
29+
def generate(with_terraform: false)
3030
return unless manifold_exists? && any_vectors?
3131

3232
generate_dimensions
33+
generate_terraform if with_terraform
3334
logger.info("Generated BigQuery dimensions table schema for workspace '#{name}'.")
3435
end
3536

@@ -99,6 +100,15 @@ def any_vectors?
99100
def vectors
100101
manifold_yaml["vectors"]
101102
end
103+
104+
def generate_terraform
105+
config = Terraform::WorkspaceConfiguration.new(name)
106+
config.write(terraform_main_path)
107+
end
108+
109+
def terraform_main_path
110+
directory.join("main.tf.json")
111+
end
102112
end
103113
end
104114
end

lib/manifold/cli.rb

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,12 @@ def add(name)
4646
end
4747

4848
desc "generate", "Generate BigQuery schema for all workspaces in the project"
49+
method_option :tf, type: :boolean, desc: "Generate Terraform configurations"
4950
def generate
50-
name = Pathname.pwd.basename.to_s
51-
project = API::Project.new(name, directory: Pathname.pwd, logger:)
52-
project.generate
51+
path = Pathname.pwd
52+
name = path.basename.to_s
53+
project = API::Project.new(name, directory: path, logger:)
54+
project.generate(with_terraform: options[:tf])
5355
logger.info "Generated BigQuery schema for all workspaces in the project."
5456
end
5557
end
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# frozen_string_literal: true
2+
3+
module Manifold
4+
module Terraform
5+
# Provides a base class for Terraform configuration files.
6+
class Configuration
7+
def as_json
8+
raise NotImplementedError, "#{self.class} must implement #as_json"
9+
end
10+
11+
def write(path)
12+
path.write("#{JSON.pretty_generate(as_json)}\n")
13+
end
14+
end
15+
end
16+
end
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# frozen_string_literal: true
2+
3+
module Manifold
4+
module Terraform
5+
# Represents a Terraform configuration for a Manifold project.
6+
class ProjectConfiguration < Configuration
7+
attr_reader :workspaces, :provider_version
8+
9+
DEFAULT_TERRAFORM_GOOGLE_PROVIDER_VERSION = "6.18.1"
10+
11+
def initialize(workspaces, provider_version: DEFAULT_TERRAFORM_GOOGLE_PROVIDER_VERSION)
12+
super()
13+
@workspaces = workspaces
14+
@provider_version = provider_version
15+
end
16+
17+
def as_json
18+
{
19+
"terraform" => terraform_block,
20+
"provider" => provider_block,
21+
"variable" => variables_block,
22+
"module" => workspace_modules
23+
}
24+
end
25+
26+
private
27+
28+
def terraform_block
29+
{
30+
"required_providers" => {
31+
"google" => {
32+
"source" => "hashicorp/google",
33+
"version" => provider_version
34+
}
35+
}
36+
}
37+
end
38+
39+
def provider_block
40+
{
41+
"google" => {
42+
"project" => "${var.project_id}"
43+
}
44+
}
45+
end
46+
47+
def variables_block
48+
{
49+
"project_id" => {
50+
"description" => "The GCP project ID where resources will be created",
51+
"type" => "string"
52+
}
53+
}
54+
end
55+
56+
def workspace_modules
57+
workspaces.each_with_object({}) do |workspace, modules|
58+
modules[workspace.name] = {
59+
"source" => "./workspaces/#{workspace.name}",
60+
"project_id" => "${var.project_id}"
61+
}
62+
end
63+
end
64+
end
65+
end
66+
end
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# frozen_string_literal: true
2+
3+
module Manifold
4+
module Terraform
5+
# Represents a Terraform configuration for a Manifold workspace.
6+
class WorkspaceConfiguration < Configuration
7+
attr_reader :name
8+
9+
def initialize(name)
10+
super()
11+
@name = name
12+
end
13+
14+
def as_json
15+
{
16+
"variable" => variables_block,
17+
"resource" => {
18+
"google_bigquery_dataset" => dataset_config,
19+
"google_bigquery_table" => table_config
20+
}
21+
}
22+
end
23+
24+
private
25+
26+
def variables_block
27+
{
28+
"project_id" => {
29+
"description" => "The GCP project ID where resources will be created",
30+
"type" => "string"
31+
}
32+
}
33+
end
34+
35+
def dataset_config
36+
{
37+
name => {
38+
"dataset_id" => name,
39+
"project" => "${var.project_id}",
40+
"location" => "US"
41+
}
42+
}
43+
end
44+
45+
def table_config
46+
{
47+
"dimensions" => {
48+
"dataset_id" => name,
49+
"project" => "${var.project_id}",
50+
"table_id" => "Dimensions",
51+
"schema" => "${file(\"${path.module}/tables/dimensions.json\")}",
52+
"depends_on" => ["google_bigquery_dataset.#{name}"]
53+
}
54+
}
55+
end
56+
end
57+
end
58+
end

manifold.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
1111
spec.summary = "A CLI for managing data infrastructures in BigQuery"
1212
spec.homepage = "https://github.com/bustle/manifold"
1313
spec.license = "MIT"
14-
spec.required_ruby_version = ">= 3.1.0"
14+
spec.required_ruby_version = ">= 3.2.0"
1515

1616
spec.metadata["homepage_uri"] = spec.homepage
1717
spec.metadata["source_code_uri"] = "https://github.com/bustle/manifold"

0 commit comments

Comments
 (0)