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
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ func (c *CreateOrUpdateResource[P, T]) executeRecipeIfNeeded(ctx context.Context
if err != nil {
return nil, fmt.Errorf("failed to get connected resource IDs: %w", err)
}
connectedResourcesProperties := make(map[string]map[string]any)
connectedResourcesProperties := make(map[string]recipes.ConnectedResource)

// If there are connected resources, we need to fetch their properties and add them to the recipe context.
for connName, connectedResourceID := range connectionsAndSourceIDs {
Expand All @@ -188,12 +188,17 @@ func (c *CreateOrUpdateResource[P, T]) executeRecipeIfNeeded(ctx context.Context
return nil, fmt.Errorf("failed to get connected resource %s: %w", connectedResourceID, err)
}

connectedResourceProperties, err := resourceutil.GetPropertiesFromResource(connectedResource.Data)
connectedResourceMetadata, err := resourceutil.GetAllPropertiesFromResource(connectedResource.Data, connectedResourceID)
if err != nil {
return nil, fmt.Errorf("failed to get properties from connected resource %s: %w", connectedResourceID, err)
return nil, fmt.Errorf("failed to get metadata from connected resource %s: %w", connectedResourceID, err)
}

connectedResourcesProperties[connName] = connectedResourceProperties
connectedResourcesProperties[connName] = recipes.ConnectedResource{
ID: connectedResourceMetadata.ID,
Name: connectedResourceMetadata.Name,
Type: connectedResourceMetadata.Type,
Properties: connectedResourceMetadata.Properties,
}
}

metadata := recipes.ResourceMetadata{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ func TestCreateOrUpdateResource_Run(t *testing.T) {
"p1": "v1",
},
Properties: properties,
ConnectedResourcesProperties: map[string]map[string]any{},
ConnectedResourcesProperties: map[string]recipes.ConnectedResource{},
}

prevState := []string{
Expand Down Expand Up @@ -470,3 +470,34 @@ func TestCreateOrUpdateResource_Run(t *testing.T) {
})
}
}

func TestGetAllPropertiesFromResource_Integration(t *testing.T) {
// Simple integration test for resourceutil.GetAllPropertiesFromResource
connectedResourceID := "/planes/radius/local/resourceGroups/radius-test-rg/providers/Applications.Datastores/sqlDatabases/test-db"

// Create a test resource with properties
testResource := struct {
Properties map[string]any `json:"properties"`
}{
Properties: map[string]any{
"host": "localhost",
"port": 5432,
"database": "testdb",
},
}

// Test the GetAllPropertiesFromResource function
metadata, err := resourceutil.GetAllPropertiesFromResource(testResource, connectedResourceID)

// Assertions
require.NoError(t, err)
require.NotNil(t, metadata)
require.Equal(t, connectedResourceID, metadata.ID)
require.Equal(t, "test-db", metadata.Name)
require.Equal(t, "Applications.Datastores/sqlDatabases", metadata.Type)
require.Equal(t, map[string]any{
"host": "localhost",
"port": float64(5432), // JSON unmarshaling converts to float64
"database": "testdb",
}, metadata.Properties)
}
6 changes: 6 additions & 0 deletions pkg/recipes/configloader/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ func getRecipeDefinitionFromEnvironmentV20250801(ctx context.Context, environmen
ResourceType: resource.Type(),
Parameters: parameters,
TemplatePath: recipeDefinition.RecipeLocation,
PlainHTTP: recipeDefinition.PlainHTTP,
}
return definition, nil
}
Expand Down Expand Up @@ -315,10 +316,15 @@ func fetchRecipeDefinition(ctx context.Context, recipePackIDs []string, armOptio
// Convert recipes map
for recipePackResourceType, definition := range recipePackResource.Properties.Recipes {
if strings.EqualFold(recipePackResourceType, resourceType) {
var plainHTTP bool
if definition.PlainHTTP != nil {
plainHTTP = *definition.PlainHTTP
}
return &recipes.RecipeDefinition{
RecipeKind: string(*definition.RecipeKind),
RecipeLocation: string(*definition.RecipeLocation),
Parameters: definition.Parameters,
PlainHTTP: plainHTTP,
}, nil
}
}
Expand Down
9 changes: 7 additions & 2 deletions pkg/recipes/engine/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,14 @@ func Test_Engine_Execute_Success(t *testing.T) {
Parameters: map[string]any{
"resourceName": "resource1",
},
ConnectedResourcesProperties: map[string]map[string]any{
ConnectedResourcesProperties: map[string]recipes.ConnectedResource{
"database": {
"name": "db",
ID: "/planes/radius/local/resourceGroups/radius-test-rg/providers/Applications.Datastores/sqlDatabases/database",
Name: "database",
Type: "Applications.Datastores/sqlDatabases",
Properties: map[string]any{
"name": "db",
},
},
},
}
Expand Down
79 changes: 79 additions & 0 deletions pkg/recipes/recipecontext/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,3 +304,82 @@ func TestNewContext_failures(t *testing.T) {
})
}
}

