From bf11754979165271417ed6c4bae455986433ab10 Mon Sep 17 00:00:00 2001 From: Lakshmi Javadekar <103459615+lakshmimsft@users.noreply.github.com> Date: Thu, 18 Jul 2024 11:49:55 -0700 Subject: [PATCH] Add datamodel changes for secret support to Terraform Providers (#7731) # Description Added datamodel updates for secret support to Terraform Providers configuration. This includes secrets in recipeConfig under specific Provider configurations and environment variables. Pls see link to associated [Design Doc](https://github.com/radius-project/design-notes/pull/47/files) NOTE: Updates to functional tests are commented till we merge in corresponding PR in bicep repo : https://github.com/radius-project/bicep/pull/751 ## Type of change - This pull request fixes a bug in Radius and has an approved issue (#6539) Fixes: #6539 Signed-off-by: Reshma Abdul Rahim --- .../2023-10-01-preview/types.json | 2 +- hack/bicep-types-radius/generated/index.json | 2 +- .../environment_conversion.go | 56 +++- .../environment_conversion_test.go | 270 +++++++++++++++--- .../testdata/environmentresource.json | 18 +- .../environmentresourcedatamodel.json | 16 ++ .../v20231001preview/zz_generated_models.go | 27 +- .../zz_generated_models_serde.go | 77 +++++ .../examples/Environments_CreateOrUpdate.json | 18 +- .../examples/Environments_GetEnv0.json | 18 +- .../examples/Environments_List.json | 18 +- .../examples/Environments_PatchEnv0.json | 18 +- .../preview/2023-10-01-preview/openapi.json | 42 ++- .../resources/recipe_terraform_test.go | 7 + .../corerp-resources-terraform-postgres.bicep | 41 ++- typespec/Applications.Core/environments.tsp | 19 +- .../Environments_CreateOrUpdate.json | 18 +- .../Environments_GetEnv0.json | 18 +- .../2023-10-01-preview/Environments_List.json | 18 +- .../Environments_PatchEnv0.json | 18 +- 20 files changed, 656 insertions(+), 65 deletions(-) diff --git a/hack/bicep-types-radius/generated/applications/applications.core/2023-10-01-preview/types.json b/hack/bicep-types-radius/generated/applications/applications.core/2023-10-01-preview/types.json index eff500a957..97103f765c 100644 --- a/hack/bicep-types-radius/generated/applications/applications.core/2023-10-01-preview/types.json +++ b/hack/bicep-types-radius/generated/applications/applications.core/2023-10-01-preview/types.json @@ -1 +1 @@ -[{"1":{"Kind":1}},{"1":{"Kind":2}},{"1":{"Kind":3}},{"1":{"Kind":4}},{"1":{"Kind":5}},{"1":{"Kind":6}},{"1":{"Kind":7}},{"1":{"Kind":8}},{"6":{"Value":"Applications.Core/applications"}},{"6":{"Value":"2023-10-01-preview"}},{"2":{"Name":"Applications.Core/applications","Properties":{"id":{"Type":4,"Flags":10,"Description":"The resource id"},"name":{"Type":4,"Flags":9,"Description":"The resource name"},"type":{"Type":8,"Flags":10,"Description":"The resource type"},"apiVersion":{"Type":9,"Flags":10,"Description":"The resource api version"},"properties":{"Type":11,"Flags":1,"Description":"Application properties"},"tags":{"Type":46,"Flags":0,"Description":"Resource tags."},"location":{"Type":4,"Flags":1,"Description":"The geo-location where the resource lives"},"systemData":{"Type":47,"Flags":2,"Description":"Metadata pertaining to creation and last modification of the resource."}}}},{"2":{"Name":"ApplicationProperties","Properties":{"provisioningState":{"Type":19,"Flags":2,"Description":"Provisioning state of the resource at the time the operation was called"},"environment":{"Type":4,"Flags":1,"Description":"Fully qualified resource ID for the environment that the application is linked to"},"extensions":{"Type":34,"Flags":0,"Description":"The application extension."},"status":{"Type":35,"Flags":2,"Description":"Status of a resource."}}}},{"6":{"Value":"Succeeded"}},{"6":{"Value":"Failed"}},{"6":{"Value":"Canceled"}},{"6":{"Value":"Provisioning"}},{"6":{"Value":"Updating"}},{"6":{"Value":"Deleting"}},{"6":{"Value":"Accepted"}},{"5":{"Elements":[12,13,14,15,16,17,18]}},{"7":{"Name":"Extension","Discriminator":"kind","BaseProperties":{},"Elements":{"daprSidecar":21,"kubernetesMetadata":26,"kubernetesNamespace":30,"manualScaling":32}}},{"2":{"Name":"DaprSidecarExtension","Properties":{"appPort":{"Type":3,"Flags":0,"Description":"The Dapr appPort. Specifies the internal listening port for the application to handle requests from the Dapr sidecar."},"appId":{"Type":4,"Flags":1,"Description":"The Dapr appId. Specifies the identifier used by Dapr for service invocation."},"config":{"Type":4,"Flags":0,"Description":"Specifies the Dapr configuration to use for the resource."},"protocol":{"Type":24,"Flags":0,"Description":"The Dapr sidecar extension protocol"},"kind":{"Type":25,"Flags":1,"Description":"Discriminator property for Extension."}}}},{"6":{"Value":"http"}},{"6":{"Value":"grpc"}},{"5":{"Elements":[22,23]}},{"6":{"Value":"daprSidecar"}},{"2":{"Name":"KubernetesMetadataExtension","Properties":{"annotations":{"Type":27,"Flags":0,"Description":"Annotations to be applied to the Kubernetes resources output by the resource"},"labels":{"Type":28,"Flags":0,"Description":"Labels to be applied to the Kubernetes resources output by the resource"},"kind":{"Type":29,"Flags":1,"Description":"Discriminator property for Extension."}}}},{"2":{"Name":"KubernetesMetadataExtensionAnnotations","Properties":{},"AdditionalProperties":4}},{"2":{"Name":"KubernetesMetadataExtensionLabels","Properties":{},"AdditionalProperties":4}},{"6":{"Value":"kubernetesMetadata"}},{"2":{"Name":"KubernetesNamespaceExtension","Properties":{"namespace":{"Type":4,"Flags":1,"Description":"The namespace of the application environment."},"kind":{"Type":31,"Flags":1,"Description":"Discriminator property for Extension."}}}},{"6":{"Value":"kubernetesNamespace"}},{"2":{"Name":"ManualScalingExtension","Properties":{"replicas":{"Type":3,"Flags":1,"Description":"Replica count."},"kind":{"Type":33,"Flags":1,"Description":"Discriminator property for Extension."}}}},{"6":{"Value":"manualScaling"}},{"3":{"ItemType":20}},{"2":{"Name":"ResourceStatus","Properties":{"compute":{"Type":36,"Flags":0,"Description":"Represents backing compute resource"},"recipe":{"Type":43,"Flags":2,"Description":"Recipe status at deployment time for a resource."},"outputResources":{"Type":45,"Flags":0,"Description":"Properties of an output resource"}}}},{"7":{"Name":"EnvironmentCompute","Discriminator":"kind","BaseProperties":{"resourceId":{"Type":4,"Flags":0,"Description":"The resource id of the compute resource for application environment."},"identity":{"Type":37,"Flags":0,"Description":"IdentitySettings is the external identity setting."}},"Elements":{"kubernetes":41}}},{"2":{"Name":"IdentitySettings","Properties":{"kind":{"Type":40,"Flags":1,"Description":"IdentitySettingKind is the kind of supported external identity setting"},"oidcIssuer":{"Type":4,"Flags":0,"Description":"The URI for your compute platform's OIDC issuer"},"resource":{"Type":4,"Flags":0,"Description":"The resource ID of the provisioned identity"}}}},{"6":{"Value":"undefined"}},{"6":{"Value":"azure.com.workload"}},{"5":{"Elements":[38,39]}},{"2":{"Name":"KubernetesCompute","Properties":{"namespace":{"Type":4,"Flags":1,"Description":"The namespace to use for the environment."},"kind":{"Type":42,"Flags":1,"Description":"Discriminator property for EnvironmentCompute."}}}},{"6":{"Value":"kubernetes"}},{"2":{"Name":"RecipeStatus","Properties":{"templateKind":{"Type":4,"Flags":1,"Description":"TemplateKind is the kind of the recipe template used by the portable resource upon deployment."},"templatePath":{"Type":4,"Flags":1,"Description":"TemplatePath is the path of the recipe consumed by the portable resource upon deployment."},"templateVersion":{"Type":4,"Flags":0,"Description":"TemplateVersion is the version number of the template."}}}},{"2":{"Name":"OutputResource","Properties":{"localId":{"Type":4,"Flags":0,"Description":"The logical identifier scoped to the owning Radius resource. This is only needed or used when a resource has a dependency relationship. LocalIDs do not have any particular format or meaning beyond being compared to determine dependency relationships."},"id":{"Type":4,"Flags":0,"Description":"The UCP resource ID of the underlying resource."},"radiusManaged":{"Type":2,"Flags":0,"Description":"Determines whether Radius manages the lifecycle of the underlying resource."}}}},{"3":{"ItemType":44}},{"2":{"Name":"TrackedResourceTags","Properties":{},"AdditionalProperties":4}},{"2":{"Name":"SystemData","Properties":{"createdBy":{"Type":4,"Flags":0,"Description":"The identity that created the resource."},"createdByType":{"Type":52,"Flags":0,"Description":"The type of identity that created the resource."},"createdAt":{"Type":4,"Flags":0,"Description":"The timestamp of resource creation (UTC)."},"lastModifiedBy":{"Type":4,"Flags":0,"Description":"The identity that last modified the resource."},"lastModifiedByType":{"Type":57,"Flags":0,"Description":"The type of identity that created the resource."},"lastModifiedAt":{"Type":4,"Flags":0,"Description":"The timestamp of resource last modification (UTC)"}}}},{"6":{"Value":"User"}},{"6":{"Value":"Application"}},{"6":{"Value":"ManagedIdentity"}},{"6":{"Value":"Key"}},{"5":{"Elements":[48,49,50,51]}},{"6":{"Value":"User"}},{"6":{"Value":"Application"}},{"6":{"Value":"ManagedIdentity"}},{"6":{"Value":"Key"}},{"5":{"Elements":[53,54,55,56]}},{"4":{"Name":"Applications.Core/applications@2023-10-01-preview","ScopeType":0,"Body":10}},{"6":{"Value":"Applications.Core/containers"}},{"6":{"Value":"2023-10-01-preview"}},{"2":{"Name":"Applications.Core/containers","Properties":{"id":{"Type":4,"Flags":10,"Description":"The resource id"},"name":{"Type":4,"Flags":9,"Description":"The resource name"},"type":{"Type":59,"Flags":10,"Description":"The resource type"},"apiVersion":{"Type":60,"Flags":10,"Description":"The resource api version"},"properties":{"Type":62,"Flags":1,"Description":"Container properties"},"tags":{"Type":122,"Flags":0,"Description":"Resource tags."},"location":{"Type":4,"Flags":1,"Description":"The geo-location where the resource lives"},"systemData":{"Type":47,"Flags":2,"Description":"Metadata pertaining to creation and last modification of the resource."}}}},{"2":{"Name":"ContainerProperties","Properties":{"environment":{"Type":4,"Flags":0,"Description":"Fully qualified resource ID for the environment that the application is linked to"},"application":{"Type":4,"Flags":1,"Description":"Fully qualified resource ID for the application"},"provisioningState":{"Type":70,"Flags":2,"Description":"Provisioning state of the resource at the time the operation was called"},"status":{"Type":35,"Flags":2,"Description":"Status of a resource."},"container":{"Type":71,"Flags":1,"Description":"Definition of a container"},"connections":{"Type":108,"Flags":0,"Description":"Specifies a connection to another resource."},"identity":{"Type":37,"Flags":0,"Description":"IdentitySettings is the external identity setting."},"extensions":{"Type":109,"Flags":0,"Description":"Extensions spec of the resource"},"resourceProvisioning":{"Type":112,"Flags":0,"Description":"Specifies how the underlying service/resource is provisioned and managed. Available values are 'internal', where Radius manages the lifecycle of the resource internally, and 'manual', where a user manages the resource."},"resources":{"Type":114,"Flags":0,"Description":"A collection of references to resources associated with the container"},"restartPolicy":{"Type":118,"Flags":0,"Description":"Restart policy for the container"},"runtimes":{"Type":119,"Flags":0,"Description":"The properties for runtime configuration"}}}},{"6":{"Value":"Succeeded"}},{"6":{"Value":"Failed"}},{"6":{"Value":"Canceled"}},{"6":{"Value":"Provisioning"}},{"6":{"Value":"Updating"}},{"6":{"Value":"Deleting"}},{"6":{"Value":"Accepted"}},{"5":{"Elements":[63,64,65,66,67,68,69]}},{"2":{"Name":"Container","Properties":{"image":{"Type":4,"Flags":1,"Description":"The registry and image to download and run in your container"},"imagePullPolicy":{"Type":75,"Flags":0,"Description":"The image pull policy for the container"},"env":{"Type":76,"Flags":0,"Description":"environment"},"ports":{"Type":81,"Flags":0,"Description":"container ports"},"readinessProbe":{"Type":82,"Flags":0,"Description":"Properties for readiness/liveness probe"},"livenessProbe":{"Type":82,"Flags":0,"Description":"Properties for readiness/liveness probe"},"volumes":{"Type":101,"Flags":0,"Description":"container volumes"},"command":{"Type":102,"Flags":0,"Description":"Entrypoint array. Overrides the container image's ENTRYPOINT"},"args":{"Type":103,"Flags":0,"Description":"Arguments to the entrypoint. Overrides the container image's CMD"},"workingDir":{"Type":4,"Flags":0,"Description":"Working directory for the container"}}}},{"6":{"Value":"Always"}},{"6":{"Value":"IfNotPresent"}},{"6":{"Value":"Never"}},{"5":{"Elements":[72,73,74]}},{"2":{"Name":"ContainerEnv","Properties":{},"AdditionalProperties":4}},{"2":{"Name":"ContainerPortProperties","Properties":{"containerPort":{"Type":3,"Flags":1,"Description":"The listening port number"},"protocol":{"Type":80,"Flags":0,"Description":"The protocol in use by the port"},"scheme":{"Type":4,"Flags":0,"Description":"Specifies the URL scheme of the communication protocol. Consumers can use the scheme to construct a URL. The value defaults to 'http' or 'https' depending on the port value"},"port":{"Type":3,"Flags":0,"Description":"Specifies the port that will be exposed by this container. Must be set when value different from containerPort is desired"}}}},{"6":{"Value":"TCP"}},{"6":{"Value":"UDP"}},{"5":{"Elements":[78,79]}},{"2":{"Name":"ContainerPorts","Properties":{},"AdditionalProperties":77}},{"7":{"Name":"HealthProbeProperties","Discriminator":"kind","BaseProperties":{"initialDelaySeconds":{"Type":3,"Flags":0,"Description":"Initial delay in seconds before probing for readiness/liveness"},"failureThreshold":{"Type":3,"Flags":0,"Description":"Threshold number of times the probe fails after which a failure would be reported"},"periodSeconds":{"Type":3,"Flags":0,"Description":"Interval for the readiness/liveness probe in seconds"},"timeoutSeconds":{"Type":3,"Flags":0,"Description":"Number of seconds after which the readiness/liveness probe times out. Defaults to 5 seconds"}},"Elements":{"exec":83,"httpGet":85,"tcp":88}}},{"2":{"Name":"ExecHealthProbeProperties","Properties":{"command":{"Type":4,"Flags":1,"Description":"Command to execute to probe readiness/liveness"},"kind":{"Type":84,"Flags":1,"Description":"Discriminator property for HealthProbeProperties."}}}},{"6":{"Value":"exec"}},{"2":{"Name":"HttpGetHealthProbeProperties","Properties":{"containerPort":{"Type":3,"Flags":1,"Description":"The listening port number"},"path":{"Type":4,"Flags":1,"Description":"The route to make the HTTP request on"},"headers":{"Type":86,"Flags":0,"Description":"Custom HTTP headers to add to the get request"},"kind":{"Type":87,"Flags":1,"Description":"Discriminator property for HealthProbeProperties."}}}},{"2":{"Name":"HttpGetHealthProbePropertiesHeaders","Properties":{},"AdditionalProperties":4}},{"6":{"Value":"httpGet"}},{"2":{"Name":"TcpHealthProbeProperties","Properties":{"containerPort":{"Type":3,"Flags":1,"Description":"The listening port number"},"kind":{"Type":89,"Flags":1,"Description":"Discriminator property for HealthProbeProperties."}}}},{"6":{"Value":"tcp"}},{"7":{"Name":"Volume","Discriminator":"kind","BaseProperties":{"mountPath":{"Type":4,"Flags":0,"Description":"The path where the volume is mounted"}},"Elements":{"ephemeral":91,"persistent":96}}},{"2":{"Name":"EphemeralVolume","Properties":{"managedStore":{"Type":94,"Flags":1,"Description":"The managed store for the ephemeral volume"},"kind":{"Type":95,"Flags":1,"Description":"Discriminator property for Volume."}}}},{"6":{"Value":"memory"}},{"6":{"Value":"disk"}},{"5":{"Elements":[92,93]}},{"6":{"Value":"ephemeral"}},{"2":{"Name":"PersistentVolume","Properties":{"permission":{"Type":99,"Flags":0,"Description":"The persistent volume permission"},"source":{"Type":4,"Flags":1,"Description":"The source of the volume"},"kind":{"Type":100,"Flags":1,"Description":"Discriminator property for Volume."}}}},{"6":{"Value":"read"}},{"6":{"Value":"write"}},{"5":{"Elements":[97,98]}},{"6":{"Value":"persistent"}},{"2":{"Name":"ContainerVolumes","Properties":{},"AdditionalProperties":90}},{"3":{"ItemType":4}},{"3":{"ItemType":4}},{"2":{"Name":"ConnectionProperties","Properties":{"source":{"Type":4,"Flags":1,"Description":"The source of the connection"},"disableDefaultEnvVars":{"Type":2,"Flags":0,"Description":"default environment variable override"},"iam":{"Type":105,"Flags":0,"Description":"IAM properties"}}}},{"2":{"Name":"IamProperties","Properties":{"kind":{"Type":106,"Flags":1,"Description":"The kind of IAM provider to configure"},"roles":{"Type":107,"Flags":0,"Description":"RBAC permissions to be assigned on the source resource"}}}},{"6":{"Value":"azure"}},{"3":{"ItemType":4}},{"2":{"Name":"ContainerPropertiesConnections","Properties":{},"AdditionalProperties":104}},{"3":{"ItemType":20}},{"6":{"Value":"internal"}},{"6":{"Value":"manual"}},{"5":{"Elements":[110,111]}},{"2":{"Name":"ResourceReference","Properties":{"id":{"Type":4,"Flags":1,"Description":"Resource id of an existing resource"}}}},{"3":{"ItemType":113}},{"6":{"Value":"Always"}},{"6":{"Value":"OnFailure"}},{"6":{"Value":"Never"}},{"5":{"Elements":[115,116,117]}},{"2":{"Name":"RuntimesProperties","Properties":{"kubernetes":{"Type":120,"Flags":0,"Description":"The runtime configuration properties for Kubernetes"}}}},{"2":{"Name":"KubernetesRuntimeProperties","Properties":{"base":{"Type":4,"Flags":0,"Description":"The serialized YAML manifest which represents the base Kubernetes resources to deploy, such as Deployment, Service, ServiceAccount, Secrets, and ConfigMaps."},"pod":{"Type":121,"Flags":0,"Description":"A strategic merge patch that will be applied to the PodSpec object when this container is being deployed."}}}},{"2":{"Name":"KubernetesPodSpec","Properties":{},"AdditionalProperties":0}},{"2":{"Name":"TrackedResourceTags","Properties":{},"AdditionalProperties":4}},{"4":{"Name":"Applications.Core/containers@2023-10-01-preview","ScopeType":0,"Body":61}},{"6":{"Value":"Applications.Core/environments"}},{"6":{"Value":"2023-10-01-preview"}},{"2":{"Name":"Applications.Core/environments","Properties":{"id":{"Type":4,"Flags":10,"Description":"The resource id"},"name":{"Type":4,"Flags":9,"Description":"The resource name"},"type":{"Type":124,"Flags":10,"Description":"The resource type"},"apiVersion":{"Type":125,"Flags":10,"Description":"The resource api version"},"properties":{"Type":127,"Flags":1,"Description":"Environment properties"},"tags":{"Type":157,"Flags":0,"Description":"Resource tags."},"location":{"Type":4,"Flags":1,"Description":"The geo-location where the resource lives"},"systemData":{"Type":47,"Flags":2,"Description":"Metadata pertaining to creation and last modification of the resource."}}}},{"2":{"Name":"EnvironmentProperties","Properties":{"provisioningState":{"Type":135,"Flags":2,"Description":"Provisioning state of the resource at the time the operation was called"},"compute":{"Type":36,"Flags":1,"Description":"Represents backing compute resource"},"providers":{"Type":136,"Flags":0,"Description":"The Cloud providers configuration."},"simulated":{"Type":2,"Flags":0,"Description":"Simulated environment."},"recipes":{"Type":145,"Flags":0,"Description":"Specifies Recipes linked to the Environment."},"recipeConfig":{"Type":146,"Flags":0,"Description":"Configuration for Recipes. Defines how each type of Recipe should be configured and run."},"extensions":{"Type":156,"Flags":0,"Description":"The environment extension."}}}},{"6":{"Value":"Succeeded"}},{"6":{"Value":"Failed"}},{"6":{"Value":"Canceled"}},{"6":{"Value":"Provisioning"}},{"6":{"Value":"Updating"}},{"6":{"Value":"Deleting"}},{"6":{"Value":"Accepted"}},{"5":{"Elements":[128,129,130,131,132,133,134]}},{"2":{"Name":"Providers","Properties":{"azure":{"Type":137,"Flags":0,"Description":"The Azure cloud provider definition."},"aws":{"Type":138,"Flags":0,"Description":"The AWS cloud provider definition."}}}},{"2":{"Name":"ProvidersAzure","Properties":{"scope":{"Type":4,"Flags":1,"Description":"Target scope for Azure resources to be deployed into. For example: '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup'."}}}},{"2":{"Name":"ProvidersAws","Properties":{"scope":{"Type":4,"Flags":1,"Description":"Target scope for AWS resources to be deployed into. For example: '/planes/aws/aws/accounts/000000000000/regions/us-west-2'."}}}},{"7":{"Name":"RecipeProperties","Discriminator":"templateKind","BaseProperties":{"templatePath":{"Type":4,"Flags":1,"Description":"Path to the template provided by the recipe. Currently only link to Azure Container Registry is supported."},"parameters":{"Type":0,"Flags":0,"Description":"Any object"}},"Elements":{"bicep":140,"terraform":142}}},{"2":{"Name":"BicepRecipeProperties","Properties":{"plainHttp":{"Type":2,"Flags":0,"Description":"Connect to the Bicep registry using HTTP (not-HTTPS). This should be used when the registry is known not to support HTTPS, for example in a locally-hosted registry. Defaults to false (use HTTPS/TLS)."},"templateKind":{"Type":141,"Flags":1,"Description":"Discriminator property for RecipeProperties."}}}},{"6":{"Value":"bicep"}},{"2":{"Name":"TerraformRecipeProperties","Properties":{"templateVersion":{"Type":4,"Flags":0,"Description":"Version of the template to deploy. For Terraform recipes using a module registry this is required, but must be omitted for other module sources."},"templateKind":{"Type":143,"Flags":1,"Description":"Discriminator property for RecipeProperties."}}}},{"6":{"Value":"terraform"}},{"2":{"Name":"DictionaryOfRecipeProperties","Properties":{},"AdditionalProperties":139}},{"2":{"Name":"EnvironmentPropertiesRecipes","Properties":{},"AdditionalProperties":144}},{"2":{"Name":"RecipeConfigProperties","Properties":{"terraform":{"Type":147,"Flags":0,"Description":"Configuration for Terraform Recipes. Controls how Terraform plans and applies templates as part of Recipe deployment."},"env":{"Type":155,"Flags":0,"Description":"The environment variables injected during Terraform Recipe execution for the recipes in the environment."}}}},{"2":{"Name":"TerraformConfigProperties","Properties":{"authentication":{"Type":148,"Flags":0,"Description":"Authentication information used to access private Terraform module sources. Supported module sources: Git."},"providers":{"Type":154,"Flags":0,"Description":"Configuration for Terraform Recipe Providers. Controls how Terraform interacts with cloud providers, SaaS providers, and other APIs. For more information, please see: https://developer.hashicorp.com/terraform/language/providers/configuration."}}}},{"2":{"Name":"AuthConfig","Properties":{"git":{"Type":149,"Flags":0,"Description":"Authentication information used to access private Terraform modules from Git repository sources."}}}},{"2":{"Name":"GitAuthConfig","Properties":{"pat":{"Type":151,"Flags":0,"Description":"Personal Access Token (PAT) configuration used to authenticate to Git platforms."}}}},{"2":{"Name":"SecretConfig","Properties":{"secret":{"Type":4,"Flags":0,"Description":"The ID of an Applications.Core/SecretStore resource containing the Git platform personal access token (PAT). The secret store must have a secret named 'pat', containing the PAT value. A secret named 'username' is optional, containing the username associated with the pat. By default no username is specified."}}}},{"2":{"Name":"GitAuthConfigPat","Properties":{},"AdditionalProperties":150}},{"2":{"Name":"ProviderConfigProperties","Properties":{},"AdditionalProperties":0}},{"3":{"ItemType":152}},{"2":{"Name":"TerraformConfigPropertiesProviders","Properties":{},"AdditionalProperties":153}},{"2":{"Name":"EnvironmentVariables","Properties":{},"AdditionalProperties":4}},{"3":{"ItemType":20}},{"2":{"Name":"TrackedResourceTags","Properties":{},"AdditionalProperties":4}},{"4":{"Name":"Applications.Core/environments@2023-10-01-preview","ScopeType":0,"Body":126}},{"6":{"Value":"Applications.Core/extenders"}},{"6":{"Value":"2023-10-01-preview"}},{"2":{"Name":"Applications.Core/extenders","Properties":{"id":{"Type":4,"Flags":10,"Description":"The resource id"},"name":{"Type":4,"Flags":9,"Description":"The resource name"},"type":{"Type":159,"Flags":10,"Description":"The resource type"},"apiVersion":{"Type":160,"Flags":10,"Description":"The resource api version"},"properties":{"Type":162,"Flags":1,"Description":"ExtenderResource portable resource properties"},"tags":{"Type":175,"Flags":0,"Description":"Resource tags."},"location":{"Type":4,"Flags":1,"Description":"The geo-location where the resource lives"},"systemData":{"Type":47,"Flags":2,"Description":"Metadata pertaining to creation and last modification of the resource."}}}},{"2":{"Name":"ExtenderProperties","Properties":{"environment":{"Type":4,"Flags":1,"Description":"Fully qualified resource ID for the environment that the portable resource is linked to"},"application":{"Type":4,"Flags":0,"Description":"Fully qualified resource ID for the application that the portable resource is consumed by (if applicable)"},"provisioningState":{"Type":170,"Flags":2,"Description":"Provisioning state of the resource at the time the operation was called"},"status":{"Type":35,"Flags":2,"Description":"Status of a resource."},"secrets":{"Type":0,"Flags":0,"Description":"Any object"},"recipe":{"Type":171,"Flags":0,"Description":"The recipe used to automatically deploy underlying infrastructure for a portable resource"},"resourceProvisioning":{"Type":174,"Flags":0,"Description":"Specifies how the underlying service/resource is provisioned and managed. Available values are 'recipe', where Radius manages the lifecycle of the resource through a Recipe, and 'manual', where a user manages the resource and provides the values."}},"AdditionalProperties":0}},{"6":{"Value":"Succeeded"}},{"6":{"Value":"Failed"}},{"6":{"Value":"Canceled"}},{"6":{"Value":"Provisioning"}},{"6":{"Value":"Updating"}},{"6":{"Value":"Deleting"}},{"6":{"Value":"Accepted"}},{"5":{"Elements":[163,164,165,166,167,168,169]}},{"2":{"Name":"Recipe","Properties":{"name":{"Type":4,"Flags":1,"Description":"The name of the recipe within the environment to use"},"parameters":{"Type":0,"Flags":0,"Description":"Any object"}}}},{"6":{"Value":"recipe"}},{"6":{"Value":"manual"}},{"5":{"Elements":[172,173]}},{"2":{"Name":"TrackedResourceTags","Properties":{},"AdditionalProperties":4}},{"4":{"Name":"Applications.Core/extenders@2023-10-01-preview","ScopeType":0,"Body":161}},{"6":{"Value":"Applications.Core/gateways"}},{"6":{"Value":"2023-10-01-preview"}},{"2":{"Name":"Applications.Core/gateways","Properties":{"id":{"Type":4,"Flags":10,"Description":"The resource id"},"name":{"Type":4,"Flags":9,"Description":"The resource name"},"type":{"Type":177,"Flags":10,"Description":"The resource type"},"apiVersion":{"Type":178,"Flags":10,"Description":"The resource api version"},"properties":{"Type":180,"Flags":1,"Description":"Gateway properties"},"tags":{"Type":196,"Flags":0,"Description":"Resource tags."},"location":{"Type":4,"Flags":1,"Description":"The geo-location where the resource lives"},"systemData":{"Type":47,"Flags":2,"Description":"Metadata pertaining to creation and last modification of the resource."}}}},{"2":{"Name":"GatewayProperties","Properties":{"environment":{"Type":4,"Flags":0,"Description":"Fully qualified resource ID for the environment that the application is linked to"},"application":{"Type":4,"Flags":1,"Description":"Fully qualified resource ID for the application"},"provisioningState":{"Type":188,"Flags":2,"Description":"Provisioning state of the resource at the time the operation was called"},"status":{"Type":35,"Flags":2,"Description":"Status of a resource."},"internal":{"Type":2,"Flags":0,"Description":"Sets Gateway to not be exposed externally (no public IP address associated). Defaults to false (exposed to internet)."},"hostname":{"Type":189,"Flags":0,"Description":"Declare hostname information for the Gateway. Leaving the hostname empty auto-assigns one: mygateway.myapp.PUBLICHOSTNAMEORIP.nip.io."},"routes":{"Type":191,"Flags":1,"Description":"Routes attached to this Gateway"},"tls":{"Type":192,"Flags":0,"Description":"TLS configuration definition for Gateway resource."},"url":{"Type":4,"Flags":2,"Description":"URL of the gateway resource. Readonly"}}}},{"6":{"Value":"Succeeded"}},{"6":{"Value":"Failed"}},{"6":{"Value":"Canceled"}},{"6":{"Value":"Provisioning"}},{"6":{"Value":"Updating"}},{"6":{"Value":"Deleting"}},{"6":{"Value":"Accepted"}},{"5":{"Elements":[181,182,183,184,185,186,187]}},{"2":{"Name":"GatewayHostname","Properties":{"prefix":{"Type":4,"Flags":0,"Description":"Specify a prefix for the hostname: myhostname.myapp.PUBLICHOSTNAMEORIP.nip.io. Mutually exclusive with 'fullyQualifiedHostname' and will be overridden if both are defined."},"fullyQualifiedHostname":{"Type":4,"Flags":0,"Description":"Specify a fully-qualified domain name: myapp.mydomain.com. Mutually exclusive with 'prefix' and will take priority if both are defined."}}}},{"2":{"Name":"GatewayRoute","Properties":{"path":{"Type":4,"Flags":0,"Description":"The path to match the incoming request path on. Ex - /myservice."},"destination":{"Type":4,"Flags":0,"Description":"The URL or id of the service to route to. Ex - 'http://myservice'."},"replacePrefix":{"Type":4,"Flags":0,"Description":"Optionally update the prefix when sending the request to the service. Ex - replacePrefix: '/' and path: '/myservice' will transform '/myservice/myroute' to '/myroute'"},"enableWebsockets":{"Type":2,"Flags":0,"Description":"Enables websocket support for the route. Defaults to false."}}}},{"3":{"ItemType":190}},{"2":{"Name":"GatewayTls","Properties":{"sslPassthrough":{"Type":2,"Flags":0,"Description":"If true, gateway lets the https traffic sslPassthrough to the backend servers for decryption."},"minimumProtocolVersion":{"Type":195,"Flags":0,"Description":"Tls Minimum versions for Gateway resource."},"certificateFrom":{"Type":4,"Flags":0,"Description":"The resource id for the secret containing the TLS certificate and key for the gateway."}}}},{"6":{"Value":"1.2"}},{"6":{"Value":"1.3"}},{"5":{"Elements":[193,194]}},{"2":{"Name":"TrackedResourceTags","Properties":{},"AdditionalProperties":4}},{"4":{"Name":"Applications.Core/gateways@2023-10-01-preview","ScopeType":0,"Body":179}},{"6":{"Value":"Applications.Core/secretStores"}},{"6":{"Value":"2023-10-01-preview"}},{"2":{"Name":"Applications.Core/secretStores","Properties":{"id":{"Type":4,"Flags":10,"Description":"The resource id"},"name":{"Type":4,"Flags":9,"Description":"The resource name"},"type":{"Type":198,"Flags":10,"Description":"The resource type"},"apiVersion":{"Type":199,"Flags":10,"Description":"The resource api version"},"properties":{"Type":201,"Flags":1,"Description":"The properties of SecretStore"},"tags":{"Type":219,"Flags":0,"Description":"Resource tags."},"location":{"Type":4,"Flags":1,"Description":"The geo-location where the resource lives"},"systemData":{"Type":47,"Flags":2,"Description":"Metadata pertaining to creation and last modification of the resource."}}}},{"2":{"Name":"SecretStoreProperties","Properties":{"environment":{"Type":4,"Flags":0,"Description":"Fully qualified resource ID for the environment that the application is linked to"},"application":{"Type":4,"Flags":0,"Description":"Fully qualified resource ID for the application"},"provisioningState":{"Type":209,"Flags":2,"Description":"Provisioning state of the resource at the time the operation was called"},"status":{"Type":35,"Flags":2,"Description":"Status of a resource."},"type":{"Type":212,"Flags":0,"Description":"The type of SecretStore data"},"data":{"Type":218,"Flags":1,"Description":"An object to represent key-value type secrets"},"resource":{"Type":4,"Flags":0,"Description":"The resource id of external secret store."}}}},{"6":{"Value":"Succeeded"}},{"6":{"Value":"Failed"}},{"6":{"Value":"Canceled"}},{"6":{"Value":"Provisioning"}},{"6":{"Value":"Updating"}},{"6":{"Value":"Deleting"}},{"6":{"Value":"Accepted"}},{"5":{"Elements":[202,203,204,205,206,207,208]}},{"6":{"Value":"generic"}},{"6":{"Value":"certificate"}},{"5":{"Elements":[210,211]}},{"2":{"Name":"SecretValueProperties","Properties":{"encoding":{"Type":216,"Flags":0,"Description":"The type of SecretValue Encoding"},"value":{"Type":4,"Flags":0,"Description":"The value of secret."},"valueFrom":{"Type":217,"Flags":0,"Description":"The Secret value source properties"}}}},{"6":{"Value":"raw"}},{"6":{"Value":"base64"}},{"5":{"Elements":[214,215]}},{"2":{"Name":"ValueFromProperties","Properties":{"name":{"Type":4,"Flags":1,"Description":"The name of the referenced secret."},"version":{"Type":4,"Flags":0,"Description":"The version of the referenced secret."}}}},{"2":{"Name":"SecretStorePropertiesData","Properties":{},"AdditionalProperties":213}},{"2":{"Name":"TrackedResourceTags","Properties":{},"AdditionalProperties":4}},{"4":{"Name":"Applications.Core/secretStores@2023-10-01-preview","ScopeType":0,"Body":200}},{"6":{"Value":"Applications.Core/volumes"}},{"6":{"Value":"2023-10-01-preview"}},{"2":{"Name":"Applications.Core/volumes","Properties":{"id":{"Type":4,"Flags":10,"Description":"The resource id"},"name":{"Type":4,"Flags":9,"Description":"The resource name"},"type":{"Type":221,"Flags":10,"Description":"The resource type"},"apiVersion":{"Type":222,"Flags":10,"Description":"The resource api version"},"properties":{"Type":224,"Flags":1,"Description":"Volume properties"},"tags":{"Type":256,"Flags":0,"Description":"Resource tags."},"location":{"Type":4,"Flags":1,"Description":"The geo-location where the resource lives"},"systemData":{"Type":47,"Flags":2,"Description":"Metadata pertaining to creation and last modification of the resource."}}}},{"7":{"Name":"VolumeProperties","Discriminator":"kind","BaseProperties":{"environment":{"Type":4,"Flags":0,"Description":"Fully qualified resource ID for the environment that the application is linked to"},"application":{"Type":4,"Flags":1,"Description":"Fully qualified resource ID for the application"},"provisioningState":{"Type":232,"Flags":2,"Description":"Provisioning state of the resource at the time the operation was called"},"status":{"Type":35,"Flags":2,"Description":"Status of a resource."}},"Elements":{"azure.com.keyvault":233}}},{"6":{"Value":"Succeeded"}},{"6":{"Value":"Failed"}},{"6":{"Value":"Canceled"}},{"6":{"Value":"Provisioning"}},{"6":{"Value":"Updating"}},{"6":{"Value":"Deleting"}},{"6":{"Value":"Accepted"}},{"5":{"Elements":[225,226,227,228,229,230,231]}},{"2":{"Name":"AzureKeyVaultVolumeProperties","Properties":{"certificates":{"Type":246,"Flags":0,"Description":"The KeyVault certificates that this volume exposes"},"keys":{"Type":248,"Flags":0,"Description":"The KeyVault keys that this volume exposes"},"resource":{"Type":4,"Flags":1,"Description":"The ID of the keyvault to use for this volume resource"},"secrets":{"Type":254,"Flags":0,"Description":"The KeyVault secrets that this volume exposes"},"kind":{"Type":255,"Flags":1,"Description":"Discriminator property for VolumeProperties."}}}},{"2":{"Name":"CertificateObjectProperties","Properties":{"alias":{"Type":4,"Flags":0,"Description":"File name when written to disk"},"encoding":{"Type":238,"Flags":0,"Description":"Represents secret encodings"},"format":{"Type":241,"Flags":0,"Description":"Represents certificate formats"},"name":{"Type":4,"Flags":1,"Description":"The name of the certificate"},"certType":{"Type":245,"Flags":0,"Description":"Represents certificate types"},"version":{"Type":4,"Flags":0,"Description":"Certificate version"}}}},{"6":{"Value":"utf-8"}},{"6":{"Value":"hex"}},{"6":{"Value":"base64"}},{"5":{"Elements":[235,236,237]}},{"6":{"Value":"pem"}},{"6":{"Value":"pfx"}},{"5":{"Elements":[239,240]}},{"6":{"Value":"certificate"}},{"6":{"Value":"privatekey"}},{"6":{"Value":"publickey"}},{"5":{"Elements":[242,243,244]}},{"2":{"Name":"AzureKeyVaultVolumePropertiesCertificates","Properties":{},"AdditionalProperties":234}},{"2":{"Name":"KeyObjectProperties","Properties":{"alias":{"Type":4,"Flags":0,"Description":"File name when written to disk"},"name":{"Type":4,"Flags":1,"Description":"The name of the key"},"version":{"Type":4,"Flags":0,"Description":"Key version"}}}},{"2":{"Name":"AzureKeyVaultVolumePropertiesKeys","Properties":{},"AdditionalProperties":247}},{"2":{"Name":"SecretObjectProperties","Properties":{"alias":{"Type":4,"Flags":0,"Description":"File name when written to disk"},"encoding":{"Type":253,"Flags":0,"Description":"Represents secret encodings"},"name":{"Type":4,"Flags":1,"Description":"The name of the secret"},"version":{"Type":4,"Flags":0,"Description":"secret version"}}}},{"6":{"Value":"utf-8"}},{"6":{"Value":"hex"}},{"6":{"Value":"base64"}},{"5":{"Elements":[250,251,252]}},{"2":{"Name":"AzureKeyVaultVolumePropertiesSecrets","Properties":{},"AdditionalProperties":249}},{"6":{"Value":"azure.com.keyvault"}},{"2":{"Name":"TrackedResourceTags","Properties":{},"AdditionalProperties":4}},{"4":{"Name":"Applications.Core/volumes@2023-10-01-preview","ScopeType":0,"Body":223}},{"8":{"Name":"listSecrets","ResourceType":"Applications.Core/extenders","ApiVersion":"2023-10-01-preview","Output":0,"Input":0}},{"2":{"Name":"SecretStoreListSecretsResult","Properties":{"type":{"Type":262,"Flags":2,"Description":"The type of SecretStore data"},"data":{"Type":263,"Flags":2,"Description":"An object to represent key-value type secrets"}}}},{"6":{"Value":"generic"}},{"6":{"Value":"certificate"}},{"5":{"Elements":[260,261]}},{"2":{"Name":"SecretStoreListSecretsResultData","Properties":{},"AdditionalProperties":213}},{"8":{"Name":"listSecrets","ResourceType":"Applications.Core/secretStores","ApiVersion":"2023-10-01-preview","Output":259,"Input":0}}] \ No newline at end of file +[{"1":{"Kind":1}},{"1":{"Kind":2}},{"1":{"Kind":3}},{"1":{"Kind":4}},{"1":{"Kind":5}},{"1":{"Kind":6}},{"1":{"Kind":7}},{"1":{"Kind":8}},{"6":{"Value":"Applications.Core/applications"}},{"6":{"Value":"2023-10-01-preview"}},{"2":{"Name":"Applications.Core/applications","Properties":{"id":{"Type":4,"Flags":10,"Description":"The resource id"},"name":{"Type":4,"Flags":9,"Description":"The resource name"},"type":{"Type":8,"Flags":10,"Description":"The resource type"},"apiVersion":{"Type":9,"Flags":10,"Description":"The resource api version"},"properties":{"Type":11,"Flags":1,"Description":"Application properties"},"tags":{"Type":46,"Flags":0,"Description":"Resource tags."},"location":{"Type":4,"Flags":1,"Description":"The geo-location where the resource lives"},"systemData":{"Type":47,"Flags":2,"Description":"Metadata pertaining to creation and last modification of the resource."}}}},{"2":{"Name":"ApplicationProperties","Properties":{"provisioningState":{"Type":19,"Flags":2,"Description":"Provisioning state of the resource at the time the operation was called"},"environment":{"Type":4,"Flags":1,"Description":"Fully qualified resource ID for the environment that the application is linked to"},"extensions":{"Type":34,"Flags":0,"Description":"The application extension."},"status":{"Type":35,"Flags":2,"Description":"Status of a resource."}}}},{"6":{"Value":"Succeeded"}},{"6":{"Value":"Failed"}},{"6":{"Value":"Canceled"}},{"6":{"Value":"Provisioning"}},{"6":{"Value":"Updating"}},{"6":{"Value":"Deleting"}},{"6":{"Value":"Accepted"}},{"5":{"Elements":[12,13,14,15,16,17,18]}},{"7":{"Name":"Extension","Discriminator":"kind","BaseProperties":{},"Elements":{"daprSidecar":21,"kubernetesMetadata":26,"kubernetesNamespace":30,"manualScaling":32}}},{"2":{"Name":"DaprSidecarExtension","Properties":{"appPort":{"Type":3,"Flags":0,"Description":"The Dapr appPort. Specifies the internal listening port for the application to handle requests from the Dapr sidecar."},"appId":{"Type":4,"Flags":1,"Description":"The Dapr appId. Specifies the identifier used by Dapr for service invocation."},"config":{"Type":4,"Flags":0,"Description":"Specifies the Dapr configuration to use for the resource."},"protocol":{"Type":24,"Flags":0,"Description":"The Dapr sidecar extension protocol"},"kind":{"Type":25,"Flags":1,"Description":"Discriminator property for Extension."}}}},{"6":{"Value":"http"}},{"6":{"Value":"grpc"}},{"5":{"Elements":[22,23]}},{"6":{"Value":"daprSidecar"}},{"2":{"Name":"KubernetesMetadataExtension","Properties":{"annotations":{"Type":27,"Flags":0,"Description":"Annotations to be applied to the Kubernetes resources output by the resource"},"labels":{"Type":28,"Flags":0,"Description":"Labels to be applied to the Kubernetes resources output by the resource"},"kind":{"Type":29,"Flags":1,"Description":"Discriminator property for Extension."}}}},{"2":{"Name":"KubernetesMetadataExtensionAnnotations","Properties":{},"AdditionalProperties":4}},{"2":{"Name":"KubernetesMetadataExtensionLabels","Properties":{},"AdditionalProperties":4}},{"6":{"Value":"kubernetesMetadata"}},{"2":{"Name":"KubernetesNamespaceExtension","Properties":{"namespace":{"Type":4,"Flags":1,"Description":"The namespace of the application environment."},"kind":{"Type":31,"Flags":1,"Description":"Discriminator property for Extension."}}}},{"6":{"Value":"kubernetesNamespace"}},{"2":{"Name":"ManualScalingExtension","Properties":{"replicas":{"Type":3,"Flags":1,"Description":"Replica count."},"kind":{"Type":33,"Flags":1,"Description":"Discriminator property for Extension."}}}},{"6":{"Value":"manualScaling"}},{"3":{"ItemType":20}},{"2":{"Name":"ResourceStatus","Properties":{"compute":{"Type":36,"Flags":0,"Description":"Represents backing compute resource"},"recipe":{"Type":43,"Flags":2,"Description":"Recipe status at deployment time for a resource."},"outputResources":{"Type":45,"Flags":0,"Description":"Properties of an output resource"}}}},{"7":{"Name":"EnvironmentCompute","Discriminator":"kind","BaseProperties":{"resourceId":{"Type":4,"Flags":0,"Description":"The resource id of the compute resource for application environment."},"identity":{"Type":37,"Flags":0,"Description":"IdentitySettings is the external identity setting."}},"Elements":{"kubernetes":41}}},{"2":{"Name":"IdentitySettings","Properties":{"kind":{"Type":40,"Flags":1,"Description":"IdentitySettingKind is the kind of supported external identity setting"},"oidcIssuer":{"Type":4,"Flags":0,"Description":"The URI for your compute platform's OIDC issuer"},"resource":{"Type":4,"Flags":0,"Description":"The resource ID of the provisioned identity"}}}},{"6":{"Value":"undefined"}},{"6":{"Value":"azure.com.workload"}},{"5":{"Elements":[38,39]}},{"2":{"Name":"KubernetesCompute","Properties":{"namespace":{"Type":4,"Flags":1,"Description":"The namespace to use for the environment."},"kind":{"Type":42,"Flags":1,"Description":"Discriminator property for EnvironmentCompute."}}}},{"6":{"Value":"kubernetes"}},{"2":{"Name":"RecipeStatus","Properties":{"templateKind":{"Type":4,"Flags":1,"Description":"TemplateKind is the kind of the recipe template used by the portable resource upon deployment."},"templatePath":{"Type":4,"Flags":1,"Description":"TemplatePath is the path of the recipe consumed by the portable resource upon deployment."},"templateVersion":{"Type":4,"Flags":0,"Description":"TemplateVersion is the version number of the template."}}}},{"2":{"Name":"OutputResource","Properties":{"localId":{"Type":4,"Flags":0,"Description":"The logical identifier scoped to the owning Radius resource. This is only needed or used when a resource has a dependency relationship. LocalIDs do not have any particular format or meaning beyond being compared to determine dependency relationships."},"id":{"Type":4,"Flags":0,"Description":"The UCP resource ID of the underlying resource."},"radiusManaged":{"Type":2,"Flags":0,"Description":"Determines whether Radius manages the lifecycle of the underlying resource."}}}},{"3":{"ItemType":44}},{"2":{"Name":"TrackedResourceTags","Properties":{},"AdditionalProperties":4}},{"2":{"Name":"SystemData","Properties":{"createdBy":{"Type":4,"Flags":0,"Description":"The identity that created the resource."},"createdByType":{"Type":52,"Flags":0,"Description":"The type of identity that created the resource."},"createdAt":{"Type":4,"Flags":0,"Description":"The timestamp of resource creation (UTC)."},"lastModifiedBy":{"Type":4,"Flags":0,"Description":"The identity that last modified the resource."},"lastModifiedByType":{"Type":57,"Flags":0,"Description":"The type of identity that created the resource."},"lastModifiedAt":{"Type":4,"Flags":0,"Description":"The timestamp of resource last modification (UTC)"}}}},{"6":{"Value":"User"}},{"6":{"Value":"Application"}},{"6":{"Value":"ManagedIdentity"}},{"6":{"Value":"Key"}},{"5":{"Elements":[48,49,50,51]}},{"6":{"Value":"User"}},{"6":{"Value":"Application"}},{"6":{"Value":"ManagedIdentity"}},{"6":{"Value":"Key"}},{"5":{"Elements":[53,54,55,56]}},{"4":{"Name":"Applications.Core/applications@2023-10-01-preview","ScopeType":0,"Body":10}},{"6":{"Value":"Applications.Core/containers"}},{"6":{"Value":"2023-10-01-preview"}},{"2":{"Name":"Applications.Core/containers","Properties":{"id":{"Type":4,"Flags":10,"Description":"The resource id"},"name":{"Type":4,"Flags":9,"Description":"The resource name"},"type":{"Type":59,"Flags":10,"Description":"The resource type"},"apiVersion":{"Type":60,"Flags":10,"Description":"The resource api version"},"properties":{"Type":62,"Flags":1,"Description":"Container properties"},"tags":{"Type":122,"Flags":0,"Description":"Resource tags."},"location":{"Type":4,"Flags":1,"Description":"The geo-location where the resource lives"},"systemData":{"Type":47,"Flags":2,"Description":"Metadata pertaining to creation and last modification of the resource."}}}},{"2":{"Name":"ContainerProperties","Properties":{"environment":{"Type":4,"Flags":0,"Description":"Fully qualified resource ID for the environment that the application is linked to"},"application":{"Type":4,"Flags":1,"Description":"Fully qualified resource ID for the application"},"provisioningState":{"Type":70,"Flags":2,"Description":"Provisioning state of the resource at the time the operation was called"},"status":{"Type":35,"Flags":2,"Description":"Status of a resource."},"container":{"Type":71,"Flags":1,"Description":"Definition of a container"},"connections":{"Type":108,"Flags":0,"Description":"Specifies a connection to another resource."},"identity":{"Type":37,"Flags":0,"Description":"IdentitySettings is the external identity setting."},"extensions":{"Type":109,"Flags":0,"Description":"Extensions spec of the resource"},"resourceProvisioning":{"Type":112,"Flags":0,"Description":"Specifies how the underlying service/resource is provisioned and managed. Available values are 'internal', where Radius manages the lifecycle of the resource internally, and 'manual', where a user manages the resource."},"resources":{"Type":114,"Flags":0,"Description":"A collection of references to resources associated with the container"},"restartPolicy":{"Type":118,"Flags":0,"Description":"Restart policy for the container"},"runtimes":{"Type":119,"Flags":0,"Description":"The properties for runtime configuration"}}}},{"6":{"Value":"Succeeded"}},{"6":{"Value":"Failed"}},{"6":{"Value":"Canceled"}},{"6":{"Value":"Provisioning"}},{"6":{"Value":"Updating"}},{"6":{"Value":"Deleting"}},{"6":{"Value":"Accepted"}},{"5":{"Elements":[63,64,65,66,67,68,69]}},{"2":{"Name":"Container","Properties":{"image":{"Type":4,"Flags":1,"Description":"The registry and image to download and run in your container"},"imagePullPolicy":{"Type":75,"Flags":0,"Description":"The image pull policy for the container"},"env":{"Type":76,"Flags":0,"Description":"environment"},"ports":{"Type":81,"Flags":0,"Description":"container ports"},"readinessProbe":{"Type":82,"Flags":0,"Description":"Properties for readiness/liveness probe"},"livenessProbe":{"Type":82,"Flags":0,"Description":"Properties for readiness/liveness probe"},"volumes":{"Type":101,"Flags":0,"Description":"container volumes"},"command":{"Type":102,"Flags":0,"Description":"Entrypoint array. Overrides the container image's ENTRYPOINT"},"args":{"Type":103,"Flags":0,"Description":"Arguments to the entrypoint. Overrides the container image's CMD"},"workingDir":{"Type":4,"Flags":0,"Description":"Working directory for the container"}}}},{"6":{"Value":"Always"}},{"6":{"Value":"IfNotPresent"}},{"6":{"Value":"Never"}},{"5":{"Elements":[72,73,74]}},{"2":{"Name":"ContainerEnv","Properties":{},"AdditionalProperties":4}},{"2":{"Name":"ContainerPortProperties","Properties":{"containerPort":{"Type":3,"Flags":1,"Description":"The listening port number"},"protocol":{"Type":80,"Flags":0,"Description":"The protocol in use by the port"},"scheme":{"Type":4,"Flags":0,"Description":"Specifies the URL scheme of the communication protocol. Consumers can use the scheme to construct a URL. The value defaults to 'http' or 'https' depending on the port value"},"port":{"Type":3,"Flags":0,"Description":"Specifies the port that will be exposed by this container. Must be set when value different from containerPort is desired"}}}},{"6":{"Value":"TCP"}},{"6":{"Value":"UDP"}},{"5":{"Elements":[78,79]}},{"2":{"Name":"ContainerPorts","Properties":{},"AdditionalProperties":77}},{"7":{"Name":"HealthProbeProperties","Discriminator":"kind","BaseProperties":{"initialDelaySeconds":{"Type":3,"Flags":0,"Description":"Initial delay in seconds before probing for readiness/liveness"},"failureThreshold":{"Type":3,"Flags":0,"Description":"Threshold number of times the probe fails after which a failure would be reported"},"periodSeconds":{"Type":3,"Flags":0,"Description":"Interval for the readiness/liveness probe in seconds"},"timeoutSeconds":{"Type":3,"Flags":0,"Description":"Number of seconds after which the readiness/liveness probe times out. Defaults to 5 seconds"}},"Elements":{"exec":83,"httpGet":85,"tcp":88}}},{"2":{"Name":"ExecHealthProbeProperties","Properties":{"command":{"Type":4,"Flags":1,"Description":"Command to execute to probe readiness/liveness"},"kind":{"Type":84,"Flags":1,"Description":"Discriminator property for HealthProbeProperties."}}}},{"6":{"Value":"exec"}},{"2":{"Name":"HttpGetHealthProbeProperties","Properties":{"containerPort":{"Type":3,"Flags":1,"Description":"The listening port number"},"path":{"Type":4,"Flags":1,"Description":"The route to make the HTTP request on"},"headers":{"Type":86,"Flags":0,"Description":"Custom HTTP headers to add to the get request"},"kind":{"Type":87,"Flags":1,"Description":"Discriminator property for HealthProbeProperties."}}}},{"2":{"Name":"HttpGetHealthProbePropertiesHeaders","Properties":{},"AdditionalProperties":4}},{"6":{"Value":"httpGet"}},{"2":{"Name":"TcpHealthProbeProperties","Properties":{"containerPort":{"Type":3,"Flags":1,"Description":"The listening port number"},"kind":{"Type":89,"Flags":1,"Description":"Discriminator property for HealthProbeProperties."}}}},{"6":{"Value":"tcp"}},{"7":{"Name":"Volume","Discriminator":"kind","BaseProperties":{"mountPath":{"Type":4,"Flags":0,"Description":"The path where the volume is mounted"}},"Elements":{"ephemeral":91,"persistent":96}}},{"2":{"Name":"EphemeralVolume","Properties":{"managedStore":{"Type":94,"Flags":1,"Description":"The managed store for the ephemeral volume"},"kind":{"Type":95,"Flags":1,"Description":"Discriminator property for Volume."}}}},{"6":{"Value":"memory"}},{"6":{"Value":"disk"}},{"5":{"Elements":[92,93]}},{"6":{"Value":"ephemeral"}},{"2":{"Name":"PersistentVolume","Properties":{"permission":{"Type":99,"Flags":0,"Description":"The persistent volume permission"},"source":{"Type":4,"Flags":1,"Description":"The source of the volume"},"kind":{"Type":100,"Flags":1,"Description":"Discriminator property for Volume."}}}},{"6":{"Value":"read"}},{"6":{"Value":"write"}},{"5":{"Elements":[97,98]}},{"6":{"Value":"persistent"}},{"2":{"Name":"ContainerVolumes","Properties":{},"AdditionalProperties":90}},{"3":{"ItemType":4}},{"3":{"ItemType":4}},{"2":{"Name":"ConnectionProperties","Properties":{"source":{"Type":4,"Flags":1,"Description":"The source of the connection"},"disableDefaultEnvVars":{"Type":2,"Flags":0,"Description":"default environment variable override"},"iam":{"Type":105,"Flags":0,"Description":"IAM properties"}}}},{"2":{"Name":"IamProperties","Properties":{"kind":{"Type":106,"Flags":1,"Description":"The kind of IAM provider to configure"},"roles":{"Type":107,"Flags":0,"Description":"RBAC permissions to be assigned on the source resource"}}}},{"6":{"Value":"azure"}},{"3":{"ItemType":4}},{"2":{"Name":"ContainerPropertiesConnections","Properties":{},"AdditionalProperties":104}},{"3":{"ItemType":20}},{"6":{"Value":"internal"}},{"6":{"Value":"manual"}},{"5":{"Elements":[110,111]}},{"2":{"Name":"ResourceReference","Properties":{"id":{"Type":4,"Flags":1,"Description":"Resource id of an existing resource"}}}},{"3":{"ItemType":113}},{"6":{"Value":"Always"}},{"6":{"Value":"OnFailure"}},{"6":{"Value":"Never"}},{"5":{"Elements":[115,116,117]}},{"2":{"Name":"RuntimesProperties","Properties":{"kubernetes":{"Type":120,"Flags":0,"Description":"The runtime configuration properties for Kubernetes"}}}},{"2":{"Name":"KubernetesRuntimeProperties","Properties":{"base":{"Type":4,"Flags":0,"Description":"The serialized YAML manifest which represents the base Kubernetes resources to deploy, such as Deployment, Service, ServiceAccount, Secrets, and ConfigMaps."},"pod":{"Type":121,"Flags":0,"Description":"A strategic merge patch that will be applied to the PodSpec object when this container is being deployed."}}}},{"2":{"Name":"KubernetesPodSpec","Properties":{},"AdditionalProperties":0}},{"2":{"Name":"TrackedResourceTags","Properties":{},"AdditionalProperties":4}},{"4":{"Name":"Applications.Core/containers@2023-10-01-preview","ScopeType":0,"Body":61}},{"6":{"Value":"Applications.Core/environments"}},{"6":{"Value":"2023-10-01-preview"}},{"2":{"Name":"Applications.Core/environments","Properties":{"id":{"Type":4,"Flags":10,"Description":"The resource id"},"name":{"Type":4,"Flags":9,"Description":"The resource name"},"type":{"Type":124,"Flags":10,"Description":"The resource type"},"apiVersion":{"Type":125,"Flags":10,"Description":"The resource api version"},"properties":{"Type":127,"Flags":1,"Description":"Environment properties"},"tags":{"Type":160,"Flags":0,"Description":"Resource tags."},"location":{"Type":4,"Flags":1,"Description":"The geo-location where the resource lives"},"systemData":{"Type":47,"Flags":2,"Description":"Metadata pertaining to creation and last modification of the resource."}}}},{"2":{"Name":"EnvironmentProperties","Properties":{"provisioningState":{"Type":135,"Flags":2,"Description":"Provisioning state of the resource at the time the operation was called"},"compute":{"Type":36,"Flags":1,"Description":"Represents backing compute resource"},"providers":{"Type":136,"Flags":0,"Description":"The Cloud providers configuration."},"simulated":{"Type":2,"Flags":0,"Description":"Simulated environment."},"recipes":{"Type":145,"Flags":0,"Description":"Specifies Recipes linked to the Environment."},"recipeConfig":{"Type":146,"Flags":0,"Description":"Configuration for Recipes. Defines how each type of Recipe should be configured and run."},"extensions":{"Type":159,"Flags":0,"Description":"The environment extension."}}}},{"6":{"Value":"Succeeded"}},{"6":{"Value":"Failed"}},{"6":{"Value":"Canceled"}},{"6":{"Value":"Provisioning"}},{"6":{"Value":"Updating"}},{"6":{"Value":"Deleting"}},{"6":{"Value":"Accepted"}},{"5":{"Elements":[128,129,130,131,132,133,134]}},{"2":{"Name":"Providers","Properties":{"azure":{"Type":137,"Flags":0,"Description":"The Azure cloud provider definition."},"aws":{"Type":138,"Flags":0,"Description":"The AWS cloud provider definition."}}}},{"2":{"Name":"ProvidersAzure","Properties":{"scope":{"Type":4,"Flags":1,"Description":"Target scope for Azure resources to be deployed into. For example: '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup'."}}}},{"2":{"Name":"ProvidersAws","Properties":{"scope":{"Type":4,"Flags":1,"Description":"Target scope for AWS resources to be deployed into. For example: '/planes/aws/aws/accounts/000000000000/regions/us-west-2'."}}}},{"7":{"Name":"RecipeProperties","Discriminator":"templateKind","BaseProperties":{"templatePath":{"Type":4,"Flags":1,"Description":"Path to the template provided by the recipe. Currently only link to Azure Container Registry is supported."},"parameters":{"Type":0,"Flags":0,"Description":"Any object"}},"Elements":{"bicep":140,"terraform":142}}},{"2":{"Name":"BicepRecipeProperties","Properties":{"plainHttp":{"Type":2,"Flags":0,"Description":"Connect to the Bicep registry using HTTP (not-HTTPS). This should be used when the registry is known not to support HTTPS, for example in a locally-hosted registry. Defaults to false (use HTTPS/TLS)."},"templateKind":{"Type":141,"Flags":1,"Description":"Discriminator property for RecipeProperties."}}}},{"6":{"Value":"bicep"}},{"2":{"Name":"TerraformRecipeProperties","Properties":{"templateVersion":{"Type":4,"Flags":0,"Description":"Version of the template to deploy. For Terraform recipes using a module registry this is required, but must be omitted for other module sources."},"templateKind":{"Type":143,"Flags":1,"Description":"Discriminator property for RecipeProperties."}}}},{"6":{"Value":"terraform"}},{"2":{"Name":"DictionaryOfRecipeProperties","Properties":{},"AdditionalProperties":139}},{"2":{"Name":"EnvironmentPropertiesRecipes","Properties":{},"AdditionalProperties":144}},{"2":{"Name":"RecipeConfigProperties","Properties":{"terraform":{"Type":147,"Flags":0,"Description":"Configuration for Terraform Recipes. Controls how Terraform plans and applies templates as part of Recipe deployment."},"env":{"Type":157,"Flags":0,"Description":"The environment variables injected during Terraform Recipe execution for the recipes in the environment."},"envSecrets":{"Type":158,"Flags":0,"Description":"Environment variables containing sensitive information can be stored as secrets. The secrets are stored in Applications.Core/SecretStores resource."}}}},{"2":{"Name":"TerraformConfigProperties","Properties":{"authentication":{"Type":148,"Flags":0,"Description":"Authentication information used to access private Terraform module sources. Supported module sources: Git."},"providers":{"Type":156,"Flags":0,"Description":"Configuration for Terraform Recipe Providers. Controls how Terraform interacts with cloud providers, SaaS providers, and other APIs. For more information, please see: https://developer.hashicorp.com/terraform/language/providers/configuration."}}}},{"2":{"Name":"AuthConfig","Properties":{"git":{"Type":149,"Flags":0,"Description":"Authentication information used to access private Terraform modules from Git repository sources."}}}},{"2":{"Name":"GitAuthConfig","Properties":{"pat":{"Type":151,"Flags":0,"Description":"Personal Access Token (PAT) configuration used to authenticate to Git platforms."}}}},{"2":{"Name":"SecretConfig","Properties":{"secret":{"Type":4,"Flags":0,"Description":"The ID of an Applications.Core/SecretStore resource containing the Git platform personal access token (PAT). The secret store must have a secret named 'pat', containing the PAT value. A secret named 'username' is optional, containing the username associated with the pat. By default no username is specified."}}}},{"2":{"Name":"GitAuthConfigPat","Properties":{},"AdditionalProperties":150}},{"2":{"Name":"ProviderConfigProperties","Properties":{"secrets":{"Type":154,"Flags":0,"Description":"Sensitive data in provider configuration can be stored as secrets. The secrets are stored in Applications.Core/SecretStores resource."}},"AdditionalProperties":0}},{"2":{"Name":"SecretReference","Properties":{"source":{"Type":4,"Flags":1,"Description":"The ID of an Applications.Core/SecretStore resource containing sensitive data required for recipe execution."},"key":{"Type":4,"Flags":1,"Description":"The key for the secret in the secret store."}}}},{"2":{"Name":"ProviderConfigPropertiesSecrets","Properties":{},"AdditionalProperties":153}},{"3":{"ItemType":152}},{"2":{"Name":"TerraformConfigPropertiesProviders","Properties":{},"AdditionalProperties":155}},{"2":{"Name":"EnvironmentVariables","Properties":{},"AdditionalProperties":4}},{"2":{"Name":"RecipeConfigPropertiesEnvSecrets","Properties":{},"AdditionalProperties":153}},{"3":{"ItemType":20}},{"2":{"Name":"TrackedResourceTags","Properties":{},"AdditionalProperties":4}},{"4":{"Name":"Applications.Core/environments@2023-10-01-preview","ScopeType":0,"Body":126}},{"6":{"Value":"Applications.Core/extenders"}},{"6":{"Value":"2023-10-01-preview"}},{"2":{"Name":"Applications.Core/extenders","Properties":{"id":{"Type":4,"Flags":10,"Description":"The resource id"},"name":{"Type":4,"Flags":9,"Description":"The resource name"},"type":{"Type":162,"Flags":10,"Description":"The resource type"},"apiVersion":{"Type":163,"Flags":10,"Description":"The resource api version"},"properties":{"Type":165,"Flags":1,"Description":"ExtenderResource portable resource properties"},"tags":{"Type":178,"Flags":0,"Description":"Resource tags."},"location":{"Type":4,"Flags":1,"Description":"The geo-location where the resource lives"},"systemData":{"Type":47,"Flags":2,"Description":"Metadata pertaining to creation and last modification of the resource."}}}},{"2":{"Name":"ExtenderProperties","Properties":{"environment":{"Type":4,"Flags":1,"Description":"Fully qualified resource ID for the environment that the portable resource is linked to"},"application":{"Type":4,"Flags":0,"Description":"Fully qualified resource ID for the application that the portable resource is consumed by (if applicable)"},"provisioningState":{"Type":173,"Flags":2,"Description":"Provisioning state of the resource at the time the operation was called"},"status":{"Type":35,"Flags":2,"Description":"Status of a resource."},"secrets":{"Type":0,"Flags":0,"Description":"Any object"},"recipe":{"Type":174,"Flags":0,"Description":"The recipe used to automatically deploy underlying infrastructure for a portable resource"},"resourceProvisioning":{"Type":177,"Flags":0,"Description":"Specifies how the underlying service/resource is provisioned and managed. Available values are 'recipe', where Radius manages the lifecycle of the resource through a Recipe, and 'manual', where a user manages the resource and provides the values."}},"AdditionalProperties":0}},{"6":{"Value":"Succeeded"}},{"6":{"Value":"Failed"}},{"6":{"Value":"Canceled"}},{"6":{"Value":"Provisioning"}},{"6":{"Value":"Updating"}},{"6":{"Value":"Deleting"}},{"6":{"Value":"Accepted"}},{"5":{"Elements":[166,167,168,169,170,171,172]}},{"2":{"Name":"Recipe","Properties":{"name":{"Type":4,"Flags":1,"Description":"The name of the recipe within the environment to use"},"parameters":{"Type":0,"Flags":0,"Description":"Any object"}}}},{"6":{"Value":"recipe"}},{"6":{"Value":"manual"}},{"5":{"Elements":[175,176]}},{"2":{"Name":"TrackedResourceTags","Properties":{},"AdditionalProperties":4}},{"4":{"Name":"Applications.Core/extenders@2023-10-01-preview","ScopeType":0,"Body":164}},{"6":{"Value":"Applications.Core/gateways"}},{"6":{"Value":"2023-10-01-preview"}},{"2":{"Name":"Applications.Core/gateways","Properties":{"id":{"Type":4,"Flags":10,"Description":"The resource id"},"name":{"Type":4,"Flags":9,"Description":"The resource name"},"type":{"Type":180,"Flags":10,"Description":"The resource type"},"apiVersion":{"Type":181,"Flags":10,"Description":"The resource api version"},"properties":{"Type":183,"Flags":1,"Description":"Gateway properties"},"tags":{"Type":199,"Flags":0,"Description":"Resource tags."},"location":{"Type":4,"Flags":1,"Description":"The geo-location where the resource lives"},"systemData":{"Type":47,"Flags":2,"Description":"Metadata pertaining to creation and last modification of the resource."}}}},{"2":{"Name":"GatewayProperties","Properties":{"environment":{"Type":4,"Flags":0,"Description":"Fully qualified resource ID for the environment that the application is linked to"},"application":{"Type":4,"Flags":1,"Description":"Fully qualified resource ID for the application"},"provisioningState":{"Type":191,"Flags":2,"Description":"Provisioning state of the resource at the time the operation was called"},"status":{"Type":35,"Flags":2,"Description":"Status of a resource."},"internal":{"Type":2,"Flags":0,"Description":"Sets Gateway to not be exposed externally (no public IP address associated). Defaults to false (exposed to internet)."},"hostname":{"Type":192,"Flags":0,"Description":"Declare hostname information for the Gateway. Leaving the hostname empty auto-assigns one: mygateway.myapp.PUBLICHOSTNAMEORIP.nip.io."},"routes":{"Type":194,"Flags":1,"Description":"Routes attached to this Gateway"},"tls":{"Type":195,"Flags":0,"Description":"TLS configuration definition for Gateway resource."},"url":{"Type":4,"Flags":2,"Description":"URL of the gateway resource. Readonly"}}}},{"6":{"Value":"Succeeded"}},{"6":{"Value":"Failed"}},{"6":{"Value":"Canceled"}},{"6":{"Value":"Provisioning"}},{"6":{"Value":"Updating"}},{"6":{"Value":"Deleting"}},{"6":{"Value":"Accepted"}},{"5":{"Elements":[184,185,186,187,188,189,190]}},{"2":{"Name":"GatewayHostname","Properties":{"prefix":{"Type":4,"Flags":0,"Description":"Specify a prefix for the hostname: myhostname.myapp.PUBLICHOSTNAMEORIP.nip.io. Mutually exclusive with 'fullyQualifiedHostname' and will be overridden if both are defined."},"fullyQualifiedHostname":{"Type":4,"Flags":0,"Description":"Specify a fully-qualified domain name: myapp.mydomain.com. Mutually exclusive with 'prefix' and will take priority if both are defined."}}}},{"2":{"Name":"GatewayRoute","Properties":{"path":{"Type":4,"Flags":0,"Description":"The path to match the incoming request path on. Ex - /myservice."},"destination":{"Type":4,"Flags":0,"Description":"The URL or id of the service to route to. Ex - 'http://myservice'."},"replacePrefix":{"Type":4,"Flags":0,"Description":"Optionally update the prefix when sending the request to the service. Ex - replacePrefix: '/' and path: '/myservice' will transform '/myservice/myroute' to '/myroute'"},"enableWebsockets":{"Type":2,"Flags":0,"Description":"Enables websocket support for the route. Defaults to false."}}}},{"3":{"ItemType":193}},{"2":{"Name":"GatewayTls","Properties":{"sslPassthrough":{"Type":2,"Flags":0,"Description":"If true, gateway lets the https traffic sslPassthrough to the backend servers for decryption."},"minimumProtocolVersion":{"Type":198,"Flags":0,"Description":"Tls Minimum versions for Gateway resource."},"certificateFrom":{"Type":4,"Flags":0,"Description":"The resource id for the secret containing the TLS certificate and key for the gateway."}}}},{"6":{"Value":"1.2"}},{"6":{"Value":"1.3"}},{"5":{"Elements":[196,197]}},{"2":{"Name":"TrackedResourceTags","Properties":{},"AdditionalProperties":4}},{"4":{"Name":"Applications.Core/gateways@2023-10-01-preview","ScopeType":0,"Body":182}},{"6":{"Value":"Applications.Core/secretStores"}},{"6":{"Value":"2023-10-01-preview"}},{"2":{"Name":"Applications.Core/secretStores","Properties":{"id":{"Type":4,"Flags":10,"Description":"The resource id"},"name":{"Type":4,"Flags":9,"Description":"The resource name"},"type":{"Type":201,"Flags":10,"Description":"The resource type"},"apiVersion":{"Type":202,"Flags":10,"Description":"The resource api version"},"properties":{"Type":204,"Flags":1,"Description":"The properties of SecretStore"},"tags":{"Type":222,"Flags":0,"Description":"Resource tags."},"location":{"Type":4,"Flags":1,"Description":"The geo-location where the resource lives"},"systemData":{"Type":47,"Flags":2,"Description":"Metadata pertaining to creation and last modification of the resource."}}}},{"2":{"Name":"SecretStoreProperties","Properties":{"environment":{"Type":4,"Flags":0,"Description":"Fully qualified resource ID for the environment that the application is linked to"},"application":{"Type":4,"Flags":0,"Description":"Fully qualified resource ID for the application"},"provisioningState":{"Type":212,"Flags":2,"Description":"Provisioning state of the resource at the time the operation was called"},"status":{"Type":35,"Flags":2,"Description":"Status of a resource."},"type":{"Type":215,"Flags":0,"Description":"The type of SecretStore data"},"data":{"Type":221,"Flags":1,"Description":"An object to represent key-value type secrets"},"resource":{"Type":4,"Flags":0,"Description":"The resource id of external secret store."}}}},{"6":{"Value":"Succeeded"}},{"6":{"Value":"Failed"}},{"6":{"Value":"Canceled"}},{"6":{"Value":"Provisioning"}},{"6":{"Value":"Updating"}},{"6":{"Value":"Deleting"}},{"6":{"Value":"Accepted"}},{"5":{"Elements":[205,206,207,208,209,210,211]}},{"6":{"Value":"generic"}},{"6":{"Value":"certificate"}},{"5":{"Elements":[213,214]}},{"2":{"Name":"SecretValueProperties","Properties":{"encoding":{"Type":219,"Flags":0,"Description":"The type of SecretValue Encoding"},"value":{"Type":4,"Flags":0,"Description":"The value of secret."},"valueFrom":{"Type":220,"Flags":0,"Description":"The Secret value source properties"}}}},{"6":{"Value":"raw"}},{"6":{"Value":"base64"}},{"5":{"Elements":[217,218]}},{"2":{"Name":"ValueFromProperties","Properties":{"name":{"Type":4,"Flags":1,"Description":"The name of the referenced secret."},"version":{"Type":4,"Flags":0,"Description":"The version of the referenced secret."}}}},{"2":{"Name":"SecretStorePropertiesData","Properties":{},"AdditionalProperties":216}},{"2":{"Name":"TrackedResourceTags","Properties":{},"AdditionalProperties":4}},{"4":{"Name":"Applications.Core/secretStores@2023-10-01-preview","ScopeType":0,"Body":203}},{"6":{"Value":"Applications.Core/volumes"}},{"6":{"Value":"2023-10-01-preview"}},{"2":{"Name":"Applications.Core/volumes","Properties":{"id":{"Type":4,"Flags":10,"Description":"The resource id"},"name":{"Type":4,"Flags":9,"Description":"The resource name"},"type":{"Type":224,"Flags":10,"Description":"The resource type"},"apiVersion":{"Type":225,"Flags":10,"Description":"The resource api version"},"properties":{"Type":227,"Flags":1,"Description":"Volume properties"},"tags":{"Type":259,"Flags":0,"Description":"Resource tags."},"location":{"Type":4,"Flags":1,"Description":"The geo-location where the resource lives"},"systemData":{"Type":47,"Flags":2,"Description":"Metadata pertaining to creation and last modification of the resource."}}}},{"7":{"Name":"VolumeProperties","Discriminator":"kind","BaseProperties":{"environment":{"Type":4,"Flags":0,"Description":"Fully qualified resource ID for the environment that the application is linked to"},"application":{"Type":4,"Flags":1,"Description":"Fully qualified resource ID for the application"},"provisioningState":{"Type":235,"Flags":2,"Description":"Provisioning state of the resource at the time the operation was called"},"status":{"Type":35,"Flags":2,"Description":"Status of a resource."}},"Elements":{"azure.com.keyvault":236}}},{"6":{"Value":"Succeeded"}},{"6":{"Value":"Failed"}},{"6":{"Value":"Canceled"}},{"6":{"Value":"Provisioning"}},{"6":{"Value":"Updating"}},{"6":{"Value":"Deleting"}},{"6":{"Value":"Accepted"}},{"5":{"Elements":[228,229,230,231,232,233,234]}},{"2":{"Name":"AzureKeyVaultVolumeProperties","Properties":{"certificates":{"Type":249,"Flags":0,"Description":"The KeyVault certificates that this volume exposes"},"keys":{"Type":251,"Flags":0,"Description":"The KeyVault keys that this volume exposes"},"resource":{"Type":4,"Flags":1,"Description":"The ID of the keyvault to use for this volume resource"},"secrets":{"Type":257,"Flags":0,"Description":"The KeyVault secrets that this volume exposes"},"kind":{"Type":258,"Flags":1,"Description":"Discriminator property for VolumeProperties."}}}},{"2":{"Name":"CertificateObjectProperties","Properties":{"alias":{"Type":4,"Flags":0,"Description":"File name when written to disk"},"encoding":{"Type":241,"Flags":0,"Description":"Represents secret encodings"},"format":{"Type":244,"Flags":0,"Description":"Represents certificate formats"},"name":{"Type":4,"Flags":1,"Description":"The name of the certificate"},"certType":{"Type":248,"Flags":0,"Description":"Represents certificate types"},"version":{"Type":4,"Flags":0,"Description":"Certificate version"}}}},{"6":{"Value":"utf-8"}},{"6":{"Value":"hex"}},{"6":{"Value":"base64"}},{"5":{"Elements":[238,239,240]}},{"6":{"Value":"pem"}},{"6":{"Value":"pfx"}},{"5":{"Elements":[242,243]}},{"6":{"Value":"certificate"}},{"6":{"Value":"privatekey"}},{"6":{"Value":"publickey"}},{"5":{"Elements":[245,246,247]}},{"2":{"Name":"AzureKeyVaultVolumePropertiesCertificates","Properties":{},"AdditionalProperties":237}},{"2":{"Name":"KeyObjectProperties","Properties":{"alias":{"Type":4,"Flags":0,"Description":"File name when written to disk"},"name":{"Type":4,"Flags":1,"Description":"The name of the key"},"version":{"Type":4,"Flags":0,"Description":"Key version"}}}},{"2":{"Name":"AzureKeyVaultVolumePropertiesKeys","Properties":{},"AdditionalProperties":250}},{"2":{"Name":"SecretObjectProperties","Properties":{"alias":{"Type":4,"Flags":0,"Description":"File name when written to disk"},"encoding":{"Type":256,"Flags":0,"Description":"Represents secret encodings"},"name":{"Type":4,"Flags":1,"Description":"The name of the secret"},"version":{"Type":4,"Flags":0,"Description":"secret version"}}}},{"6":{"Value":"utf-8"}},{"6":{"Value":"hex"}},{"6":{"Value":"base64"}},{"5":{"Elements":[253,254,255]}},{"2":{"Name":"AzureKeyVaultVolumePropertiesSecrets","Properties":{},"AdditionalProperties":252}},{"6":{"Value":"azure.com.keyvault"}},{"2":{"Name":"TrackedResourceTags","Properties":{},"AdditionalProperties":4}},{"4":{"Name":"Applications.Core/volumes@2023-10-01-preview","ScopeType":0,"Body":226}},{"8":{"Name":"listSecrets","ResourceType":"Applications.Core/extenders","ApiVersion":"2023-10-01-preview","Output":0,"Input":0}},{"2":{"Name":"SecretStoreListSecretsResult","Properties":{"type":{"Type":265,"Flags":2,"Description":"The type of SecretStore data"},"data":{"Type":266,"Flags":2,"Description":"An object to represent key-value type secrets"}}}},{"6":{"Value":"generic"}},{"6":{"Value":"certificate"}},{"5":{"Elements":[263,264]}},{"2":{"Name":"SecretStoreListSecretsResultData","Properties":{},"AdditionalProperties":216}},{"8":{"Name":"listSecrets","ResourceType":"Applications.Core/secretStores","ApiVersion":"2023-10-01-preview","Output":262,"Input":0}}] \ No newline at end of file diff --git a/hack/bicep-types-radius/generated/index.json b/hack/bicep-types-radius/generated/index.json index 2cd93b575c..1c82904878 100644 --- a/hack/bicep-types-radius/generated/index.json +++ b/hack/bicep-types-radius/generated/index.json @@ -1 +1 @@ -{"Resources":{"Applications.Core/applications@2023-10-01-preview":{"RelativePath":"applications/applications.core/2023-10-01-preview/types.json","Index":58},"Applications.Core/containers@2023-10-01-preview":{"RelativePath":"applications/applications.core/2023-10-01-preview/types.json","Index":123},"Applications.Core/environments@2023-10-01-preview":{"RelativePath":"applications/applications.core/2023-10-01-preview/types.json","Index":158},"Applications.Core/extenders@2023-10-01-preview":{"RelativePath":"applications/applications.core/2023-10-01-preview/types.json","Index":176},"Applications.Core/gateways@2023-10-01-preview":{"RelativePath":"applications/applications.core/2023-10-01-preview/types.json","Index":197},"Applications.Core/secretStores@2023-10-01-preview":{"RelativePath":"applications/applications.core/2023-10-01-preview/types.json","Index":220},"Applications.Core/volumes@2023-10-01-preview":{"RelativePath":"applications/applications.core/2023-10-01-preview/types.json","Index":257},"Applications.Dapr/pubSubBrokers@2023-10-01-preview":{"RelativePath":"applications/applications.dapr/2023-10-01-preview/types.json","Index":49},"Applications.Dapr/secretStores@2023-10-01-preview":{"RelativePath":"applications/applications.dapr/2023-10-01-preview/types.json","Index":66},"Applications.Dapr/stateStores@2023-10-01-preview":{"RelativePath":"applications/applications.dapr/2023-10-01-preview/types.json","Index":84},"Applications.Datastores/mongoDatabases@2023-10-01-preview":{"RelativePath":"applications/applications.datastores/2023-10-01-preview/types.json","Index":50},"Applications.Datastores/redisCaches@2023-10-01-preview":{"RelativePath":"applications/applications.datastores/2023-10-01-preview/types.json","Index":69},"Applications.Datastores/sqlDatabases@2023-10-01-preview":{"RelativePath":"applications/applications.datastores/2023-10-01-preview/types.json","Index":88},"Applications.Messaging/rabbitMQQueues@2023-10-01-preview":{"RelativePath":"applications/applications.messaging/2023-10-01-preview/types.json","Index":50}},"Functions":{"applications.core/extenders":{"2023-10-01-preview":[{"RelativePath":"applications/applications.core/2023-10-01-preview/types.json","Index":258}]},"applications.core/secretstores":{"2023-10-01-preview":[{"RelativePath":"applications/applications.core/2023-10-01-preview/types.json","Index":264}]},"applications.datastores/mongodatabases":{"2023-10-01-preview":[{"RelativePath":"applications/applications.datastores/2023-10-01-preview/types.json","Index":90}]},"applications.datastores/rediscaches":{"2023-10-01-preview":[{"RelativePath":"applications/applications.datastores/2023-10-01-preview/types.json","Index":92}]},"applications.datastores/sqldatabases":{"2023-10-01-preview":[{"RelativePath":"applications/applications.datastores/2023-10-01-preview/types.json","Index":94}]},"applications.messaging/rabbitmqqueues":{"2023-10-01-preview":[{"RelativePath":"applications/applications.messaging/2023-10-01-preview/types.json","Index":52}]}}} \ No newline at end of file +{"Resources":{"Applications.Core/applications@2023-10-01-preview":{"RelativePath":"applications/applications.core/2023-10-01-preview/types.json","Index":58},"Applications.Core/containers@2023-10-01-preview":{"RelativePath":"applications/applications.core/2023-10-01-preview/types.json","Index":123},"Applications.Core/environments@2023-10-01-preview":{"RelativePath":"applications/applications.core/2023-10-01-preview/types.json","Index":161},"Applications.Core/extenders@2023-10-01-preview":{"RelativePath":"applications/applications.core/2023-10-01-preview/types.json","Index":179},"Applications.Core/gateways@2023-10-01-preview":{"RelativePath":"applications/applications.core/2023-10-01-preview/types.json","Index":200},"Applications.Core/secretStores@2023-10-01-preview":{"RelativePath":"applications/applications.core/2023-10-01-preview/types.json","Index":223},"Applications.Core/volumes@2023-10-01-preview":{"RelativePath":"applications/applications.core/2023-10-01-preview/types.json","Index":260},"Applications.Dapr/pubSubBrokers@2023-10-01-preview":{"RelativePath":"applications/applications.dapr/2023-10-01-preview/types.json","Index":49},"Applications.Dapr/secretStores@2023-10-01-preview":{"RelativePath":"applications/applications.dapr/2023-10-01-preview/types.json","Index":66},"Applications.Dapr/stateStores@2023-10-01-preview":{"RelativePath":"applications/applications.dapr/2023-10-01-preview/types.json","Index":84},"Applications.Datastores/mongoDatabases@2023-10-01-preview":{"RelativePath":"applications/applications.datastores/2023-10-01-preview/types.json","Index":50},"Applications.Datastores/redisCaches@2023-10-01-preview":{"RelativePath":"applications/applications.datastores/2023-10-01-preview/types.json","Index":69},"Applications.Datastores/sqlDatabases@2023-10-01-preview":{"RelativePath":"applications/applications.datastores/2023-10-01-preview/types.json","Index":88},"Applications.Messaging/rabbitMQQueues@2023-10-01-preview":{"RelativePath":"applications/applications.messaging/2023-10-01-preview/types.json","Index":50}},"Functions":{"applications.core/extenders":{"2023-10-01-preview":[{"RelativePath":"applications/applications.core/2023-10-01-preview/types.json","Index":261}]},"applications.core/secretstores":{"2023-10-01-preview":[{"RelativePath":"applications/applications.core/2023-10-01-preview/types.json","Index":267}]},"applications.datastores/mongodatabases":{"2023-10-01-preview":[{"RelativePath":"applications/applications.datastores/2023-10-01-preview/types.json","Index":90}]},"applications.datastores/rediscaches":{"2023-10-01-preview":[{"RelativePath":"applications/applications.datastores/2023-10-01-preview/types.json","Index":92}]},"applications.datastores/sqldatabases":{"2023-10-01-preview":[{"RelativePath":"applications/applications.datastores/2023-10-01-preview/types.json","Index":94}]},"applications.messaging/rabbitmqqueues":{"2023-10-01-preview":[{"RelativePath":"applications/applications.messaging/2023-10-01-preview/types.json","Index":52}]}}} \ No newline at end of file diff --git a/pkg/corerp/api/v20231001preview/environment_conversion.go b/pkg/corerp/api/v20231001preview/environment_conversion.go index 7584f3c8b7..bed5bdc533 100644 --- a/pkg/corerp/api/v20231001preview/environment_conversion.go +++ b/pkg/corerp/api/v20231001preview/environment_conversion.go @@ -208,6 +208,7 @@ func toRecipeConfigDatamodel(config *RecipeConfigProperties) datamodel.RecipeCon } recipeConfig.Env = toRecipeConfigEnvDatamodel(config) + recipeConfig.EnvSecrets = toSecretReferenceDatamodel(config.EnvSecrets) return recipeConfig } @@ -239,6 +240,7 @@ func fromRecipeConfigDatamodel(config datamodel.RecipeConfigProperties) *RecipeC } recipeConfig.Env = fromRecipeConfigEnvDatamodel(config) + recipeConfig.EnvSecrets = fromSecretReferenceDatamodel(config.EnvSecrets) return recipeConfig } @@ -417,9 +419,10 @@ func toRecipeConfigTerraformProvidersDatamodel(config *RecipeConfigProperties) m for k, v := range config.Terraform.Providers { dm[k] = []datamodel.ProviderConfigProperties{} - for _, providerAdditionalProperties := range v { + for _, providerConfigProperties := range v { dm[k] = append(dm[k], datamodel.ProviderConfigProperties{ - AdditionalProperties: providerAdditionalProperties, + AdditionalProperties: providerConfigProperties.AdditionalProperties, + Secrets: toSecretReferenceDatamodel(providerConfigProperties.Secrets), }) } } @@ -427,22 +430,63 @@ func toRecipeConfigTerraformProvidersDatamodel(config *RecipeConfigProperties) m return dm } -func fromRecipeConfigTerraformProvidersDatamodel(config datamodel.RecipeConfigProperties) map[string][]map[string]any { +func fromRecipeConfigTerraformProvidersDatamodel(config datamodel.RecipeConfigProperties) map[string][]*ProviderConfigProperties { if config.Terraform.Providers == nil { return nil } - providers := map[string][]map[string]any{} + providers := map[string][]*ProviderConfigProperties{} for k, v := range config.Terraform.Providers { - providers[k] = []map[string]any{} + providers[k] = []*ProviderConfigProperties{} + for _, provider := range v { - providers[k] = append(providers[k], provider.AdditionalProperties) + providers[k] = append(providers[k], &ProviderConfigProperties{ + AdditionalProperties: provider.AdditionalProperties, + Secrets: fromSecretReferenceDatamodel(provider.Secrets), + }) } } return providers } +func toSecretReferenceDatamodel(configSecrets map[string]*SecretReference) map[string]datamodel.SecretReference { + var secrets map[string]datamodel.SecretReference + + for secretKey, secretValue := range configSecrets { + if secretValue != nil { + secret := datamodel.SecretReference{ + Source: to.String(secretValue.Source), + Key: to.String(secretValue.Key), + } + + if secrets == nil { + secrets = map[string]datamodel.SecretReference{} + } + secrets[secretKey] = secret + } + } + + return secrets +} + +func fromSecretReferenceDatamodel(secrets map[string]datamodel.SecretReference) map[string]*SecretReference { + var converted map[string]*SecretReference + + for secretKey, secretValue := range secrets { + if converted == nil { + converted = map[string]*SecretReference{} + } + + converted[secretKey] = &SecretReference{ + Source: to.Ptr(secretValue.Source), + Key: to.Ptr(secretValue.Key), + } + } + + return converted +} + func toRecipeConfigEnvDatamodel(config *RecipeConfigProperties) datamodel.EnvironmentVariables { if config.Env == nil { return datamodel.EnvironmentVariables{} diff --git a/pkg/corerp/api/v20231001preview/environment_conversion_test.go b/pkg/corerp/api/v20231001preview/environment_conversion_test.go index 08d38a595b..3d7aa32bb8 100644 --- a/pkg/corerp/api/v20231001preview/environment_conversion_test.go +++ b/pkg/corerp/api/v20231001preview/environment_conversion_test.go @@ -142,6 +142,16 @@ func TestConvertVersionedToDataModel(t *testing.T) { Providers: map[string][]datamodel.ProviderConfigProperties{ "azurerm": { { + Secrets: map[string]datamodel.SecretReference{ + "secret1": { + Source: "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore1", + Key: "key1", + }, + "secret2": { + Source: "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore2", + Key: "key2", + }, + }, AdditionalProperties: map[string]any{ "subscriptionId": "00000000-0000-0000-0000-000000000000", }, @@ -154,6 +164,12 @@ func TestConvertVersionedToDataModel(t *testing.T) { "myEnvVar": "myEnvValue", }, }, + EnvSecrets: map[string]datamodel.SecretReference{ + "myEnvSecretVar": { + Source: "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/envSecretStore1", + Key: "envKey1", + }, + }, }, Recipes: map[string]map[string]datamodel.EnvironmentRecipeProperties{ ds_ctrl.MongoDatabasesResourceType: { @@ -356,6 +372,7 @@ func TestConvertVersionedToDataModel(t *testing.T) { } func TestConvertDataModelToVersioned(t *testing.T) { + baseSecretStorePath := "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/" conversionTests := []struct { filename string err error @@ -407,7 +424,7 @@ func TestConvertDataModelToVersioned(t *testing.T) { if tt.filename == "environmentresourcedatamodel.json" { require.Equal(t, "Azure/cosmosdb/azurerm", string(*versioned.Properties.Recipes[ds_ctrl.MongoDatabasesResourceType]["terraform-recipe"].GetRecipeProperties().TemplatePath)) require.Equal(t, recipes.TemplateKindTerraform, string(*versioned.Properties.Recipes[ds_ctrl.MongoDatabasesResourceType]["terraform-recipe"].GetRecipeProperties().TemplateKind)) - require.Equal(t, "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/github", string(*versioned.Properties.RecipeConfig.Terraform.Authentication.Git.Pat["dev.azure.com"].Secret)) + require.Equal(t, baseSecretStorePath+"github", string(*versioned.Properties.RecipeConfig.Terraform.Authentication.Git.Pat["dev.azure.com"].Secret)) switch c := recipeDetails.(type) { case *TerraformRecipeProperties: require.Equal(t, "1.1.0", string(*c.TemplateVersion)) @@ -416,10 +433,22 @@ func TestConvertDataModelToVersioned(t *testing.T) { } require.Equal(t, 1, len(versioned.Properties.RecipeConfig.Terraform.Providers)) require.Equal(t, 1, len(versioned.Properties.RecipeConfig.Terraform.Providers["azurerm"])) - subscriptionId := versioned.Properties.RecipeConfig.Terraform.Providers["azurerm"][0]["subscriptionId"] + subscriptionId := versioned.Properties.RecipeConfig.Terraform.Providers["azurerm"][0].AdditionalProperties["subscriptionId"] require.Equal(t, "00000000-0000-0000-0000-000000000000", subscriptionId) + + providerSecretIDs := versioned.Properties.RecipeConfig.Terraform.Providers["azurerm"][0].Secrets + require.Equal(t, 2, len(providerSecretIDs)) + require.Equal(t, providerSecretIDs["secret1"], to.Ptr(SecretReference{Source: to.Ptr(baseSecretStorePath + "secretstore1"), Key: to.Ptr("key1")})) + require.Equal(t, providerSecretIDs["secret2"], to.Ptr(SecretReference{Source: to.Ptr(baseSecretStorePath + "secretstore2"), Key: to.Ptr("key2")})) + require.Equal(t, 1, len(versioned.Properties.RecipeConfig.Env)) require.Equal(t, to.Ptr("myEnvValue"), versioned.Properties.RecipeConfig.Env["myEnvVar"]) + + envSecretIDs := versioned.Properties.RecipeConfig.EnvSecrets + envSecretRef, ok := envSecretIDs["myEnvSecretVar"] + require.True(t, ok) + require.Equal(t, envSecretRef, to.Ptr(SecretReference{Source: to.Ptr(baseSecretStorePath + "envSecretStore1"), Key: to.Ptr("envKey1")})) + require.Equal(t, 1, len(envSecretIDs)) } if tt.filename == "environmentresourcedatamodelemptyext.json" { @@ -485,7 +514,7 @@ func TestConvertDataModelWithIdentityToVersioned(t *testing.T) { require.Equal(t, "azure.com.workload", string(*versioned.Properties.Compute.GetEnvironmentCompute().Identity.Kind)) require.Equal(t, "/subscriptions/testSub/resourcegroups/testGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/radius-mi-app", string(*versioned.Properties.Compute.GetEnvironmentCompute().Identity.Resource)) require.Equal(t, "https://oidcurl/guid", string(*versioned.Properties.Compute.GetEnvironmentCompute().Identity.OidcIssuer)) - require.Equal(t, map[string][]map[string]any{}, versioned.Properties.RecipeConfig.Terraform.Providers) + require.Equal(t, map[string][]*ProviderConfigProperties{}, versioned.Properties.RecipeConfig.Terraform.Providers) require.Equal(t, map[string]*string{}, versioned.Properties.RecipeConfig.Env) } @@ -575,9 +604,10 @@ func getTestKubernetesEmptyMetadataExtensions() []datamodel.Extension { func Test_toRecipeConfigTerraformProvidersDatamodel(t *testing.T) { tests := []struct { - name string - config *RecipeConfigProperties - want map[string][]datamodel.ProviderConfigProperties + name string + config *RecipeConfigProperties + want map[string][]datamodel.ProviderConfigProperties + expectError bool }{ { name: "Empty Recipe Configuration", @@ -588,10 +618,12 @@ func Test_toRecipeConfigTerraformProvidersDatamodel(t *testing.T) { name: "Single Provider Configuration", config: &RecipeConfigProperties{ Terraform: &TerraformConfigProperties{ - Providers: map[string][]map[string]any{ + Providers: map[string][]*ProviderConfigProperties{ "azurerm": { - { - "subscription_id": "00000000-0000-0000-0000-000000000000", + &ProviderConfigProperties{ + AdditionalProperties: map[string]any{ + "subscription_id": "00000000-0000-0000-0000-000000000000", + }, }, }, }, @@ -611,14 +643,18 @@ func Test_toRecipeConfigTerraformProvidersDatamodel(t *testing.T) { name: "Single Provider With Multiple Configuration", config: &RecipeConfigProperties{ Terraform: &TerraformConfigProperties{ - Providers: map[string][]map[string]any{ + Providers: map[string][]*ProviderConfigProperties{ "azurerm": { { - "subscription_id": "00000000-0000-0000-0000-000000000000", + AdditionalProperties: map[string]any{ + "subscription_id": "00000000-0000-0000-0000-000000000000", + }, }, { - "tenant_id": "00000000-0000-0000-0000-000000000000", - "alias": "az-example-service", + AdditionalProperties: map[string]any{ + "tenant_id": "00000000-0000-0000-0000-000000000000", + "alias": "az-example-service", + }, }, }, }, @@ -644,23 +680,31 @@ func Test_toRecipeConfigTerraformProvidersDatamodel(t *testing.T) { name: "Multiple Providers With Multiple Configurations", config: &RecipeConfigProperties{ Terraform: &TerraformConfigProperties{ - Providers: map[string][]map[string]any{ + Providers: map[string][]*ProviderConfigProperties{ "azurerm": { { - "subscription_id": "00000000-0000-0000-0000-000000000000", + AdditionalProperties: map[string]any{ + "subscription_id": "00000000-0000-0000-0000-000000000000", + }, }, { - "tenant_id": "00000000-0000-0000-0000-000000000000", - "alias": "az-example-service", + AdditionalProperties: map[string]any{ + "tenant_id": "00000000-0000-0000-0000-000000000000", + "alias": "az-example-service", + }, }, }, "aws": { { - "region": "us-west-2", + AdditionalProperties: map[string]any{ + "region": "us-west-2", + }, }, { - "account_id": "140313373712", - "alias": "account-service", + AdditionalProperties: map[string]any{ + "account_id": "140313373712", + "alias": "account-service", + }, }, }, }, @@ -695,6 +739,43 @@ func Test_toRecipeConfigTerraformProvidersDatamodel(t *testing.T) { }, }, }, + { + name: "Provider Configuration With Secret", + config: &RecipeConfigProperties{ + Terraform: &TerraformConfigProperties{ + Providers: map[string][]*ProviderConfigProperties{ + "azurerm": { + &ProviderConfigProperties{ + AdditionalProperties: map[string]any{ + "subscription_id": "00000000-0000-0000-0000-000000000000", + }, + Secrets: map[string]*SecretReference{ + "secret1": { + Source: to.Ptr("/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore1"), + Key: to.Ptr("key1"), + }, + }, + }, + }, + }, + }, + }, + want: map[string][]datamodel.ProviderConfigProperties{ + "azurerm": { + { + AdditionalProperties: map[string]any{ + "subscription_id": "00000000-0000-0000-0000-000000000000", + }, + Secrets: map[string]datamodel.SecretReference{ + "secret1": { + Source: "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore1", + Key: "key1", + }, + }, + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -708,7 +789,7 @@ func Test_fromRecipeConfigTerraformProvidersDatamodel(t *testing.T) { tests := []struct { name string config datamodel.RecipeConfigProperties - want map[string][]map[string]any + want map[string][]*ProviderConfigProperties }{ { name: "Empty Recipe Configuration", @@ -730,10 +811,12 @@ func Test_fromRecipeConfigTerraformProvidersDatamodel(t *testing.T) { }, }, }, - want: map[string][]map[string]any{ + want: map[string][]*ProviderConfigProperties{ "azurerm": { { - "subscription_id": "00000000-0000-0000-0000-000000000000", + AdditionalProperties: map[string]any{ + "subscription_id": "00000000-0000-0000-0000-000000000000", + }, }, }, }, @@ -759,14 +842,18 @@ func Test_fromRecipeConfigTerraformProvidersDatamodel(t *testing.T) { }, }, }, - want: map[string][]map[string]any{ + want: map[string][]*ProviderConfigProperties{ "azurerm": { { - "subscription_id": "00000000-0000-0000-0000-000000000000", + AdditionalProperties: map[string]any{ + "subscription_id": "00000000-0000-0000-0000-000000000000", + }, }, { - "tenant_id": "00000000-0000-0000-0000-000000000000", - "alias": "tenant", + AdditionalProperties: map[string]any{ + "tenant_id": "00000000-0000-0000-0000-000000000000", + "alias": "tenant", + }, }, }, }, @@ -799,19 +886,25 @@ func Test_fromRecipeConfigTerraformProvidersDatamodel(t *testing.T) { }, }, }, - want: map[string][]map[string]any{ + want: map[string][]*ProviderConfigProperties{ "azurerm": { { - "subscription_id": "00000000-0000-0000-0000-000000000000", + AdditionalProperties: map[string]any{ + "subscription_id": "00000000-0000-0000-0000-000000000000", + }, }, }, "aws": { { - "region": "us-west-2", + AdditionalProperties: map[string]any{ + "region": "us-west-2", + }, }, { - "account_id": "140313373712", - "alias": "account", + AdditionalProperties: map[string]any{ + "account_id": "140313373712", + "alias": "account", + }, }, }, }, @@ -894,3 +987,116 @@ func Test_fromRecipeConfigEnvDatamodel(t *testing.T) { }) } } + +func Test_toSecretReferenceDatamodel(t *testing.T) { + tests := []struct { + name string + configSecrets map[string]*SecretReference + expectedResult map[string]datamodel.SecretReference + }{ + { + name: "Multiple Provider Secrets", + configSecrets: map[string]*SecretReference{ + "secret1": { + Source: to.Ptr("source1"), + Key: to.Ptr("key1"), + }, + "secret2": { + Source: to.Ptr("source2"), + Key: to.Ptr("key2"), + }, + }, + expectedResult: map[string]datamodel.SecretReference{ + "secret1": { + Source: "source1", + Key: "key1", + }, + "secret2": { + Source: "source2", + Key: "key2", + }, + }, + }, + { + name: "Nil Provider Secrets", + configSecrets: nil, + expectedResult: nil, + }, + { + name: "Nil Secret in Provider Properties", + configSecrets: map[string]*SecretReference{ + "secret1": nil, + }, + expectedResult: nil, + }, + { + name: "Nil + Valid Secret in Provider Properties", + configSecrets: map[string]*SecretReference{ + "secret1": nil, + "secret2": { + Source: to.Ptr("source2"), + Key: to.Ptr("key2"), + }, + }, + expectedResult: map[string]datamodel.SecretReference{ + "secret2": { + Source: "source2", + Key: "key2", + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := toSecretReferenceDatamodel(tt.configSecrets) + require.Equal(t, tt.expectedResult, result) + }) + } +} + +func Test_fromSecretReferenceDatamodel(t *testing.T) { + tests := []struct { + name string + secrets map[string]datamodel.SecretReference + expected map[string]*SecretReference + }{ + { + name: "Empty Secret", + secrets: map[string]datamodel.SecretReference{}, + expected: nil, + }, + { + name: "Nil Secret", + secrets: nil, + expected: nil, + }, + { + name: "Single Secret", + secrets: map[string]datamodel.SecretReference{ + "secret1": {Source: "source1", Key: "key1"}, + }, + expected: map[string]*SecretReference{ + "secret1": {Source: to.Ptr("source1"), Key: to.Ptr("key1")}, + }, + }, + { + name: "Multiple Secrets", + secrets: map[string]datamodel.SecretReference{ + "secret1": {Source: "source1", Key: "key1"}, + "secret2": {Source: "source2", Key: "key2"}, + }, + expected: map[string]*SecretReference{ + "secret1": {Source: to.Ptr("source1"), Key: to.Ptr("key1")}, + "secret2": {Source: to.Ptr("source2"), Key: to.Ptr("key2")}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := fromSecretReferenceDatamodel(tt.secrets) + require.Equal(t, tt.expected, result) + }) + } +} diff --git a/pkg/corerp/api/v20231001preview/testdata/environmentresource.json b/pkg/corerp/api/v20231001preview/testdata/environmentresource.json index da72ff00de..8d045112bd 100644 --- a/pkg/corerp/api/v20231001preview/testdata/environmentresource.json +++ b/pkg/corerp/api/v20231001preview/testdata/environmentresource.json @@ -30,13 +30,29 @@ "providers": { "azurerm": [ { - "subscriptionId": "00000000-0000-0000-0000-000000000000" + "subscriptionId": "00000000-0000-0000-0000-000000000000", + "secrets": { + "secret1": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore1", + "key": "key1" + }, + "secret2": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore2", + "key": "key2" + } + } } ] } }, "env": { "myEnvVar": "myEnvValue" + }, + "envSecrets": { + "myEnvSecretVar": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/envSecretStore1", + "key": "envKey1" + } } }, "recipes": { diff --git a/pkg/corerp/api/v20231001preview/testdata/environmentresourcedatamodel.json b/pkg/corerp/api/v20231001preview/testdata/environmentresourcedatamodel.json index aa33e0008a..95adf79b86 100644 --- a/pkg/corerp/api/v20231001preview/testdata/environmentresourcedatamodel.json +++ b/pkg/corerp/api/v20231001preview/testdata/environmentresourcedatamodel.json @@ -45,6 +45,16 @@ { "additionalProperties": { "subscriptionId": "00000000-0000-0000-0000-000000000000" + }, + "secrets": { + "secret1": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore1", + "key": "key1" + }, + "secret2": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore2", + "key": "key2" + } } } ] @@ -54,6 +64,12 @@ "additionalProperties": { "myEnvVar": "myEnvValue" } + }, + "envSecrets": { + "myEnvSecretVar": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/envSecretStore1", + "key": "envKey1" + } } }, "recipes": { diff --git a/pkg/corerp/api/v20231001preview/zz_generated_models.go b/pkg/corerp/api/v20231001preview/zz_generated_models.go index a98b682652..6b494e07bf 100644 --- a/pkg/corerp/api/v20231001preview/zz_generated_models.go +++ b/pkg/corerp/api/v20231001preview/zz_generated_models.go @@ -1248,6 +1248,17 @@ func (p *PersistentVolume) GetVolume() *Volume { } } +// ProviderConfigProperties - This configuration holds the necessary information to authenticate and interact with a provider +// for the recipe execution. +type ProviderConfigProperties struct { + // OPTIONAL; Contains additional key/value pairs not defined in the schema. + AdditionalProperties map[string]any + + // Sensitive data in provider configuration can be stored as secrets. The secrets are stored in Applications.Core/SecretStores +// resource. + Secrets map[string]*SecretReference +} + // Providers - The Cloud providers configuration. type Providers struct { // The AWS cloud provider configuration. @@ -1304,6 +1315,10 @@ type RecipeConfigProperties struct { // Environment variables injected during Terraform Recipe execution for the recipes in the environment. Env map[string]*string + // Environment variables containing sensitive information can be stored as secrets. The secrets are stored in Applications.Core/SecretStores +// resource. + EnvSecrets map[string]*SecretReference + // Configuration for Terraform Recipes. Controls how Terraform plans and applies templates as part of Recipe deployment. Terraform *TerraformConfigProperties } @@ -1450,6 +1465,16 @@ type SecretObjectProperties struct { Version *string } +// SecretReference - This secret is used within a recipe. Secrets are encrypted, often have fine-grained access control, auditing +// and are recommended to be used to hold sensitive data. +type SecretReference struct { + // REQUIRED; The key for the secret in the secret store. + Key *string + + // REQUIRED; The ID of an Applications.Core/SecretStore resource containing sensitive data required for recipe execution. + Source *string +} + // SecretStoreListSecretsResult - The list of secrets type SecretStoreListSecretsResult struct { // REQUIRED; An object to represent key-value type secrets @@ -1617,7 +1642,7 @@ type TerraformConfigProperties struct { // Configuration for Terraform Recipe Providers. Controls how Terraform interacts with cloud providers, SaaS providers, and // other APIs. For more information, please see: // https://developer.hashicorp.com/terraform/language/providers/configuration. - Providers map[string][]map[string]any + Providers map[string][]*ProviderConfigProperties } // TerraformRecipeProperties - Represents Terraform recipe properties. diff --git a/pkg/corerp/api/v20231001preview/zz_generated_models_serde.go b/pkg/corerp/api/v20231001preview/zz_generated_models_serde.go index 1e408af451..4b9af82111 100644 --- a/pkg/corerp/api/v20231001preview/zz_generated_models_serde.go +++ b/pkg/corerp/api/v20231001preview/zz_generated_models_serde.go @@ -2856,6 +2856,48 @@ func (p *PersistentVolume) UnmarshalJSON(data []byte) error { return nil } +// MarshalJSON implements the json.Marshaller interface for type ProviderConfigProperties. +func (p ProviderConfigProperties) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "secrets", p.Secrets) + if p.AdditionalProperties != nil { + for key, val := range p.AdditionalProperties { + objectMap[key] = val + } + } + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type ProviderConfigProperties. +func (p *ProviderConfigProperties) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", p, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "secrets": + err = unpopulate(val, "Secrets", &p.Secrets) + delete(rawMsg, key) + default: + if p.AdditionalProperties == nil { + p.AdditionalProperties = map[string]any{} + } + if val != nil { + var aux any + err = json.Unmarshal(val, &aux) + p.AdditionalProperties[key] = aux + } + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", p, err) + } + } + return nil +} + // MarshalJSON implements the json.Marshaller interface for type Providers. func (p Providers) MarshalJSON() ([]byte, error) { objectMap := make(map[string]any) @@ -3061,6 +3103,7 @@ func (r *Recipe) UnmarshalJSON(data []byte) error { func (r RecipeConfigProperties) MarshalJSON() ([]byte, error) { objectMap := make(map[string]any) populate(objectMap, "env", r.Env) + populate(objectMap, "envSecrets", r.EnvSecrets) populate(objectMap, "terraform", r.Terraform) return json.Marshal(objectMap) } @@ -3077,6 +3120,9 @@ func (r *RecipeConfigProperties) UnmarshalJSON(data []byte) error { case "env": err = unpopulate(val, "Env", &r.Env) delete(rawMsg, key) + case "envSecrets": + err = unpopulate(val, "EnvSecrets", &r.EnvSecrets) + delete(rawMsg, key) case "terraform": err = unpopulate(val, "Terraform", &r.Terraform) delete(rawMsg, key) @@ -3492,6 +3538,37 @@ func (s *SecretObjectProperties) UnmarshalJSON(data []byte) error { return nil } +// MarshalJSON implements the json.Marshaller interface for type SecretReference. +func (s SecretReference) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "key", s.Key) + populate(objectMap, "source", s.Source) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type SecretReference. +func (s *SecretReference) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", s, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "key": + err = unpopulate(val, "Key", &s.Key) + delete(rawMsg, key) + case "source": + err = unpopulate(val, "Source", &s.Source) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", s, err) + } + } + return nil +} + // MarshalJSON implements the json.Marshaller interface for type SecretStoreListSecretsResult. func (s SecretStoreListSecretsResult) MarshalJSON() ([]byte, error) { objectMap := make(map[string]any) diff --git a/swagger/specification/applications/resource-manager/Applications.Core/preview/2023-10-01-preview/examples/Environments_CreateOrUpdate.json b/swagger/specification/applications/resource-manager/Applications.Core/preview/2023-10-01-preview/examples/Environments_CreateOrUpdate.json index 6d7e2eb635..84a9440279 100644 --- a/swagger/specification/applications/resource-manager/Applications.Core/preview/2023-10-01-preview/examples/Environments_CreateOrUpdate.json +++ b/swagger/specification/applications/resource-manager/Applications.Core/preview/2023-10-01-preview/examples/Environments_CreateOrUpdate.json @@ -31,7 +31,17 @@ "providers": { "azurerm": [ { - "subscriptionId": "00000000-0000-0000-0000-000000000000" + "subscriptionId": "00000000-0000-0000-0000-000000000000", + "secrets": { + "secret1": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore1", + "key": "key1" + }, + "secret2": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore2", + "key": "key2" + } + } }, { "tenantId": "00000000-0000-0000-0000-000000000000", @@ -42,6 +52,12 @@ }, "env": { "myEnvVar": "myEnvValue" + }, + "envSecrets": { + "myEnvSecretVar": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/envSecretStore1", + "key": "envKey1" + } } }, "recipes": { diff --git a/swagger/specification/applications/resource-manager/Applications.Core/preview/2023-10-01-preview/examples/Environments_GetEnv0.json b/swagger/specification/applications/resource-manager/Applications.Core/preview/2023-10-01-preview/examples/Environments_GetEnv0.json index 1a73cbecc3..dd4c320eef 100644 --- a/swagger/specification/applications/resource-manager/Applications.Core/preview/2023-10-01-preview/examples/Environments_GetEnv0.json +++ b/swagger/specification/applications/resource-manager/Applications.Core/preview/2023-10-01-preview/examples/Environments_GetEnv0.json @@ -42,13 +42,29 @@ "providers": { "azurerm": [ { - "subscriptionId": "00000000-0000-0000-0000-000000000000" + "subscriptionId": "00000000-0000-0000-0000-000000000000", + "secrets": { + "secret1": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore1", + "key": "key1" + }, + "secret2": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore2", + "key": "key2" + } + } } ] } }, "env": { "myEnvVar": "myEnvValue" + }, + "envSecrets": { + "myEnvSecretVar": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/envSecretStore1", + "key": "envKey1" + } } }, "recipes": { diff --git a/swagger/specification/applications/resource-manager/Applications.Core/preview/2023-10-01-preview/examples/Environments_List.json b/swagger/specification/applications/resource-manager/Applications.Core/preview/2023-10-01-preview/examples/Environments_List.json index 76acf36fbb..f1bb8b2096 100644 --- a/swagger/specification/applications/resource-manager/Applications.Core/preview/2023-10-01-preview/examples/Environments_List.json +++ b/swagger/specification/applications/resource-manager/Applications.Core/preview/2023-10-01-preview/examples/Environments_List.json @@ -43,13 +43,29 @@ "providers": { "azurerm": [ { - "subscriptionId": "00000000-0000-0000-0000-000000000000" + "subscriptionId": "00000000-0000-0000-0000-000000000000", + "secrets": { + "secret1": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore1", + "key": "key1" + }, + "secret2": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore2", + "key": "key2" + } + } } ] } }, "envVariables": { "myEnvVar": "myEnvValue" + }, + "envSecrets": { + "myEnvSecretVar": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/envSecretStore1", + "key": "envKey1" + } } }, "recipes": { diff --git a/swagger/specification/applications/resource-manager/Applications.Core/preview/2023-10-01-preview/examples/Environments_PatchEnv0.json b/swagger/specification/applications/resource-manager/Applications.Core/preview/2023-10-01-preview/examples/Environments_PatchEnv0.json index a4a2a60d30..8f2e4b52bb 100644 --- a/swagger/specification/applications/resource-manager/Applications.Core/preview/2023-10-01-preview/examples/Environments_PatchEnv0.json +++ b/swagger/specification/applications/resource-manager/Applications.Core/preview/2023-10-01-preview/examples/Environments_PatchEnv0.json @@ -68,13 +68,29 @@ "providers": { "azurerm": [ { - "subscriptionId": "00000000-0000-0000-0000-000000000000" + "subscriptionId": "00000000-0000-0000-0000-000000000000", + "secrets": { + "secret1": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore1", + "key": "key1" + }, + "secret2": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore2", + "key": "key2" + } + } } ] } }, "env": { "myEnvVar": "myEnvValue" + }, + "envSecrets": { + "myEnvSecretVar": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/envSecretStore1", + "key": "envKey1" + } } }, "extensions": [ diff --git a/swagger/specification/applications/resource-manager/Applications.Core/preview/2023-10-01-preview/openapi.json b/swagger/specification/applications/resource-manager/Applications.Core/preview/2023-10-01-preview/openapi.json index eb7cc15e50..62141141c6 100644 --- a/swagger/specification/applications/resource-manager/Applications.Core/preview/2023-10-01-preview/openapi.json +++ b/swagger/specification/applications/resource-manager/Applications.Core/preview/2023-10-01-preview/openapi.json @@ -4182,7 +4182,22 @@ "ProviderConfigProperties": { "type": "object", "description": "This configuration holds the necessary information to authenticate and interact with a provider for the recipe execution.", - "additionalProperties": true + "properties": { + "secrets": { + "type": "object", + "description": "Sensitive data in provider configuration can be stored as secrets. The secrets are stored in Applications.Core/SecretStores resource.", + "additionalProperties": { + "$ref": "#/definitions/SecretReference" + } + } + }, + "additionalProperties": true, + "allOf": [ + { + "type": "object", + "additionalProperties": true + } + ] }, "Providers": { "type": "object", @@ -4342,6 +4357,13 @@ "env": { "$ref": "#/definitions/EnvironmentVariables", "description": "Environment variables injected during Terraform Recipe execution for the recipes in the environment." + }, + "envSecrets": { + "type": "object", + "description": "Environment variables containing sensitive information can be stored as secrets. The secrets are stored in Applications.Core/SecretStores resource.", + "additionalProperties": { + "$ref": "#/definitions/SecretReference" + } } } }, @@ -4615,6 +4637,24 @@ "name" ] }, + "SecretReference": { + "type": "object", + "description": "This secret is used within a recipe. Secrets are encrypted, often have fine-grained access control, auditing and are recommended to be used to hold sensitive data.", + "properties": { + "source": { + "type": "string", + "description": "The ID of an Applications.Core/SecretStore resource containing sensitive data required for recipe execution." + }, + "key": { + "type": "string", + "description": "The key for the secret in the secret store." + } + }, + "required": [ + "source", + "key" + ] + }, "SecretStoreDataType": { "type": "string", "description": "The type of SecretStore data", diff --git a/test/functional-portable/corerp/noncloud/resources/recipe_terraform_test.go b/test/functional-portable/corerp/noncloud/resources/recipe_terraform_test.go index 7a6f928fba..d61145fa5e 100644 --- a/test/functional-portable/corerp/noncloud/resources/recipe_terraform_test.go +++ b/test/functional-portable/corerp/noncloud/resources/recipe_terraform_test.go @@ -144,6 +144,8 @@ func Test_TerraformRecipe_KubernetesPostgres(t *testing.T) { appName := "corerp-resources-terraform-pg-app" envName := "corerp-resources-terraform-pg-env" extenderName := "pgs-resources-terraform-pgsapp" + secretName := "pgs-hostsecret" + secretResourceName := appName + "/" + secretName userName := "postgres" password := "abc-123-hgd-@#$'" @@ -172,6 +174,10 @@ func Test_TerraformRecipe_KubernetesPostgres(t *testing.T) { {ID: "/planes/kubernetes/local/namespaces/corerp-resources-terraform-pg-app/providers/core/Service/postgres"}, }, }, + { + Name: secretName, + Type: validation.SecretStoresResource, + }, }, }, K8sObjects: &validation.K8sObjectSet{ @@ -181,6 +187,7 @@ func Test_TerraformRecipe_KubernetesPostgres(t *testing.T) { ValidateLabels(false), validation.NewK8sPodForResource(appName, "postgres"). ValidateLabels(false), + validation.NewK8sSecretForResourceWithResourceName(secretResourceName), }, secretNamespace: { validation.NewK8sSecretForResourceWithResourceName(secretPrefix + secretSuffix). diff --git a/test/functional-portable/corerp/noncloud/resources/testdata/corerp-resources-terraform-postgres.bicep b/test/functional-portable/corerp/noncloud/resources/testdata/corerp-resources-terraform-postgres.bicep index e5572813d1..d50c2a3fed 100644 --- a/test/functional-portable/corerp/noncloud/resources/testdata/corerp-resources-terraform-postgres.bicep +++ b/test/functional-portable/corerp/noncloud/resources/testdata/corerp-resources-terraform-postgres.bicep @@ -20,19 +20,24 @@ resource env 'Applications.Core/environments@2023-10-01-preview' = { namespace: 'corerp-resources-terraform-pg-env' } recipeConfig: { - terraform:{ - providers:{ - postgresql:[{ - alias: 'pgdb-test' - username: userName - password: password - sslmode: 'disable' - }] + terraform: { + providers: { + postgresql: [ { + alias: 'pgdb-test' + username: userName + password: password + sslmode: 'disable' + secrets: { + host: { + source: pgshostsecret.id + key: 'host' + } + } + } ] } } env: { - PGHOST: 'postgres.corerp-resources-terraform-pg-app.svc.cluster.local' - PGPORT: '5432' + PGPORT: '5432' } } recipes: { @@ -68,9 +73,21 @@ resource pgsapp 'Applications.Core/extenders@2023-10-01-preview' = { recipe: { name: 'defaultpostgres' parameters: { - password: password + password: password + } + } + } +} + +resource pgshostsecret 'Applications.Core/secretStores@2023-10-01-preview' = { + name: 'pgs-hostsecret' + properties: { + resource: 'corerp-resources-terraform-pg-app/pgs-hostsecret' + type: 'generic' + data: { + host: { + value: 'postgres.corerp-resources-terraform-pg-app.svc.cluster.local' } } } } - \ No newline at end of file diff --git a/typespec/Applications.Core/environments.tsp b/typespec/Applications.Core/environments.tsp index 3802b7e6ee..e1eea5e636 100644 --- a/typespec/Applications.Core/environments.tsp +++ b/typespec/Applications.Core/environments.tsp @@ -81,6 +81,9 @@ model RecipeConfigProperties { @doc("Environment variables injected during Terraform Recipe execution for the recipes in the environment.") env?: EnvironmentVariables; + + @doc("Environment variables containing sensitive information can be stored as secrets. The secrets are stored in Applications.Core/SecretStores resource.") + envSecrets?: Record; } @doc("Configuration for Terraform Recipes. Controls how Terraform plans and applies templates as part of Recipe deployment.") @@ -110,11 +113,14 @@ model SecretConfig { secret?: string; } -// ProviderConfigProperties allows to get the additional properties. To ensure that `additionalProperties` is true, we need to extend `Record`. +// ProviderConfigProperties allows to get the additional properties and secrets. To ensure that `additionalProperties` is true, we need to extend `Record`. // Reference: https://github.com/Azure/typespec-azure/blob/main/packages/typespec-autorest/test/additional-properties.test.ts #suppress "@azure-tools/typespec-azure-core/bad-record-type" @doc("This configuration holds the necessary information to authenticate and interact with a provider for the recipe execution.") -model ProviderConfigProperties extends Record {} +model ProviderConfigProperties extends Record { + @doc("Sensitive data in provider configuration can be stored as secrets. The secrets are stored in Applications.Core/SecretStores resource.") + secrets?: Record; +} @doc("The environment variables injected during Terraform Recipe execution for the recipes in the environment.") model EnvironmentVariables extends Record {} @@ -168,6 +174,15 @@ model TerraformRecipeProperties extends RecipeProperties { templateVersion?: string; } +@doc("This secret is used within a recipe. Secrets are encrypted, often have fine-grained access control, auditing and are recommended to be used to hold sensitive data.") +model SecretReference { + @doc("The ID of an Applications.Core/SecretStore resource containing sensitive data required for recipe execution.") + source: string; + + @doc("The key for the secret in the secret store.") + key: string; +} + @doc("Represents the request body of the getmetadata action.") model RecipeGetMetadata { @doc("Type of the resource this recipe can be consumed by. For example: 'Applications.Datastores/mongoDatabases'.") diff --git a/typespec/Applications.Core/examples/2023-10-01-preview/Environments_CreateOrUpdate.json b/typespec/Applications.Core/examples/2023-10-01-preview/Environments_CreateOrUpdate.json index 6d7e2eb635..84a9440279 100644 --- a/typespec/Applications.Core/examples/2023-10-01-preview/Environments_CreateOrUpdate.json +++ b/typespec/Applications.Core/examples/2023-10-01-preview/Environments_CreateOrUpdate.json @@ -31,7 +31,17 @@ "providers": { "azurerm": [ { - "subscriptionId": "00000000-0000-0000-0000-000000000000" + "subscriptionId": "00000000-0000-0000-0000-000000000000", + "secrets": { + "secret1": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore1", + "key": "key1" + }, + "secret2": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore2", + "key": "key2" + } + } }, { "tenantId": "00000000-0000-0000-0000-000000000000", @@ -42,6 +52,12 @@ }, "env": { "myEnvVar": "myEnvValue" + }, + "envSecrets": { + "myEnvSecretVar": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/envSecretStore1", + "key": "envKey1" + } } }, "recipes": { diff --git a/typespec/Applications.Core/examples/2023-10-01-preview/Environments_GetEnv0.json b/typespec/Applications.Core/examples/2023-10-01-preview/Environments_GetEnv0.json index 1a73cbecc3..dd4c320eef 100644 --- a/typespec/Applications.Core/examples/2023-10-01-preview/Environments_GetEnv0.json +++ b/typespec/Applications.Core/examples/2023-10-01-preview/Environments_GetEnv0.json @@ -42,13 +42,29 @@ "providers": { "azurerm": [ { - "subscriptionId": "00000000-0000-0000-0000-000000000000" + "subscriptionId": "00000000-0000-0000-0000-000000000000", + "secrets": { + "secret1": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore1", + "key": "key1" + }, + "secret2": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore2", + "key": "key2" + } + } } ] } }, "env": { "myEnvVar": "myEnvValue" + }, + "envSecrets": { + "myEnvSecretVar": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/envSecretStore1", + "key": "envKey1" + } } }, "recipes": { diff --git a/typespec/Applications.Core/examples/2023-10-01-preview/Environments_List.json b/typespec/Applications.Core/examples/2023-10-01-preview/Environments_List.json index 76acf36fbb..f1bb8b2096 100644 --- a/typespec/Applications.Core/examples/2023-10-01-preview/Environments_List.json +++ b/typespec/Applications.Core/examples/2023-10-01-preview/Environments_List.json @@ -43,13 +43,29 @@ "providers": { "azurerm": [ { - "subscriptionId": "00000000-0000-0000-0000-000000000000" + "subscriptionId": "00000000-0000-0000-0000-000000000000", + "secrets": { + "secret1": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore1", + "key": "key1" + }, + "secret2": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore2", + "key": "key2" + } + } } ] } }, "envVariables": { "myEnvVar": "myEnvValue" + }, + "envSecrets": { + "myEnvSecretVar": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/envSecretStore1", + "key": "envKey1" + } } }, "recipes": { diff --git a/typespec/Applications.Core/examples/2023-10-01-preview/Environments_PatchEnv0.json b/typespec/Applications.Core/examples/2023-10-01-preview/Environments_PatchEnv0.json index a4a2a60d30..8f2e4b52bb 100644 --- a/typespec/Applications.Core/examples/2023-10-01-preview/Environments_PatchEnv0.json +++ b/typespec/Applications.Core/examples/2023-10-01-preview/Environments_PatchEnv0.json @@ -68,13 +68,29 @@ "providers": { "azurerm": [ { - "subscriptionId": "00000000-0000-0000-0000-000000000000" + "subscriptionId": "00000000-0000-0000-0000-000000000000", + "secrets": { + "secret1": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore1", + "key": "key1" + }, + "secret2": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/secretstore2", + "key": "key2" + } + } } ] } }, "env": { "myEnvVar": "myEnvValue" + }, + "envSecrets": { + "myEnvSecretVar": { + "source": "/planes/radius/local/resourcegroups/default/providers/Applications.Core/secretStores/envSecretStore1", + "key": "envKey1" + } } }, "extensions": [