From 2200295c156b743ca660cd62e4fd4d1cb4065623 Mon Sep 17 00:00:00 2001 From: Ilyaas Kapadia <86218345+IlyaasK@users.noreply.github.com> Date: Sat, 11 Jul 2026 07:49:57 -0400 Subject: [PATCH 1/2] Expose browser pool Chrome policy Normalize SDK Chrome-policy JSON into stable Terraform string state. Keep loose maps at the response boundary and preserve absent or null policy compatibility. --- docs/data-sources/browser_pool.md | 1 + .../datasources/browserpool/datasource.go | 33 ++++++++++++ .../browserpool/datasource_test.go | 52 ++++++++++++++++++- 3 files changed, 84 insertions(+), 2 deletions(-) diff --git a/docs/data-sources/browser_pool.md b/docs/data-sources/browser_pool.md index 7c6dccd..7b803bf 100644 --- a/docs/data-sources/browser_pool.md +++ b/docs/data-sources/browser_pool.md @@ -23,6 +23,7 @@ Lookup durable Kernel browser pool configuration. ### Read-Only +- `chrome_policy` (String) Normalized JSON object of Chrome enterprise policy overrides, if configured. - `extension_ids` (List of String) Resolved extension IDs attached to the pool, in load order. - `fill_rate_per_minute` (Number) Percentage of the pool filled per minute. - `headless` (Boolean) Whether browsers use a headless image. diff --git a/internal/datasources/browserpool/datasource.go b/internal/datasources/browserpool/datasource.go index 07fd0db..e41ccc6 100644 --- a/internal/datasources/browserpool/datasource.go +++ b/internal/datasources/browserpool/datasource.go @@ -1,10 +1,12 @@ package browserpool import ( + "bytes" "context" "encoding/json" "fmt" "strconv" + "strings" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" @@ -55,6 +57,7 @@ type browserPoolModel struct { TimeoutSeconds types.Int64 `tfsdk:"timeout_seconds"` FillRatePerMinute types.Int64 `tfsdk:"fill_rate_per_minute"` Viewport types.Object `tfsdk:"viewport"` + ChromePolicy types.String `tfsdk:"chrome_policy"` } func NewDataSource() datasource.DataSource { @@ -140,6 +143,10 @@ func (d *browserPoolDataSource) Schema(_ context.Context, _ datasource.SchemaReq "refresh_rate": dschema.Int64Attribute{Computed: true, MarkdownDescription: "Display refresh rate in Hz, if configured."}, }, }, + "chrome_policy": dschema.StringAttribute{ + Computed: true, + MarkdownDescription: "Normalized JSON object of Chrome enterprise policy overrides, if configured.", + }, }, } } @@ -281,6 +288,7 @@ func flattenBrowserPool(pool kernel.BrowserPool) (browserPoolModel, diag.Diagnos TimeoutSeconds: flattenTimeoutSeconds(config.JSON.TimeoutSeconds.Raw(), config.JSON.TimeoutSeconds.Valid(), config.TimeoutSeconds, &diags), FillRatePerMinute: flattenFillRatePerMinute(config.JSON.FillRatePerMinute.Raw(), config.JSON.FillRatePerMinute.Valid(), config.FillRatePerMinute, &diags), Viewport: flattenViewport(config.JSON.Viewport.Raw(), config.JSON.Viewport.Valid(), config.Viewport, &diags), + ChromePolicy: flattenChromePolicy(config.JSON.ChromePolicy.Raw(), config.JSON.ChromePolicy.Valid(), &diags), }, diags } @@ -384,6 +392,31 @@ func viewportAttributeTypes() map[string]attr.Type { } } +func flattenChromePolicy(raw string, valid bool, diags *diag.Diagnostics) types.String { + if raw == "" || !datasources.FieldPresent(raw) { + return types.StringNull() + } + if !valid { + datasources.AddInvalidResponseField(diags, "Browser Pool", "browser_pool_config.chrome_policy") + return types.StringNull() + } + + var policy map[string]any + if err := json.Unmarshal([]byte(raw), &policy); err != nil || policy == nil { + datasources.AddInvalidResponseField(diags, "Browser Pool", "browser_pool_config.chrome_policy") + return types.StringNull() + } + + var normalized bytes.Buffer + encoder := json.NewEncoder(&normalized) + encoder.SetEscapeHTML(false) + if err := encoder.Encode(policy); err != nil { + datasources.AddInvalidResponseField(diags, "Browser Pool", "browser_pool_config.chrome_policy") + return types.StringNull() + } + return types.StringValue(strings.TrimSuffix(normalized.String(), "\n")) +} + func flattenResolvedProfileID(pool kernel.BrowserPool, diags *diag.Diagnostics) types.String { raw := pool.JSON.ProfileID.Raw() if raw != "" { diff --git a/internal/datasources/browserpool/datasource_test.go b/internal/datasources/browserpool/datasource_test.go index bdd714f..f1ac064 100644 --- a/internal/datasources/browserpool/datasource_test.go +++ b/internal/datasources/browserpool/datasource_test.go @@ -48,7 +48,7 @@ func TestDataSourceMetadataSchemaAndConfigure(t *testing.T) { var schema datasource.SchemaResponse ds.Schema(context.Background(), datasource.SchemaRequest{}, &schema) - for _, name := range []string{"id", "name", "project_id", "size", "profile_id", "extension_ids", "proxy_id", "headless", "kiosk_mode", "stealth", "start_url", "timeout_seconds", "fill_rate_per_minute", "viewport"} { + for _, name := range []string{"id", "name", "project_id", "size", "profile_id", "extension_ids", "proxy_id", "headless", "kiosk_mode", "stealth", "start_url", "timeout_seconds", "fill_rate_per_minute", "viewport", "chrome_policy"} { if _, ok := schema.Schema.Attributes[name]; !ok { t.Fatalf("schema missing %s", name) } @@ -213,7 +213,8 @@ func TestReadSetsTerraformState(t *testing.T) { "start_url":"chrome://newtab", "timeout_seconds":10, "fill_rate_per_minute":0, - "viewport":{"width":1280,"height":800,"refresh_rate":60} + "viewport":{"width":1280,"height":800,"refresh_rate":60}, + "chrome_policy":{"RestoreOnStartup":4,"HomepageLocation":"https://example.com?x=1&y=2"} } }`), nil }, @@ -254,6 +255,51 @@ func TestReadSetsTerraformState(t *testing.T) { t.Fatalf("warmup state = %#v", state) } assertBrowserPoolViewport(t, state.Viewport, 1280, 800, types.Int64Value(60)) + if state.ChromePolicy.ValueString() != `{"HomepageLocation":"https://example.com?x=1&y=2","RestoreOnStartup":4}` { + t.Fatalf("chrome_policy = %q", state.ChromePolicy.ValueString()) + } +} + +func TestFlattenBrowserPoolChromePolicyNormalization(t *testing.T) { + t.Parallel() + + omitted, diags := flattenBrowserPool(*browserPoolFromJSON(`{"id":"pool-1","extension_ids":[],"browser_pool_config":{"size":1}}`)) + if diags.HasError() { + t.Fatalf("unexpected omitted policy diagnostics: %v", diags) + } + if !omitted.ChromePolicy.IsNull() { + t.Fatalf("omitted chrome_policy = %v, want null", omitted.ChromePolicy) + } + explicitNull, diags := flattenBrowserPool(*browserPoolFromJSON(`{"id":"pool-1","extension_ids":[],"browser_pool_config":{"size":1,"chrome_policy":null}}`)) + if diags.HasError() || !explicitNull.ChromePolicy.IsNull() { + t.Fatalf("explicit-null chrome_policy = %v, diagnostics = %v", explicitNull.ChromePolicy, diags) + } + + state, diags := flattenBrowserPool(*browserPoolFromJSON(`{"id":"pool-1","extension_ids":[],"browser_pool_config":{"size":1,"chrome_policy":{"Tag":"","Number":1.0,"Nested":{"enabled":true}}}}`)) + if diags.HasError() { + t.Fatalf("unexpected policy diagnostics: %v", diags) + } + want := `{"Nested":{"enabled":true},"Number":1,"Tag":""}` + if state.ChromePolicy.ValueString() != want { + t.Fatalf("chrome_policy = %q, want %q", state.ChromePolicy.ValueString(), want) + } +} + +func TestFlattenBrowserPoolRejectsInvalidChromePolicy(t *testing.T) { + t.Parallel() + + for name, body := range map[string]string{ + "array": `{"id":"pool-1","extension_ids":[],"browser_pool_config":{"size":1,"chrome_policy":[]}}`, + "string": `{"id":"pool-1","extension_ids":[],"browser_pool_config":{"size":1,"chrome_policy":"policy"}}`, + } { + t.Run(name, func(t *testing.T) { + t.Parallel() + _, diags := flattenBrowserPool(*browserPoolFromJSON(body)) + if !diags.HasError() { + t.Fatal("expected diagnostics") + } + }) + } } func TestFlattenBrowserPoolViewportOptionalFields(t *testing.T) { @@ -616,6 +662,7 @@ func browserPoolConfigValue(id, name, projectID tftypes.Value) tftypes.Value { "timeout_seconds": tftypes.Number, "fill_rate_per_minute": tftypes.Number, "viewport": viewportType, + "chrome_policy": tftypes.String, }}, map[string]tftypes.Value{ "id": id, @@ -632,6 +679,7 @@ func browserPoolConfigValue(id, name, projectID tftypes.Value) tftypes.Value { "timeout_seconds": tftypes.NewValue(tftypes.Number, nil), "fill_rate_per_minute": tftypes.NewValue(tftypes.Number, nil), "viewport": tftypes.NewValue(viewportType, nil), + "chrome_policy": tftypes.NewValue(tftypes.String, nil), }, ) } From 42dcc73825415754df25d5cbc83909bc0d224b8f Mon Sep 17 00:00:00 2001 From: Ilyaas Kapadia <86218345+IlyaasK@users.noreply.github.com> Date: Mon, 3 Aug 2026 10:23:46 -0400 Subject: [PATCH 2/2] test Chrome policy schema mutations --- internal/datasources/browserpool/datasource_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/datasources/browserpool/datasource_test.go b/internal/datasources/browserpool/datasource_test.go index f1ac064..bd1c8a9 100644 --- a/internal/datasources/browserpool/datasource_test.go +++ b/internal/datasources/browserpool/datasource_test.go @@ -99,6 +99,7 @@ func TestDataSourceSchemaSemantics(t *testing.T) { for _, name := range []string{"width", "height", "refresh_rate"} { assertAttributeMapMode(t, viewport.Attributes, name, false, true) } + assertAttributeMode(t, resp.Schema, "chrome_policy", false, true) projectID := resp.Schema.Attributes["project_id"].(dschema.StringAttribute) if !validateProjectID(projectID.Validators, "").HasError() {