func TestNewContext_WithConnectedResources(t *testing.T) {
testMetadata := &recipes.ResourceMetadata{
ResourceID: "/planes/radius/local/resourceGroups/testGroup/providers/applications.datastores/mongodatabases/mongo0",
ApplicationID: "/planes/radius/local/resourceGroups/testGroup/providers/applications.core/applications/testApplication",
EnvironmentID: "/planes/radius/local/resourceGroups/testGroup/providers/applications.core/environments/testEnvironment",
Name: "recipe0",
Properties: map[string]any{
"property1": "value1",
},
ConnectedResourcesProperties: map[string]recipes.ConnectedResource{
"database": {
ID: "/planes/radius/local/resourceGroups/testGroup/providers/applications.datastores/sqldatabases/testdb",
Name: "testdb",
Type: "Applications.Datastores/sqlDatabases",
Properties: map[string]any{
"host": "localhost",
"port": 5432,
"database": "testdb",
},
},
"cache": {
ID: "/planes/radius/local/resourceGroups/testGroup/providers/applications.datastores/redis/testcache",
Name: "testcache",
Type: "Applications.Datastores/redis",
Properties: map[string]any{
"host": "cache-host",
"port": 6379,
},
},
},
}

testConfig := &recipes.Configuration{
Providers: coredm.Providers{
Azure: coredm.ProvidersAzure{
Scope: "/planes/radius/local/resourceGroups/testGroup",
},
AWS: coredm.ProvidersAWS{
Scope: "/planes/aws/aws/accounts/1234567890/regions/us-west-2",
},
},
}

result, err := New(testMetadata, testConfig)
require.NoError(t, err)
require.NotNil(t, result)

// Verify basic resource properties
require.Equal(t, "mongo0", result.Resource.Name)
require.Equal(t, testMetadata.ResourceID, result.Resource.ID)
require.Equal(t, "applications.datastores/mongodatabases", result.Resource.Type)

// Verify connected resources metadata is available (connections are set by recipe engines)
require.NotNil(t, testMetadata.ConnectedResourcesProperties)
require.Len(t, testMetadata.ConnectedResourcesProperties, 2)

// Verify the metadata contains the expected connected resources
dbConn, exists := testMetadata.ConnectedResourcesProperties["database"]
require.True(t, exists)
require.Equal(t, "/planes/radius/local/resourceGroups/testGroup/providers/applications.datastores/sqldatabases/testdb", dbConn.ID)
require.Equal(t, "testdb", dbConn.Name)
require.Equal(t, "Applications.Datastores/sqlDatabases", dbConn.Type)
require.Equal(t, map[string]any{
"host": "localhost",
"port": 5432,
"database": "testdb",
}, dbConn.Properties)

cacheConn, exists := testMetadata.ConnectedResourcesProperties["cache"]
require.True(t, exists)
require.Equal(t, "/planes/radius/local/resourceGroups/testGroup/providers/applications.datastores/redis/testcache", cacheConn.ID)
require.Equal(t, "testcache", cacheConn.Name)
require.Equal(t, "Applications.Datastores/redis", cacheConn.Type)
require.Equal(t, map[string]any{
"host": "cache-host",
"port": 6379,
}, cacheConn.Properties)
}
11 changes: 7 additions & 4 deletions pkg/recipes/recipecontext/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,13 @@ type Resource struct {
Properties map[string]any `json:"properties,omitempty"`

// Connections represent a map of connections to other resources.
// The key is the connection name, and the value is a map of connected resource properties.
// We enrich the recipe context with this, allowing the recipe to access properties of connected resources using the following format:
// context.resource.connections.[connection-name].[connected-resource-property]
Connections map[string]map[string]any `json:"connections,omitempty"`
// The key is the connection name, and the value contains the connected resource's metadata and properties.
// We enrich the recipe context with this, allowing the recipe to access connected resource info using:
// context.resource.connections.[connection-name].properties.[property-name]
// context.resource.connections.[connection-name].id
// context.resource.connections.[connection-name].name
// context.resource.connections.[connection-name].type
Connections map[string]recipes.ConnectedResource `json:"connections,omitempty"`
}

