Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
4107b3a
Add datasource for observability folder settings
leowonderful Jan 27, 2026
7e22557
Add required wait to test
leowonderful Jan 30, 2026
0011be5
remove retry logic from datasource
leowonderful Jan 30, 2026
07a0ec0
Add Observability Folder Settings resource
leowonderful Jan 30, 2026
586ab72
reflect beta, not ga
leowonderful Feb 5, 2026
ca7f7c7
Test formatting fixes
leowonderful Feb 24, 2026
5a35816
make tests beta-only
leowonderful Feb 24, 2026
768777e
fully make tests beta-only
leowonderful Feb 24, 2026
23ec614
stop using autogen async
leowonderful Feb 24, 2026
309d58f
format and add version guards
leowonderful Feb 25, 2026
69be1ac
guard the entire import block
leowonderful Feb 26, 2026
d76e4d7
add docs
leowonderful Feb 26, 2026
eda2f96
reflect beta-only in datasource doc
leowonderful Feb 26, 2026
4a06d7a
backlink to resource
leowonderful Feb 26, 2026
dcfb264
just use a shared pre_create and pre_update for all observability set…
leowonderful Feb 26, 2026
2fcae96
actually just use unique pre_[create, update]
leowonderful Feb 26, 2026
33f530c
version guard datasource and remove custom operation
leowonderful Feb 26, 2026
667e225
add sleep for propagation
leowonderful Feb 26, 2026
1cf8a27
More descriptive fmt.Error
leowonderful Feb 26, 2026
da4f729
Make datasource more robust and accept empty string masks
leowonderful Feb 26, 2026
3fc060b
stop re-setting obj during mask construction
leowonderful Feb 27, 2026
9774cc4
Add new guide to datasource docs
leowonderful Feb 27, 2026
8d24055
format examples correctly, use `update_mask`
leowonderful Feb 27, 2026
2fd215f
Merge branch 'main' into observability-folder-settings-resource
leowonderful Mar 2, 2026
879efeb
Fix "too many arguments" by custom defining Wait operation
leowonderful Mar 2, 2026
28596ae
use `include_project` so we can get rid of the custom operation code
leowonderful Mar 3, 2026
9dfcdb7
also do include_project for org settings
leowonderful Mar 3, 2026
1d941ed
Merge branch 'main' into observability-folder-settings-resource
leowonderful Mar 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions mmv1/products/observability/FolderSettings.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Copyright 2026 Google Inc.
# 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.

---
name: 'FolderSettings'
description: Manages Cloud Observability settings for a folder.
min_version: 'beta'

base_url: 'folders/{{folder}}/locations/{{location}}/settings'
self_link: 'folders/{{folder}}/locations/{{location}}/settings'
create_url: 'folders/{{folder}}/locations/{{location}}/settings'
create_verb: 'PATCH'
update_url: 'folders/{{folder}}/locations/{{location}}/settings'
update_verb: 'PATCH'
exclude_delete: true
autogen_async: true
update_mask: true

custom_code:
pre_create: templates/terraform/pre_create/observability_folder_settings.go.tmpl
import_format:
- 'folders/{{folder}}/locations/{{location}}/settings'

async:
include_project: true
operation:
timeouts:
insert_minutes: 10
update_minutes: 10
base_url: '{{op_id}}'
result:
resource_inside_response: true

examples:
- name: "observability_folder_settings_basic"
config_path: "templates/terraform/examples/observability_folder_settings_basic.tf.tmpl"
primary_resource_id: "primary"
external_providers: ["time"]
vars:
location: "us"
kms_key_name: "example-key"
folder_name: "tf-test-folder"
test_env_vars:
org_id: 'ORG_ID'
test_vars_overrides:
kms_key_name: 'acctest.BootstrapKMSKeyInLocation(t, "us").CryptoKey.Name'
- name: "observability_folder_settings_basic_global"
config_path: "templates/terraform/examples/observability_folder_settings_basic_global.tf.tmpl"
primary_resource_id: "primary_global"
external_providers: ["time"]
vars:
location: "global"
test_env_vars:
org_id: 'ORG_ID'

