Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions docs/data-sources/browser_pool.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
33 changes: 33 additions & 0 deletions internal/datasources/browserpool/datasource.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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.",
},
},
}
}
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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()
Comment thread
IlyaasK marked this conversation as resolved.
}

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 != "" {
Expand Down
53 changes: 51 additions & 2 deletions internal/datasources/browserpool/datasource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -213,7 +214,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
},
Expand Down Expand Up @@ -254,6 +256,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":"<b>","Number":1.0,"Nested":{"enabled":true}}}}`))
if diags.HasError() {
t.Fatalf("unexpected policy diagnostics: %v", diags)
}
want := `{"Nested":{"enabled":true},"Number":1,"Tag":"<b>"}`
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) {
Expand Down Expand Up @@ -616,6 +663,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,
Expand All @@ -632,6 +680,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),
},
)
}
Expand Down