Skip to content

Commit 4f4f315

Browse files
feat: datasource for supabase_project_apikeys (#212)
* ♻️ (provider): refactor data source implementation to follow standard Go coding conventions and improve readability * ♻️ (internal/provider/project_apikeys_data_source_test.go): refactor test functions for better organization and reusability * ✨ (docs/tutorial.md): add support for retrieving project API keys using data source ♻️ (docs/tutorial.md): refactor code to use Terraform data sources instead of hardcoded values * fix tests * linter and schema --------- Co-authored-by: Han Qiao <qiao@supabase.io>
1 parent df6a6c2 commit 4f4f315

File tree

6 files changed

+245
-2
lines changed

6 files changed

+245
-2
lines changed

docs/data-sources/project_apikeys.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
# generated by https://github.com/hashicorp/terraform-plugin-docs
3+
page_title: "supabase_project_apikeys Data Source - terraform-provider-supabase"
4+
subcategory: ""
5+
description: |-
6+
Project API Keys data source
7+
---
8+
9+
# supabase_project_apikeys (Data Source)
10+
11+
Project API Keys data source
12+
13+
14+
15+
<!-- schema generated by tfplugindocs -->
16+
## Schema
17+
18+
### Required
19+
20+
- `project_id` (String) Project identifier
21+
22+
### Read-Only
23+
24+
- `anon_key` (String, Sensitive) Anonymous API key for the project
25+
- `service_role_key` (String, Sensitive) Service role API key for the project

docs/schema.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,35 @@
288288
"description": "Pooler data source",
289289
"description_kind": "markdown"
290290
}
291+
},
292+
"supabase_project_apikeys": {
293+
"version": 0,
294+
"block": {
295+
"attributes": {
296+
"anon_key": {
297+
"type": "string",
298+
"description": "Anonymous API key for the project",
299+
"description_kind": "markdown",
300+
"computed": true,
301+
"sensitive": true
302+
},
303+
"project_id": {
304+
"type": "string",
305+
"description": "Project identifier",
306+
"description_kind": "markdown",
307+
"required": true
308+
},
309+
"service_role_key": {
310+
"type": "string",
311+
"description": "Service role API key for the project",
312+
"description_kind": "markdown",
313+
"computed": true,
314+
"sensitive": true
315+
}
316+
},
317+
"description": "Project API Keys data source",
318+
"description_kind": "markdown"
319+
}
291320
}
292321
}
293322
}

docs/tutorial.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,27 @@ resource "supabase_project" "production" {
4141
ignore_changes = [database_password]
4242
}
4343
}
44+
45+
# Retrieve project API keys
46+
data "supabase_project_apikeys" "production" {
47+
project_id = supabase_project.production.id
48+
}
49+
50+
# Output the API keys (careful with sensitive data!)
51+
output "anon_key" {
52+
value = data.supabase_project_apikeys.production.anon_key
53+
sensitive = true
54+
}
55+
56+
output "service_role_key" {
57+
value = data.supabase_project_apikeys.production.service_role_key
58+
sensitive = true
59+
}
4460
```
4561

46-
Remember to substitute placeholder values with your own. For sensitive fields such as the password, consider storing and retrieving them from a secure credentials store.
62+
Remember to substitute placeholder values with your own. For sensitive fields such as the password, consider storing and retrieving them from a secure credentials store. The API keys are marked as sensitive and will be hidden in logs, but make sure to handle them securely in your workflow.
4763

48-
Next, run `terraform -chdir=module apply` to create the new project resource.
64+
Next, run `terraform -chdir=module apply` to create the new project resource and retrieve its API keys.
4965

5066
### Importing a project
5167

@@ -75,6 +91,11 @@ resource "supabase_project" "production" {
7591
ignore_changes = [database_password]
7692
}
7793
}
94+
95+
# Retrieve project API keys
96+
data "supabase_project_apikeys" "production" {
97+
project_id = supabase_project.production.id
98+
}
7899
```
79100