parameters:
- name: 'location'
type: String
description: 'The location of the settings.'
url_param_only: true
required: true
immutable: true
- name: 'folder'
type: String
description: 'The folder ID.'
url_param_only: true
required: true
immutable: true

properties:
- name: 'defaultStorageLocation'
type: String
description: 'The default storage location for new resources, e.g. buckets. Only valid for global location.'
- name: 'kmsKeyName'
type: String
description: 'The default Cloud KMS key to use for new resources. Only valid for regional locations.'
- name: 'name'
type: String
description: 'The resource name of the settings.'
output: true
- name: 'serviceAccountId'
type: String
description: 'The service account used by Cloud Observability for this folder.'
output: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
resource "google_folder" "test_folder" {
provider = "google-beta"
display_name = "tf-test-%{random_suffix}"
parent = "organizations/{{index $.TestEnvVars "org_id"}}"
deletion_protection = false
}

# Wait for the folder to be created and recognized by the Observability API
resource "time_sleep" "wait_for_settings_propagation" {
create_duration = "90s"
depends_on = [google_folder.test_folder]
}

data "google_observability_folder_settings" "settings_data" {
provider = "google-beta"
folder = google_folder.test_folder.folder_id
location = "us"
depends_on = [time_sleep.wait_for_settings_propagation]
}

# Add a delay to allow the service account to propagate
resource "time_sleep" "wait_for_sa_propagation" {
create_duration = "90s"
depends_on = [data.google_observability_folder_settings.settings_data]
}

resource "google_kms_crypto_key_iam_member" "iam" {
provider = "google-beta"
crypto_key_id = "{{index $.Vars "kms_key_name"}}"
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
member = "serviceAccount:${data.google_observability_folder_settings.settings_data.service_account_id}"
depends_on = [time_sleep.wait_for_sa_propagation]
}

