Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

containerApp evn var collections #922

Merged
merged 3 commits into from
May 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ Release Notes
=============

## 1.7.1
* Container Apps: Support for collections of env vars, fix ACR credentials linking
* Event Hub: Don't create the `$Default` consumer group explicitly. It will automatically be created by Azure when the resource is created.

## 1.7.0
Expand Down
3 changes: 3 additions & 0 deletions docs/content/api-overview/resources/container-apps.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,11 @@ The Container Apps builder (`containerApp`) is used to define one or more contai
| add_containers | Adds a list of containers to this container app. All containers in the app share resources and scaling. |
| add_simple_container | Adds a single container that references a public docker image and version. |
| add_secret_parameter | Adds an application secret to the entire container app. This is passed as a secure parameter to the template, and an environment variable is automatically created which references the secret. |
| add_secret_parameters | Adds application secrets to the entire container app. This is passed as secure parameters to the template, and environment variables are automatically created which reference the secret. |
| add_secret_expression | As per `add_secret_parameter`, but the value is sourced from an ARM expression instead of as a parameter. Useful for e.g. storage keys etc. |
| add_secret_expressions | As per `add_secret_parameters`, but the values are sourced from an ARM expressions instead of as parameters. Useful for e.g. storage keys etc. |
| add_env_variable | Adds a static, plain text environment variable. |
| add_env_variables | Adds static, plain text environment variables. |

##### Scale Rules

Expand Down
7 changes: 4 additions & 3 deletions src/Farmer/Arm/App.fs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ type ContainerApp =
interface IArmResource with
member this.ResourceId = containerApps.resourceId this.Name
member this.JsonModel =
let usernameSecretName (resourceId:ResourceId) = $"{resourceId.Name.Value}-username"
{| containerApps.Create(this.Name, this.Location, this.dependencies) with
kind = "containerapp"
identity =
Expand All @@ -72,7 +73,7 @@ type ContainerApp =
{| name = cred.Username
value = cred.Password.ArmExpression.Eval() |}
| ImageRegistryAuthentication.ListCredentials resourceId ->
{| name = ArmExpression.create($"listCredentials({resourceId.ArmExpression.Value}, '2019-05-01').username").Eval()
{| name = usernameSecretName resourceId
value = ArmExpression.create($"listCredentials({resourceId.ArmExpression.Value}, '2019-05-01').passwords[0].value").Eval() |}
for setting in this.Secrets do
{| name = setting.Key.Value
Expand All @@ -90,9 +91,9 @@ type ContainerApp =
username = cred.Username
passwordSecretRef = cred.Username |}
| ImageRegistryAuthentication.ListCredentials resourceId ->
{| server = ArmExpression.create($"reference({resourceId.ArmExpression.Value}, '2019-05-01').loginServer").Eval()
{| server = $"{resourceId.Name.Value}.azurecr.io"
username = ArmExpression.create($"listCredentials({resourceId.ArmExpression.Value}, '2019-05-01').username").Eval()
passwordSecretRef = ArmExpression.create($"listCredentials({resourceId.ArmExpression.Value}, '2019-05-01').username").Eval() |}
passwordSecretRef = usernameSecretName resourceId |}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I take it we just need this for now until this bug is fixed, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so, yes. Although the string concatenation approach should just work, once the underlying ARM provider is fixed I would prefer to revert to the original expression-based approach.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, and given we do t have to change the DSL, this will just keep working when they fix the bug. LGTM.

|]
ingress =
match this.IngressMode with
Expand Down
23 changes: 22 additions & 1 deletion src/Farmer/Builders/Builders.ContainerApps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@ type ContainerAppConfig =
ImageRegistryCredentials : ImageRegistryAuthentication list
Containers : ContainerConfig list
Dependencies : Set<ResourceId> }

member this.ResourceId = containerApps.resourceId this.Name
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think line 51 is correct actually, wondering if I should fix that too. I'm thinking it should be managedEnvironments.resourceId.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you are right, it's the managed environment, so this is a lurking bug.

member this.LatestRevisionFqdn =
ArmExpression
.reference(containerApps, this.ResourceId)
.Map(sprintf "%s.latestRevisionFqdn")

type ContainerEnvironmentConfig =
{ Name : ResourceName
InternalLoadBalancerState : FeatureFlag
Expand Down Expand Up @@ -280,6 +285,11 @@ type ContainerAppBuilder () =
EnvironmentVariables = state.EnvironmentVariables.Add (EnvVar.create key.Value key.Value)
}

/// Adds an application secrets to the Azure Container App.
[<CustomOperation "add_secret_parameters">]
member __.AddSecretParameters (state:ContainerAppConfig, keys:#seq<_>) =
keys |> Seq.fold (fun s k -> __.AddSecretParameter(s,k)) state

/// Adds an application secret to the Azure Container App.
[<CustomOperation "add_secret_expression">]
member _.AddSecretExpression (state:ContainerAppConfig, key, expression) =
Expand All @@ -293,13 +303,24 @@ type ContainerAppBuilder () =
| None -> state.Dependencies
}