80101
Run `terraform -chdir=module apply`. Enter the ID of your Supabase project at the prompt. If your local TF state is empty, your project will be imported from remote rather than recreated.
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package provider
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
"github.com/hashicorp/terraform-plugin-framework/datasource"
11+
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
12+
"github.com/hashicorp/terraform-plugin-framework/types"
13+
"github.com/hashicorp/terraform-plugin-log/tflog"
14+
"github.com/supabase/cli/pkg/api"
15+
)
16+
17+
// Ensure provider defined types fully satisfy framework interfaces.
18+
var _ datasource.DataSource = &ProjectAPIKeysDataSource{}
19+
20+
func NewProjectAPIKeysDataSource() datasource.DataSource {
21+
return &ProjectAPIKeysDataSource{}
22+
}
23+
24+
// ProjectAPIKeysDataSource defines the data source implementation.
25+
type ProjectAPIKeysDataSource struct {
26+
client *api.ClientWithResponses
27+
}
28+
29+
// ProjectAPIKeysDataSourceModel describes the data source data model.
30+
type ProjectAPIKeysDataSourceModel struct {
31+
ProjectId types.String `tfsdk:"project_id"`
32+
AnonKey types.String `tfsdk:"anon_key"`
33+
ServiceRoleKey types.String `tfsdk:"service_role_key"`
34+
}
35+
36+
func (d *ProjectAPIKeysDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
37+
resp.TypeName = req.ProviderTypeName + "_project_apikeys"
38+
}
39+
40+
func (d *ProjectAPIKeysDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
41+
resp.Schema = schema.Schema{
42+
MarkdownDescription: "Project API Keys data source",
43+
44+
Attributes: map[string]schema.Attribute{
45+
"project_id": schema.StringAttribute{
46+
MarkdownDescription: "Project identifier",
47+
Required: true,
48+
},
49+
"anon_key": schema.StringAttribute{
50+
MarkdownDescription: "Anonymous API key for the project",
51+
Computed: true,
52+
Sensitive: true,
53+
},
54+
"service_role_key": schema.StringAttribute{
55+
MarkdownDescription: "Service role API key for the project",
56+
Computed: true,
57+
Sensitive: true,
58+
},
59+
},
60+
}
61+
}
62+
63+
func (d *ProjectAPIKeysDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
64+
// Prevent panic if the provider has not been configured.
65+
if req.ProviderData == nil {
66+
return
67+
}
68+
69+
client, ok := req.ProviderData.(*api.ClientWithResponses)
70+
if !ok {
71+
resp.Diagnostics.AddError(
72+
"Unexpected Data Source Configure Type",
73+
fmt.Sprintf("Expected *api.ClientWithResponses, got: %T. Please report this issue to the provider developers.", req.ProviderData),
74+
)
75+
return
76+
}
77+
78+
d.client = client
79+
}
80+
81+
func (d *ProjectAPIKeysDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
82+
var data ProjectAPIKeysDataSourceModel
83+
84+
// Read Terraform configuration data into the model
85+
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
86+
if resp.Diagnostics.HasError() {
87+
return
88+
}
89+
90+
httpResp, err := d.client.V1GetProjectApiKeysWithResponse(ctx, data.ProjectId.ValueString(), &api.V1GetProjectApiKeysParams{})
91+
if err != nil {
92+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read project API keys, got error: %s", err))
93+
return
94+
}
95+
96+
if httpResp.JSON200 == nil {
97+
resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to read project API keys, got status %d: %s", httpResp.StatusCode(), httpResp.Body))
98+
return
99+
}
100+
101+
for _, key := range *httpResp.JSON200 {
102+
switch key.Name {
103+
case "anon":
104+
data.AnonKey = types.StringValue(key.ApiKey)
105+
case "service_role":
106+
data.ServiceRoleKey = types.StringValue(key.ApiKey)
107+
}
108+
}
109+
110+
tflog.Trace(ctx, "read project API keys")
111+
112+
// Save data into Terraform state
113+
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
114+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package provider
5+
6+
import (
7+
"net/http"
8+
"testing"
9+
10+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
11+
"github.com/supabase/cli/pkg/api"
12+
"gopkg.in/h2non/gock.v1"
13+
)
14+
15+
func TestAccProjectAPIKeysDataSource(t *testing.T) {
16+
// Setup mock api
17+
defer gock.OffAll()
18+
gock.New("https://api.supabase.com").
19+
Get("/v1/projects/mayuaycdtijbctgqbycg/api-keys").
20+
Times(3).
21+
Reply(http.StatusOK).
22+
JSON([]api.ApiKeyResponse{
23+
{
24+
Name: "anon",
25+
ApiKey: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.anon",
26+
},
27+
{
28+
Name: "service_role",
29+
ApiKey: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.service_role",
30+
},
31+
})
32+
33+
resource.Test(t, resource.TestCase{
34+
PreCheck: func() { testAccPreCheck(t) },
35+
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
36+
Steps: []resource.TestStep{
37+
// Read testing
38+
{
39+
Config: testAccProjectAPIKeysDataSourceConfig,
40+
Check: resource.ComposeAggregateTestCheckFunc(
41+
resource.TestCheckResourceAttr("data.supabase_project_apikeys.production", "anon_key", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.anon"),
42+
resource.TestCheckResourceAttr("data.supabase_project_apikeys.production", "service_role_key", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.service_role"),
43+
),
44+
},
45+
},
46+
})
47+
}
48+
49+
const testAccProjectAPIKeysDataSourceConfig = `
50+
data "supabase_project_apikeys" "production" {
51+
project_id = "mayuaycdtijbctgqbycg"
52+
}
53+
`

internal/provider/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ func (p *SupabaseProvider) DataSources(ctx context.Context) []func() datasource.
9696
return []func() datasource.DataSource{
9797
NewBranchDataSource,
9898
NewPoolerDataSource,
99+
NewProjectAPIKeysDataSource,
99100
}
100101
}
101102

0 commit comments

Comments
 (0)