// ResourceInfo represents name and id of the resource
Expand Down
9 changes: 7 additions & 2 deletions pkg/recipes/terraform/execute_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,14 @@ func TestGenerateConfig(t *testing.T) {
TemplatePath: "test/module/source",
},
ResourceRecipe: &recipes.ResourceMetadata{
ConnectedResourcesProperties: map[string]map[string]any{
ConnectedResourcesProperties: map[string]recipes.ConnectedResource{
"conn1": {
"dbName": "db",
ID: "/planes/radius/local/resourceGroups/radius-test-rg/providers/Applications.Datastores/redis/redis",
Name: "redis",
Type: "Applications.Datastores/redis",
Properties: map[string]any{
"dbName": "db",
},
},
},
},
Expand Down
23 changes: 18 additions & 5 deletions pkg/recipes/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ import (
rpv1 "github.com/radius-project/radius/pkg/rp/v1"
)

// ConnectedResource represents a connected resource's metadata and properties
type ConnectedResource struct {
// ID represents the fully qualified resource id
ID string `json:"id"`
// Name represents the resource name
Name string `json:"name"`
// Type represents the resource type
Type string `json:"type"`
// Properties represents the resource properties
Properties map[string]any `json:"properties,omitempty"`
}

// Configuration represents runtime and cloud provider configuration, which is used by the driver while deploying recipes.
type Configuration struct {
// Kubernetes Runtime configuration for the environment.
Expand Down Expand Up @@ -85,11 +97,10 @@ type ResourceMetadata struct {
ResourceID string
// Properties represents the properties of the resource that the recipe is deploying
Properties map[string]any
// ConnectedResourcesProperties represents the properties of the connected resources that the recipe is deploying.
// the key is connection name and the value is a map of properties for the connected resource.
// properties are inturn a map of key/value pairs, where the key is the property name and the value is the property value.
// these properties are passed into the recipe context.
ConnectedResourcesProperties map[string]map[string]any
// ConnectedResourcesProperties represents the connected resources that the recipe is deploying.
// The key is connection name and the value contains the connected resource's metadata and properties.
// These are passed into the recipe context.
ConnectedResourcesProperties map[string]ConnectedResource
// Parameters represents key/value pairs to pass into the recipe template. Overrides any parameters set by the environment.
Parameters map[string]any
}
Expand Down Expand Up @@ -147,6 +158,8 @@ type RecipeDefinition struct {
RecipeLocation string
// Parameters represents parameters to pass to the recipe
Parameters map[string]any
// PlainHTTP connects to the location using HTTP (not-HTTPS)
PlainHTTP bool
}

// PrepareRecipeOutput populates the recipe output from the recipe deployment output stored in the "result" object.
Expand Down
31 changes: 31 additions & 0 deletions pkg/resourceutil/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,34 @@ func GetConnectionNameandSourceIDs[P any](resource P) (map[string]string, error)

return connectionNamesAndSourceIDs, nil
}

// ResourceMetadata represents resource metadata including ID, Name, Type and Properties
type ResourceMetadata struct {
ID string `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Properties map[string]any `json:"properties,omitempty"`
}

// GetAllPropertiesFromResource extracts the resource metadata including ID, Name, Type and properties
// by parsing the resource ID and extracting properties.
func GetAllPropertiesFromResource[P any](resource P, resourceID string) (*ResourceMetadata, error) {
// Parse resource ID to get name and type
parsedResourceID, err := resources.Parse(resourceID)
if err != nil {
return nil, fmt.Errorf("failed to parse resource ID %s: %w", resourceID, err)
}

// Get properties using existing method
properties, err := GetPropertiesFromResource(resource)
if err != nil {
return nil, err
}

return &ResourceMetadata{
ID: resourceID,
Name: parsedResourceID.Name(),
Type: parsedResourceID.Type(),
Properties: properties,
}, nil
}
Loading
Loading