Materialize schema property defaults before the resource is saved - #12563
Open
AzureMike wants to merge 1 commit into
Open
Materialize schema property defaults before the resource is saved#12563AzureMike wants to merge 1 commit into
AzureMike wants to merge 1 commit into
Conversation
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #12563 +/- ##
========================================
Coverage 53.99% 53.99%
========================================
Files 765 767 +2
Lines 50708 50813 +105
========================================
+ Hits 27379 27439 +60
- Misses 20758 20794 +36
- Partials 2571 2580 +9 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Resource type schemas could declare a default for a property, but nothing ever applied it. ValidateResourceAgainstSchema only validates, so declared defaults in objectStorage, mongoDatabases, kafka, rabbitMQ and models had no effect, and recipes reading an unset optional property through context.resource.properties.* found nothing to resolve. Add schema.ApplyDefaults, which fills absent properties from the default declared in the schema, and run it as an UpdateFilter on the dynamic resource PUT path ahead of the encryption filter so a defaulted sensitive field is still encrypted. Explicit values are never overwritten. Read-only properties are skipped because they are recipe outputs, and an absent object is not created just to hold defaults. Required properties are never defaulted, so omitting one still fails validation rather than being quietly satisfied. The rule blocks defaulting only, not recursion: a required object the author did supply is still descended into, because its own optional properties may declare defaults of their own. A body carrying no properties at all is defaulted too. The filled map is attached only when something was actually applied, so a genuinely empty resource serializes exactly as it did before. Defaults are reapplied on every write rather than carried forward from the previous version of the resource, which is what full-replace PUT semantics ask for. The consequence is that changing a declared default changes what an existing resource gets on its next deployment, so a default is part of a resource type's API contract and should change only alongside the API version. The manifests under deploy/manifest/built-in-providers are verbatim copies of the resource-types-contrib version pinned in go.mod, and CI re-runs the copy and fails on any diff. Built-in types therefore pick up the new defaults only after the contrib change merges and go.mod is bumped. Until then this change is inert for them rather than broken. Known follow-up: this filter and the encryption filter each fetch the same schema from UCP, so a PUT now makes two identical round trips. Sharing one fetch needs either a request-scoped cache seeded in the shared controller or the two filters merged, both of which reach past this change. With defaults declared, this resolves #12532 on its own: an unset size resolves to Burstable/Standard_B1ms/Balanced_B0, and plain non-ternary paths such as database stop leaking too. Signed-off-by: Mike Azure <127820851+AzureMike@users.noreply.github.com>
AzureMike
force-pushed
the
feat/materialize-schema-defaults
branch
from
July 28, 2026 17:39
ec7598b to
5c69ad6
Compare
Radius functional test overviewClick here to see the test run details
Test Status⌛ Building Radius and pushing container images for functional tests... |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A resource type schema can declare a
defaultfor a property, and Radius never applied it.schema.ApplyDefaultsfills absent properties from the default declared in the schema, and a newUpdateFilteron the dynamic resource PUT path runs it before the resource is saved.Explicit values are never overwritten, and required properties are never defaulted, so leaving one out still fails validation instead of being quietly satisfied.
Reason for change
Fixes #12532
Deploying a resource without setting an optional property puts a literal template expression into the ARM request. The Azure recipe pack writes parameters as expressions over the resource:
Leave
sizeunset and there's nothing to substitute, so the raw{{...}}string is handed to ARM as the SKU name and rejected againstallowedValues.The cause sits a level below the recipe. Validation checked a resource against its schema without filling anything in, so every
default:key in every resource type has been inert — including the onesobjectStorage,mongoDatabases,models,kafkaandrabbitMQalready declare.With the defaults from radius-project/resource-types-contrib#260 declared, the existing recipe expressions resolve on their own, and neither the resolver nor the recipe pack needs to change. That covers ten optional properties across the Azure pack, not just
size:redisCachessizeBalanced_B0postgreSqlDatabasessizeStandard_B1ms/BurstablemySqlDatabasesversion8.0.21modelsmodelgpt-5-minimongoDatabasesdatabasemongo_dbmySqlDatabasesdatabasemysql_dbpostgreSqlDatabasesdatabasepostgres_dbrabbitMQqueuejobskafkatopiceventsobjectStoragecontainerNamedataSix of those defaults already existed, so half the fix is Radius finally reading what contrib already wrote down.
Two things reviewers should know. The filter sits ahead of the encryption filter, so a defaulted sensitive field is still encrypted. And defaults are reapplied on every write rather than carried forward from the previous version of the resource, which is what full-replace PUT semantics ask for — the consequence is that changing a declared default later changes what an existing resource gets on its next deployment, so a default is part of a resource type's API contract.
How to test
go test ./pkg/schema/... ./pkg/dynamicrp/...covers the new code directly.pkg/schema/defaults_test.goasserts that explicit values survive, required properties are never defaulted, read-only properties are skipped, absent nested objects aren't created, and map and slice defaults are deep copied so one resource can't mutate what the next one sees.End to end, all ten optional properties above were run through
ApplyDefaultsand the parameter resolver against the real contrib schemas and the realrecipepack/azure/aks-recipepack.bicepexpressions. Every one emits a literal{{...}}without defaults and resolves to the value in the table with them, on the unmodified resolver and the unmodified recipe expressions.go vetandgofmtare clean, andpkg/schema/...,pkg/dynamicrp/...,pkg/recipes/...,pkg/cli/manifest/...andpkg/ucp/integrationtests/resourceproviders/...all pass, including after rebasing onto the kin-openapi 0.144.0 bump in #12561.Built-in resource types pick this up once radius-project/resource-types-contrib#260 merges and
make update-resource-typesbumps the pin ingo.mod, since the manifests underdeploy/manifest/built-in-providersare copies CI regenerates from it.File change summary
pkg/schema/defaults.goApplyDefaultswalks a schema and fills absent properties from their declareddefault, returning the number applied.pkg/schema/defaults_test.gopkg/dynamicrp/frontend/defaultsfilter.goUpdateFilterthat fetches the resource type schema and applies defaults before the resource is saved, attaching the filled map only when something was applied.pkg/dynamicrp/frontend/routes.go