resource "google_observability_folder_settings" "primary" {
provider = "google-beta"
location = "us"
folder = google_folder.test_folder.folder_id
kms_key_name = "{{index $.Vars "kms_key_name"}}"
depends_on = [google_kms_crypto_key_iam_member.iam]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
resource "google_folder" "test_folder" {
provider = "google-beta"
display_name = "tf-test-%{random_suffix}"
parent = "organizations/{{index $.TestEnvVars "org_id"}}"
deletion_protection = false
}

# Wait for the folder to be created and recognized by the Observability API
resource "time_sleep" "wait_for_folder" {
create_duration = "90s"
depends_on = [google_folder.test_folder]
}

data "google_observability_folder_settings" "settings_data" {
provider = "google-beta"
folder = google_folder.test_folder.folder_id
location = "global"
depends_on = [time_sleep.wait_for_folder]
}

resource "google_observability_folder_settings" "primary_global" {
provider = "google-beta"
location = "global"
folder = google_folder.test_folder.folder_id
default_storage_location = "us"
depends_on = [data.google_observability_folder_settings.settings_data]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
var masks []string
if !d.GetRawConfig().GetAttr("default_storage_location").IsNull() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checking for explicit set-ness in config will guard against nulling these values out, but will also create a permadiff because Terraform expects the values out to be null/zero if unset. Can you walk through why we want to guard on being set here vs just setting both values in a fixed update mask?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two reasons, the first is that we never allow users to update both resource fields (kmsKeyName and defaultStorageLocation). The second is because I want to allow setting a field to "".

Very briefly:

  • You can't set kmsKeyName if the Settings is in the global region.
  • You can't set defaultStorageLocation if the Settings is not in the global region.

You can set either one of these fields in a Settings resource, but not both. When I used the default fixed update mask the API would complain that both fields were included as update masks, this is why I needed to make a custom pre_create and pre_update in the first place.

Second, I wanted the explicit set-ness check of ...IsNull() to allow explicit clears by setting a field to the empty string "". Originally I just had this check as GetOk but this didn't work because it treated "" as being unset.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no pre_update in this change- just confirming, are you seeing the results you expect w/o one?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I'll note there's no pre_update atm, but it does appear to work as intended.

masks = append(masks, "defaultStorageLocation")
}
if !d.GetRawConfig().GetAttr("kms_key_name").IsNull() {
masks = append(masks, "kmsKeyName")
}

if len(masks) > 0 {
url, err = transport_tpg.AddQueryParams(url, map[string]string{"updateMask": strings.Join(masks, ",")})
if err != nil {
return err
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ var handwrittenDatasources = map[string]*schema.Resource{
"google_monitoring_uptime_check_ips": monitoring.DataSourceGoogleMonitoringUptimeCheckIps(),
"google_netblock_ip_ranges": resourcemanager.DataSourceGoogleNetblockIpRanges(),
{{- if ne $.TargetVersionName "ga" }}
"google_observability_folder_settings": observability.DataSourceObservabilityFolderSettings(),
"google_observability_project_settings": observability.DataSourceObservabilityProjectSettings(),
"google_observability_organization_settings": observability.DataSourceObservabilityOrganizationSettings(),
{{- end }}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package observability

{{ if ne $.TargetVersionName "ga" -}}
import (
"fmt"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-provider-google/google/tpgresource"
)

func DataSourceObservabilityFolderSettings() *schema.Resource {
dsSchema := tpgresource.DatasourceSchemaFromResourceSchema(ResourceObservabilityFolderSettings().Schema)

tpgresource.AddRequiredFieldsToSchema(dsSchema, "folder")
tpgresource.AddRequiredFieldsToSchema(dsSchema, "location")

return &schema.Resource{
Read: dataSourceObservabilityFolderSettingsRead,
Schema: dsSchema,
}
}

func dataSourceObservabilityFolderSettingsRead(d *schema.ResourceData, meta interface{}) error {
folder := d.Get("folder").(string)
location := d.Get("location").(string)

id := fmt.Sprintf("folders/%s/locations/%s/settings", folder, location)
d.SetId(id)

err := resourceObservabilityFolderSettingsRead(d, meta)
if err != nil {
return nil
}
if d.Id() == "" {
return fmt.Errorf("%s not found", id)
}
return nil
}
{{- end }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package observability_test

{{ if ne $.TargetVersionName "ga" -}}
import (
"testing"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-provider-google/google/acctest"
"github.com/hashicorp/terraform-provider-google/google/envvar"
)

func TestAccObservabilityFolderSettings_datasource(t *testing.T) {
t.Parallel()

orgId := envvar.GetTestOrgFromEnv(t)
folderDisplayName := "tf-test-" + acctest.RandString(t, 10)

context := map[string]interface{}{
"org_id": orgId,
"folder_display_name": folderDisplayName,
"location": "us",
}
dataResourceName := "data.google_observability_folder_settings.settings"

acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderBetaFactories(t),
ExternalProviders: map[string]resource.ExternalProvider{
"time": {},
},
Steps: []resource.TestStep{
{
Config: testAccObservabilityFolderSettings_datasource(context),
Check: resource.ComposeTestCheckFunc(
acctest.CheckDataSourceStateMatchesResourceState(dataResourceName, "google_observability_folder_settings.settings"),
),
},
},
})
}

func testAccObservabilityFolderSettings_datasource(context map[string]interface{}) string {
return acctest.Nprintf(`
resource "google_folder" "test" {
provider = google-beta
display_name = "%{folder_display_name}"
parent = "organizations/%{org_id}"
deletion_protection = false
}

resource "time_sleep" "wait_for_folder" {
create_duration = "90s"
depends_on = [google_folder.test]
}

resource "google_observability_folder_settings" "settings" {
provider = google-beta
folder = google_folder.test.folder_id
location = "%{location}"
kms_key_name = ""
depends_on = [time_sleep.wait_for_folder]
}

data "google_observability_folder_settings" "settings" {
provider = google-beta
folder = google_folder.test.folder_id
location = "%{location}"
depends_on = [time_sleep.wait_for_folder]
}
`, context)
}
{{- end }}
Loading
Loading