Skip to content

Commit 251d7f3

Browse files
sethfickenicolejmsnithyatsu
authored
fix: include operator-configured parameter values in recipe metadata (#11680)
## Description Fixes: #9454 `rad recipe show` does not display the parameter values that operators configure on an environment recipe. The server returns only the template schema from the registry (type, defaultValue, min, max) and silently drops the configured values before sending the response to the CLI. ## Changes **Server (`pkg/corerp/frontend/controller/environments/getrecipemetadata.go`)** After retrieving the template schema from the registry, overlay `recipeProperties.Parameters` (the operator-configured values) onto the schema under a `"value"` key. Parameters that are configured but absent from the template schema are skipped — the response remains a faithful view of the recipe template. **CLI (`pkg/cli/cmd/recipe/`)** - Add `Value` field to `RecipeParameter` type (`types.go`) - Handle the `"value"` key in the `rad recipe show` parameter loop (`show/show.go`) - Add a `VALUE` column to the parameters table between `DEFAULT VALUE` and `MIN` (`common/objectformats.go`) ## Type of change - [x] Bug fix ## How Has This Been Tested? - Added `TestGetRecipeMetadataFromRegistry_ConfiguredParameterValuesAppearedInResponse` to `getrecipemetadata_test.go` — a regression test that verifies configured values are merged into the schema response. - Added `"Show recipe with operator-configured parameter value - Success"` test case to `show_test.go` — verifies the CLI populates the `Value` field when the server returns a `"value"` key. - Updated existing test cases in `show_test.go` and `objectformats_test.go` to reflect the new `VALUE` column and the `Value: "-"` default initialization. - `golangci-lint run` on modified packages: 0 issues. ## Checklist - [x] I have followed the [contributing guidelines](https://github.com/radius-project/radius/blob/main/CONTRIBUTING.md) - [x] I have updated or added tests - [x] I have updated documentation as needed Signed-off-by: Seth Ficke <19766129+sethficke@users.noreply.github.com> Co-authored-by: Nicole James <101607760+nicolejms@users.noreply.github.com> Co-authored-by: Nithya Subramanian <98416062+nithyatsu@users.noreply.github.com>
1 parent 2b66ff8 commit 251d7f3

7 files changed

Lines changed: 153 additions & 1 deletion

File tree

‎pkg/cli/cmd/recipe/common/objectformats.go‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ func RecipeParametersFormat() output.FormatterOptions {
6666
Heading: "DEFAULT VALUE",
6767
JSONPath: "{ .DefaultValue }",
6868
},
69+
{
70+
Heading: "VALUE",
71+
JSONPath: "{ .Value }",
72+
},
6973
{
7074
Heading: "MIN",
7175
JSONPath: "{ .MinValue }",

‎pkg/cli/cmd/recipe/common/objectformats_test.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,6 @@ func Test_RecipeParametersFormat(t *testing.T) {
5555
err := output.Write(output.FormatTable, obj, buffer, RecipeParametersFormat())
5656
require.NoError(t, err)
5757

58-
expected := "PARAMETER TYPE DEFAULT VALUE MIN MAX\ntest test-type 1 4 3\n"
58+
expected := "PARAMETER TYPE DEFAULT VALUE VALUE MIN MAX\ntest test-type 1 null 4 3\n"
5959
require.Equal(t, expected, buffer.String())
6060
}

‎pkg/cli/cmd/recipe/show/show.go‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ func (r *Runner) Run(ctx context.Context) error {
186186
paramItem := types.RecipeParameter{
187187
Name: parameter,
188188
DefaultValue: "-",
189+
Value: "-",
189190
MaxValue: "-",
190191
MinValue: "-",
191192
}
@@ -196,6 +197,8 @@ func (r *Runner) Run(ctx context.Context) error {
196197
paramItem.Type = paramDetailValue.(string)
197198
case "defaultValue":
198199
paramItem.DefaultValue = paramDetailValue
200+
case "value":
201+
paramItem.Value = paramDetailValue
199202
case "maxValue":
200203
paramItem.MaxValue = fmt.Sprintf("%v", paramDetailValue.(float64))
201204
case "minValue":

‎pkg/cli/cmd/recipe/show/show_test.go‎

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,13 +119,15 @@ func Test_Run(t *testing.T) {
119119
{
120120
Name: "throughput",
121121
Type: "float64",
122+
Value: "-",
122123
MaxValue: "800",
123124
MinValue: "-",
124125
DefaultValue: "-",
125126
},
126127
{
127128
Name: "sku",
128129
Type: "string",
130+
Value: "-",
129131
MaxValue: "-",
130132
MinValue: "-",
131133
DefaultValue: "-",
@@ -196,13 +198,15 @@ func Test_Run(t *testing.T) {
196198
{
197199
Name: "throughput",
198200
Type: "float64",
201+
Value: "-",
199202
MaxValue: "800",
200203
MinValue: "-",
201204
DefaultValue: "-",
202205
},
203206
{
204207
Name: "sku",
205208
Type: "string",
209+
Value: "-",
206210
MaxValue: "-",
207211
MinValue: "-",
208212
DefaultValue: "-",
@@ -246,6 +250,75 @@ func Test_Run(t *testing.T) {
246250
require.Equal(t, expected, outputSink.Writes)
247251
})
248252

253+
t.Run("Show recipe with operator-configured parameter value - Success", func(t *testing.T) {
254+
// Regression test for https://github.com/radius-project/radius/issues/9454
255+
// The server merges operator-configured values into the parameters map.
256+
// The CLI must surface them in the VALUE column.
257+
ctrl := gomock.NewController(t)
258+
envRecipe := v20231001preview.RecipeGetMetadataResponse{
259+
TemplateKind: to.Ptr(recipes.TemplateKindBicep),
260+
TemplatePath: new("ghcr.io/testpublicrecipe/bicep/modules/redis:v1"),
261+
Parameters: map[string]any{
262+
"location": map[string]any{
263+
"type": "string",
264+
"value": "eastus",
265+
},
266+
},
267+
}
268+
recipe := types.EnvironmentRecipe{
269+
Name: "redis-prod",
270+
ResourceType: datastoresrp.RedisCachesResourceType,
271+
TemplateKind: recipes.TemplateKindBicep,
272+
TemplatePath: "ghcr.io/testpublicrecipe/bicep/modules/redis:v1",
273+
}
274+
recipeParams := []types.RecipeParameter{
275+
{
276+
Name: "location",
277+
Type: "string",
278+
Value: "eastus",
279+
MaxValue: "-",
280+
MinValue: "-",
281+
DefaultValue: "-",
282+
},
283+
}
284+
285+
appManagementClient := clients.NewMockApplicationsManagementClient(ctrl)
286+
appManagementClient.EXPECT().
287+
GetRecipeMetadata(gomock.Any(), gomock.Any(), gomock.Any()).
288+
Return(envRecipe, nil).Times(1)
289+
290+
outputSink := &output.MockOutput{}
291+
292+
runner := &Runner{
293+
ConnectionFactory: &connections.MockFactory{ApplicationsManagementClient: appManagementClient},
294+
Output: outputSink,
295+
Workspace: &workspaces.Workspace{},
296+
Format: "table",
297+
RecipeName: "redis-prod",
298+
ResourceType: datastoresrp.RedisCachesResourceType,
299+
}
300+
301+
err := runner.Run(context.Background())
302+
require.NoError(t, err)
303+
304+
expected := []any{
305+
output.FormattedOutput{
306+
Format: "table",
307+
Obj: recipe,
308+
Options: common.RecipeFormat(),
309+
},
310+
output.LogOutput{
311+
Format: "",
312+
},
313+
output.FormattedOutput{
314+
Format: "table",
315+
Obj: recipeParams,
316+
Options: common.RecipeParametersFormat(),
317+
},
318+
}
319+
require.Equal(t, expected, outputSink.Writes)
320+
})
321+
249322
t.Run("Show terraformn recipe details - Success", func(t *testing.T) {
250323
ctrl := gomock.NewController(t)
251324
envRecipe := v20231001preview.RecipeGetMetadataResponse{
@@ -273,13 +346,15 @@ func Test_Run(t *testing.T) {
273346
{
274347
Name: "throughput",
275348
Type: "float64",
349+
Value: "-",
276350
MaxValue: "800",
277351
MinValue: "-",
278352
DefaultValue: "-",
279353
},
280354
{
281355
Name: "sku",
282356
Type: "string",
357+
Value: "-",
283358
MaxValue: "-",
284359
MinValue: "-",
285360
DefaultValue: "-",

‎pkg/cli/cmd/recipe/types.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type RecipeParameter struct {
2929
Name string `json:"name,omitempty"`
3030
DefaultValue any `json:"defaultValue,omitempty"`
3131
Type string `json:"type,omitempty"`
32+
Value any `json:"value,omitempty"`
3233
MaxValue string `json:"maxValue,omitempty"`
3334
MinValue string `json:"minValue,omitempty"`
3435
}

‎pkg/corerp/frontend/controller/environments/getrecipemetadata.go‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,19 @@ func (r *GetRecipeMetadata) GetRecipeMetadataFromRegistry(ctx context.Context, r
132132
return recipeParameters, err
133133
}
134134

135+
// Overlay operator-configured parameter values so they appear in the response.
136+
// The engine returns the template schema (type, defaultValue, etc.) but not the
137+
// values an operator has set on the environment. We merge them here so that
138+
// `rad recipe show` can display them.
139+
for paramName, configuredValue := range recipeProperties.Parameters {
140+
schemaEntry, ok := recipeParameters[paramName].(map[string]any)
141+
if !ok {
142+
// Parameter is configured but absent from the template schema — skip it.
143+
continue
144+
}
145+
schemaEntry["value"] = configuredValue
146+
}
147+
135148
return recipeParameters, nil
136149
}
137150

‎pkg/corerp/frontend/controller/environments/getrecipemetadata_test.go‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/radius-project/radius/pkg/armrpc/rpctest"
3030
"github.com/radius-project/radius/pkg/components/database"
3131
"github.com/radius-project/radius/pkg/corerp/api/v20231001preview"
32+
"github.com/radius-project/radius/pkg/corerp/datamodel"
3233
"github.com/radius-project/radius/pkg/recipes"
3334
"github.com/radius-project/radius/pkg/recipes/engine"
3435
"github.com/radius-project/radius/test/testutil"
@@ -274,6 +275,61 @@ func TestGetRecipeMetadataRun_20231001Preview(t *testing.T) {
274275
})
275276
}
276277

278+
func TestGetRecipeMetadataFromRegistry_ConfiguredParameterValuesAppearedInResponse(t *testing.T) {
279+
// Regression test for https://github.com/radius-project/radius/issues/9454
280+
// Operator-configured parameter values stored in the environment must appear in
281+
// the GetRecipeMetadata response so that `rad recipe show` can display them.
282+
mctrl := gomock.NewController(t)
283+
defer mctrl.Finish()
284+
mEngine := engine.NewMockEngine(mctrl)
285+
286+
recipeProperties := datamodel.EnvironmentRecipeProperties{
287+
TemplateKind: "bicep",
288+
TemplatePath: "ghcr.io/example/recipes/redis:1.0",
289+
Parameters: map[string]any{
290+
"location": "eastus",
291+
},
292+
}
293+
recipeDataModel := &datamodel.Recipe{
294+
Name: "redis-prod",
295+
ResourceType: "Applications.Datastores/redisCaches",
296+
}
297+
298+
// Engine returns the template schema from the registry — no "value" keys here,
299+
// only parameter definitions (type, defaultValue, etc.).
300+
recipeData := map[string]any{
301+
"parameters": map[string]any{
302+
"location": map[string]any{
303+
"type": "string",
304+
},
305+
},
306+
}
307+
mEngine.EXPECT().
308+
GetRecipeMetadata(gomock.Any(), gomock.Any()).
309+
Return(recipeData, nil)
310+
311+
databaseClient := database.NewMockClient(mctrl)
312+
opts := ctrl.Options{DatabaseClient: databaseClient}
313+
ctl, err := NewGetRecipeMetadata(opts, mEngine)
314+
require.NoError(t, err)
315+
316+
controller := ctl.(*GetRecipeMetadata)
317+
result, err := controller.GetRecipeMetadataFromRegistry(
318+
context.Background(),
319+
recipeProperties,
320+
recipeDataModel,
321+
"/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg/providers/applications.core/environments/env0",
322+
)
323+
require.NoError(t, err)
324+
325+
// The operator configured location="eastus". That value must appear in the response
326+
// so the CLI can display it. Without the fix, this assertion fails because only
327+
// {"type": "string"} is returned — the "value" key is absent.
328+
locationParam, ok := result["location"].(map[string]any)
329+
require.True(t, ok, "location parameter should be present in response")
330+
require.Equal(t, "eastus", locationParam["value"], "operator-configured value should appear in response")
331+
}
332+
277333
func TestParseAndFormatRecipeParams(t *testing.T) {
278334
t.Run("parse and format recipe parameters with context", func(t *testing.T) {
279335
recipeData := map[string]any{}

0 commit comments

Comments
 (0)