/// Adds an application secrets to the Azure Container App.
[<CustomOperation "add_secret_expressions">]
member __.AddSecretExpressions (state:ContainerAppConfig, xs: #seq<_>) =
xs |> Seq.fold (fun s (k,e) -> __.AddSecretExpression(s,k,e)) state


/// Adds a public environment variable to the Azure Container App environment variables.
[<CustomOperation "add_env_variable">]
member _.AddEnvironmentVariable (state:ContainerAppConfig, name, value) =
{ state with
EnvironmentVariables = state.EnvironmentVariables.Add (EnvVar.create name value)
}

/// Adds a public environment variables to the Azure Container App environment variables.
[<CustomOperation "add_env_variables">]
member __.AddEnvironmentVariables (state:ContainerAppConfig, vars:#seq<_>) =
vars |> Seq.fold (fun s (k,v) -> __.AddEnvironmentVariable(s,k,v)) state

[<CustomOperation "add_simple_container">]
member this.AddSimpleContainer (state:ContainerAppConfig, dockerImage, dockerVersion) =
let container =
Expand Down
31 changes: 28 additions & 3 deletions src/Tests/ContainerApps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ open Farmer.ContainerApp
open Farmer.Identity

let msi = createUserAssignedIdentity "appUser"
let containerRegistryName = "myregistry"

let fullContainerAppDeployment =
let containerLogs = logAnalytics { name "containerlogs" }
let containerRegistryDomain = "myregistry.azurecr.io"
let containerRegistryUsername = "myregistry"
let containerRegistryDomain = $"{containerRegistryName}.azurecr.io"
let acr = containerRegistry {
name containerRegistryName
}
let version = "1.0.0"
let containerEnv =
containerEnvironment {
Expand All @@ -24,7 +27,7 @@ let fullContainerAppDeployment =
add_identity msi
active_revision_mode Single
add_registry_credentials [
registry containerRegistryDomain containerRegistryUsername
registry containerRegistryDomain containerRegistryName
]
add_containers [
container {
Expand All @@ -43,9 +46,21 @@ let fullContainerAppDeployment =
dapr_app_id "http"
add_http_scale_rule "http-rule" { ConcurrentRequests = 100 }
}
containerApp {
name "multienv"
add_simple_container "mcr.microsoft.com/dotnet/samples" "aspnetapp"
ingress_target_port 80us
ingress_transport Auto
add_http_scale_rule "http-scaler" { ConcurrentRequests = 10 }
add_cpu_scale_rule "cpu-scaler" { Utilisation = 50 }
add_secret_parameters ["servicebusconnectionkey"]
add_env_variables ["ServiceBusQueueName","wishrequests"]
add_secret_expressions ["containerlogs", containerLogs.PrimarySharedKey]
}
containerApp {
name "servicebus"
active_revision_mode Single
reference_registry_credentials [(acr :> IBuilder).ResourceId]
add_containers [
container {
name "servicebus"
Expand Down Expand Up @@ -85,6 +100,12 @@ let tests = testList "Container Apps" [
Expect.isNotNull (jobj.SelectToken("parameters.servicebusconnectionkey")) "Missing 'servicebusconnectionkey' parameter"
Expect.isNotNull (jobj.SelectToken("parameters['myregistry.azurecr.io-password']")) "Missing 'myregistry.azurecr.io-password' parameter"
}
test "Seq container environment parameters" {
let containerApp = fullContainerAppDeployment.Template.Resources |> List.find(fun r -> r.ResourceId.Name.Value = "multienv") :?> Farmer.Arm.App.ContainerApp
containerApp.EnvironmentVariables.["ServiceBusQueueName"] |> ignore
containerApp.EnvironmentVariables.["servicebusconnectionkey"] |> ignore
containerApp.EnvironmentVariables.["containerlogs"] |> ignore
}
test "Full container managed environments" {
let kubeEnv = jobj.SelectToken("resources[?(@.name=='kubecontainerenv')]")
Expect.equal (kubeEnv.["type"] |> string) "Microsoft.App/managedEnvironments" "Incorrect type for kuberenetes environment"
Expand Down Expand Up @@ -133,4 +154,8 @@ let tests = testList "Container Apps" [
Expect.isNonEmpty containerApp.Identity.UserAssigned "Container app did not have identity"
Expect.equal containerApp.Identity.UserAssigned.[0] (UserAssignedIdentity(ResourceId.create(Arm.ManagedIdentity.userAssignedIdentities, ResourceName "appUser"))) "Expected user identity named 'appUser'."
}
test "Linked ACR references correct secret" {
let containerApp = fullContainerAppDeployment.Template.Resources |> List.find(fun r -> r.ResourceId.Name.Value = "servicebus") :?> Farmer.Arm.App.ContainerApp
Expect.isFalse (containerApp.Secrets |> Map.containsKey (ContainerAppValidation.ContainerAppSettingKey.Create $"{containerRegistryName}-username").OkValue) "Container app did not have linked ACR's secret"
}
]