diff --git a/content/blog/esc-schema-validation-fn-validate/index.md b/content/blog/esc-schema-validation-fn-validate/index.md new file mode 100644 index 000000000000..858b48d8a8b2 --- /dev/null +++ b/content/blog/esc-schema-validation-fn-validate/index.md @@ -0,0 +1,121 @@ +--- +title: "Schema Validation Comes to Pulumi ESC with fn::validate" +date: 2026-02-09 +draft: false +meta_desc: "Validate configuration values against JSON Schema with fn::validate in Pulumi ESC." +meta_image: meta.png +authors: + - pablo-terradillos + - claire-gaestel +tags: + - esc + - features +schema_type: auto +social: + twitter: + linkedin: +aliases: + - /blog/esc-schema-validation-fn-conform/ +--- + +Pulumi ESC environments can now validate configuration values against JSON Schema with the new `fn::validate` built-in function. Invalid configurations are caught immediately when you save, preventing misconfigurations from reaching your deployments. + + +Configuration errors are often discovered too late during deployment or, worse, in production. With `fn::validate`, you define validation rules directly in your environment, and ESC enforces them at save time. If a value doesn't match its schema, the environment cannot be saved until the issue is resolved. + +## How it works + +The `fn::validate` function takes a JSON Schema and a value. If the value conforms to the schema, it passes through unchanged. If not, ESC raises a validation error. + +```yaml +values: + port: + fn::validate: + schema: { type: number, minimum: 1, maximum: 65535 } + value: 8080 +``` + +This validates that `port` is a number between 1 and 65535. The evaluated result is simply `8080`. + +## Validating objects with required fields + +For complex configurations, you can enforce structure and required fields: + +```yaml +values: + database: + fn::validate: + schema: + type: object + properties: + host: { type: string } + port: { type: number } + name: { type: string } + required: [host, port, name] + value: + host: "db.example.com" + port: 5432 + name: "myapp" +``` + +If any required field is missing or has the wrong type, the environment cannot be saved. + +## Reusing schemas across environments + +Define schemas once and reference them across multiple environments. Using the [`environments` built-in property](/docs/esc/environments/syntax/builtin-properties/environments/) keeps the schema out of your environment's output: + +**Schema environment (myorg/schemas)** + +```yaml +values: + database-schema: + type: object + properties: + host: { type: string } + port: { type: number } + required: [host, port] +``` + +**Environment using the schema** + +```yaml +values: + database: + fn::validate: + schema: ${environments.myorg.schemas.database-schema} + value: + host: "prod-db.example.com" + port: 5432 +``` + +This pattern ensures consistent validation rules across teams and projects. + +## What happens when validation fails + +When a value doesn't conform to its schema, ESC returns a clear error message: + +```yaml +values: + port: + fn::validate: + schema: { type: string } + value: 8080 +``` + +This raises: `expected string, got number`. The environment cannot be saved until you fix the value or update the schema. + +## When to use schema validation + +Enable `fn::validate` for: + +- Values with specific type requirements (numbers, strings, arrays) +- Objects that must have certain fields present +- Numbers that must fall within a valid range +- Configurations shared across multiple environments +- Any value where catching errors early prevents downstream issues + +## Getting started + +The `fn::validate` function is available now in all Pulumi ESC environments. Add schema validation to your existing environments or use it when creating new ones. + +For more information, see the [fn::validate documentation](/docs/esc/environments/syntax/builtin-functions/fn-validate/). diff --git a/content/blog/esc-schema-validation-fn-validate/meta.png b/content/blog/esc-schema-validation-fn-validate/meta.png new file mode 100644 index 000000000000..e3850cf9b494 Binary files /dev/null and b/content/blog/esc-schema-validation-fn-validate/meta.png differ diff --git a/content/docs/esc/environments/syntax/builtin-functions/_index.md b/content/docs/esc/environments/syntax/builtin-functions/_index.md index 05f8dbf4dab5..aa1087ae272a 100644 --- a/content/docs/esc/environments/syntax/builtin-functions/_index.md +++ b/content/docs/esc/environments/syntax/builtin-functions/_index.md @@ -19,14 +19,15 @@ All function invocations take the form of an object with a single key that names ## Functions -- [`fn::fromJSON`](/docs/esc/environments/syntax/builtin-functions/fn-from-json) +- [`fn::concat`](/docs/esc/environments/syntax/builtin-functions/fn-concat) +- [`fn::validate`](/docs/esc/environments/syntax/builtin-functions/fn-validate) - [`fn::fromBase64`](/docs/esc/environments/syntax/builtin-functions/fn-from-base64) +- [`fn::fromJSON`](/docs/esc/environments/syntax/builtin-functions/fn-from-json) - [`fn::join`](/docs/esc/environments/syntax/builtin-functions/fn-join) -- [`fn::split`](/docs/esc/environments/syntax/builtin-functions/fn-split) -- [`fn::concat`](/docs/esc/environments/syntax/builtin-functions/fn-concat) - [`fn::open`](/docs/esc/environments/syntax/builtin-functions/fn-open) - [`fn::rotate`](/docs/esc/environments/syntax/builtin-functions/fn-rotate) - [`fn::secret`](/docs/esc/environments/syntax/builtin-functions/fn-secret) +- [`fn::split`](/docs/esc/environments/syntax/builtin-functions/fn-split) - [`fn::toBase64`](/docs/esc/environments/syntax/builtin-functions/fn-to-base64) - [`fn::toJSON`](/docs/esc/environments/syntax/builtin-functions/fn-to-json) - [`fn::toString`](/docs/esc/environments/syntax/builtin-functions/fn-to-string) diff --git a/content/docs/esc/environments/syntax/builtin-functions/fn-validate.md b/content/docs/esc/environments/syntax/builtin-functions/fn-validate.md new file mode 100644 index 000000000000..7f538bdf4930 --- /dev/null +++ b/content/docs/esc/environments/syntax/builtin-functions/fn-validate.md @@ -0,0 +1,233 @@ +--- +title: fn::validate +title_tag: fn::validate +h1: fn::validate +meta_desc: Pulumi ESC allows you to compose and manage hierarchical collections of configuration and secrets and consume them in various ways. +aliases: + - /docs/esc/environments/syntax/builtin-functions/fn-conform/ +menu: + esc: + parent: esc-syntax-builtin-functions + identifier: esc-syntax-fn-validate + weight: 2 +--- + +The `fn::validate` built-in function validates a value against a JSON Schema. If the value does not conform to the schema, a validation error is raised and the environment cannot be saved until the issues are resolved. + +## Declaration + +```yaml +fn::validate: + schema: json-schema + value: value-to-validate +``` + +### Parameters + +| Property | Type | Description | +|----------|--------|------------------------------------------------------| +| `schema` | object | A JSON Schema definition to validate the value against. +| `value` | any | The value to validate. + +### Returns + +If validation passes, the original `value` is returned. If validation fails, an error is raised and the environment cannot be saved. + +## Example + +### Basic type validation + +#### Definition + +```yaml +values: + valid-string: + fn::validate: + schema: { type: string } + value: "hello" +``` + +#### Evaluated result + +```json +{ + "valid-string": "hello" +} +``` + +### Number constraints + +#### Definition + +```yaml +values: + validated-number: + fn::validate: + schema: { type: number, minimum: 0 } + value: 42 +``` + +#### Evaluated result + +```json +{ + "validated-number": 42 +} +``` + +### Object with required fields + +#### Definition + +```yaml +values: + user: + fn::validate: + schema: + type: object + properties: + name: { type: string } + email: { type: string } + required: [name, email] + value: + name: "Alice" + email: "alice@example.com" +``` + +#### Evaluated result + +```json +{ + "user": { + "name": "Alice", + "email": "alice@example.com" + } +} +``` + +### Dynamic schema from reference + +You can define schemas as values and reference them dynamically: + +#### Definition + +```yaml +values: + my-schema: + type: string + minLength: 1 + validated: + fn::validate: + schema: ${my-schema} + value: "hello" +``` + +#### Evaluated result + +```json +{ + "my-schema": { + "type": "string", + "minLength": 1 + }, + "validated": "hello" +} +``` + +### Array validation + +#### Definition + +```yaml +values: + tags: + fn::validate: + schema: + type: array + items: { type: string } + value: ["production", "us-east-1"] +``` + +#### Evaluated result + +```json +{ + "tags": ["production", "us-east-1"] +} +``` + +### Reusing schemas from other environments + +You can define schemas in a separate environment and reference them for reuse across multiple environments. This allows you to centralize schema definitions and ensure consistent validation. + +Using the [`environments` built-in property](/docs/esc/environments/syntax/builtin-properties/environments/) to reference the schema is the recommended approach because it doesn't merge the schema into your environment's output. + +#### Schema environment (myproj/schemas) + +```yaml +values: + user-schema: + type: object + properties: + name: { type: string } + email: { type: string } + required: [name, email] +``` + +#### Environment using the schema reference + +```yaml +values: + user: + fn::validate: + schema: ${environments.myproj.schemas.user-schema} + value: + name: "Alice" + email: "alice@example.com" +``` + +#### Evaluated result + +```json +{ + "user": { + "name": "Alice", + "email": "alice@example.com" + } +} +``` + +This pattern is useful for enforcing consistent validation rules across teams and projects. + +## Validation errors + +When a value does not conform to its schema, `fn::validate` raises an error that prevents the environment from being saved. This ensures configuration issues are caught before the environment is used. + +### Example: type mismatch + +```yaml +values: + type-error: + fn::validate: + schema: { type: string } + value: 42 +``` + +This raises an error: `expected string, got number`. The environment cannot be saved until the value conforms to the schema. + +### Example: missing required field + +```yaml +values: + missing-required: + fn::validate: + schema: + type: object + properties: + name: { type: string } + required: [name] + value: + other: "value" +``` + +This raises an error indicating that the required field `name` is missing. The environment cannot be saved until the required field is provided.