Example of .NET 5 and C# 9
ADD
azure-nextgen-cs-net5-aks-webapp/.gitignore
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/

ADD
azure-nextgen-cs-net5-aks-webapp/Pulumi.yaml
+name: azure-dotnet5
+runtime: dotnet
+description: An example of deploying web apps to App Service and AKS with C# 9 and .NET 5

ADD
azure-nextgen-cs-net5-aks-webapp/README.md
+[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new)
+
+# Deploy Containerized Web Applications using Azure Next Generation Provider, .NET 5, and C# 9
+
+The example demonstrate several Pulumi features:
+
+- Azure NextGen provider
+- Running on .NET 5
+- Using C# 9 constructs like top-level statements, implicit constuctors, and records
+- Defining and using components with Pulumi
+
+## Adjust the code
+
+This example can cover several deployments architectures that are listed below.
+
+### Public Docker Image to Azure App Service
+
+You can deploy any public Docker image that contains a web application listening to port 80 to an Azure App Service. Modify the constructor of `MyStack` class in `Program.cs` file to
+
+```cs
+public MyStack()
+{
+ var app = new WebApplication("hello", new()
+ {
+ DockerImage = "strm/helloworld-http"
+ });
+
+ this.Endpoint = app.Endpoint;
+}
+```
+
+### Custom Application to Azure App Service
+
+Builds a Docker container from the files in `app` folder, push it to Azure Container Registry, and deploy it to an Azure App Service. Modify the constructor of `MyStack` class in `Program.cs` file to
+
+```cs
+public MyStack()
+{
+ var app = new WebApplication("hello", new()
+ {
+ AppFolder = "./app"
+ });
+
+ this.Endpoint = app.Endpoint;
+}
+```
+
+### Public Docker Image to Azure Kubernetes Service
+
+You can deploy any public Docker image that contains a web application listening to port 80 to a new AKS cluster. Modify the constructor of `MyStack` class in `Program.cs` file to
+
+```cs
+public MyStack()
+{
+ var cluster = new AksCluster("demoaks");
+
+ var app = new WebApplication("hello", new()
+ {
+ Cluster = cluster,
+ DockerImage = "strm/helloworld-http"
+ });
+
+ this.Endpoint = app.Endpoint;
+}
+```
+
+### Custom Application to Azure Kubernetes Service
+
+Builds a Docker container from the files in `app` folder, push it to Azure Container Registry, and deploy it to a new AKS cluster. Modify the constructor of `MyStack` class in `Program.cs` file to
+
+```cs
+public MyStack()
+{
+ var cluster = new AksCluster("demoaks");
+
+ var app = new WebApplication("hello", new()
+ {
+ Cluster = cluster,
+ AppFolder = "./app"
+ });
+
+ this.Endpoint = app.Endpoint;
+}
+```
+
+## Deploying the App
+
+To deploy your infrastructure, follow the below steps.
+
+### Prerequisites
+
+1. [Install Pulumi](https://www.pulumi.com/docs/get-started/install/)
+1. [Configure Azure Credentials](https://www.pulumi.com/docs/intro/cloud-providers/azure/setup/)
+
+### Steps
+
+After cloning this repo and making adjustments as described above, from this working directory, run these commands:
+
+1. Create a new stack, which is an isolated deployment target for this example:
+
+ ```bash
+ $ pulumi stack init dev
+ ```
+
+1. Login to Azure CLI (you will be prompted to do this during deployment if you forget this step):
+
+ ```
+ $ az login
+ ```
+
+1. Stand up the application by invoking pulumi
+
+ ```bash
+ $ pulumi up
+ ```
+
+1. Once you've finished experimenting, tear down your stack's resources by destroying and removing it:
+
+ ```bash
+ $ pulumi destroy --yes
+ $ pulumi stack rm --yes
+ ```

ADD
azure-nextgen-cs-net5-aks-webapp/ResourceGroup.cs
+using System;
+using System.Threading;
+using Pulumi;
+using RG = Pulumi.AzureNextGen.Resources.Latest.ResourceGroup;
+
+public static class ResourceGroup
+{
+ public static Output<string> Name => rg.Value.Name;
+
+ public static Output<string> Location => rg.Value.Location;
+
+ private static Lazy<RG> rg = new Lazy<RG>(
+ () => new RG("rg", new()
+ {
+ ResourceGroupName = "rg",
+ Location = "WestUS2",
+ }));
+}

ADD
azure-nextgen-cs-net5-aks-webapp/WebApplication.cs
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Pulumi;
+using Pulumi.AzureNextGen.Authorization.Latest;
+using Pulumi.AzureNextGen.Authorization.Latest.Inputs;
+using Pulumi.AzureNextGen.ContainerRegistry.Latest;
+using Pulumi.AzureNextGen.ContainerRegistry.Latest.Inputs;
+using Pulumi.AzureNextGen.Web.Latest;
+using Pulumi.AzureNextGen.Web.Latest.Inputs;
+using Pulumi.Docker;
+using Pulumi.Kubernetes.Types.Inputs.Apps.V1;
+using Pulumi.Kubernetes.Types.Inputs.Core.V1;
+using Pulumi.Kubernetes.Types.Inputs.Meta.V1;
+using ContainerArgs = Pulumi.Kubernetes.Types.Inputs.Core.V1.ContainerArgs;
+using Service = Pulumi.Kubernetes.Core.V1.Service;
+
+record WebApplicationArgs
+{
+ public string? DockerImage { get; init; }
+ public string? AppFolder { get; init; }
+ public AksCluster? Cluster { get; init; }
+}
+
+class WebApplication : ComponentResource
+{
+ public WebApplication(string name, WebApplicationArgs args) : base("example:my:WebApplication", name)
+ {
+ var (imageName, registry) = EnsureDockerImage(args);
+ this.Endpoint = args.Cluster != null
+ ? DeployToCluster(args.Cluster, imageName, registry)
+ : DeployToAppService(imageName, registry);
+ }
+
+ /// <summary>
+ /// The URL of the web application.
+ /// </summary>
+ [Output] public Output<string> Endpoint { get; set; }
+
+ private (Output<string>, Registry?) EnsureDockerImage(WebApplicationArgs args)
+ {
+ if (args.DockerImage != null)
+ return (Output.Create(args.DockerImage), null);
+
+ if (args.AppFolder == null)
+ throw new ArgumentException("Specify DockerImage or AppFolder");
+
+ var customImage = args.AppFolder.Replace("./", "");
+
+ var registry = new Registry("myregistry", new()
+ {
+ ResourceGroupName = ResourceGroup.Name,
+ RegistryName = "dotnet5mikhailreg",
+ Location = ResourceGroup.Location,
+ Sku = new SkuArgs { Name = "Basic" },
+ AdminUserEnabled = true
+ }, new() { Parent = this });
+
+ var credentials = Output.Tuple(ResourceGroup.Name, registry.Name).Apply(values =>
+ ListRegistryCredentials.InvokeAsync(new ListRegistryCredentialsArgs
+ {
+ ResourceGroupName = values.Item1,
+ RegistryName = values.Item2
+ }));
+ var adminUsername = credentials.Apply(c => c.Username ?? "");
+ var adminPassword = credentials.Apply(c => Output.CreateSecret(c.Passwords.First().Value ?? ""));
+
+ var myImage = new Image(customImage, new()
+ {
+ ImageName = Output.Format($"{registry.LoginServer}/{customImage}:v1.0.0"),
+ Build = new DockerBuild { Context = args.AppFolder },
+ Registry = new ImageRegistry
+ {
+ Server = registry.LoginServer,
+ Username = adminUsername,
+ Password = adminPassword
+ },
+ }, new() { Parent = this });
+
+
+ return (myImage.ImageName, registry);
+ }
+
+ private Output<string> DeployToAppService(Output<string> imageName, Registry? registry)
+ {
+ var plan = new AppServicePlan("asp", new()
+ {
+ ResourceGroupName = ResourceGroup.Name,
+ Location = ResourceGroup.Location,
+ Name = "demo-asp",
+ Kind = "Linux",
+ Reserved = true,
+ Sku = new SkuDescriptionArgs
+ {
+ Name = "B1",
+ Tier = "Basic"
+ }
+ }, new() { Parent = this });
+
+ var appSettings = new List<NameValuePairArgs>
+ {
+ new NameValuePairArgs
+ {
+ Name = "WEBSITES_ENABLE_APP_SERVICE_STORAGE",
+ Value = "false"
+ },
+ new NameValuePairArgs
+ {
+ Name = "WEBSITES_PORT",
+ Value = "80" // Our custom image exposes port 80. Adjust for your app as needed.
+ }
+ };
+
+ if (registry != null)
+ {
+ var credentials = Output.Tuple(ResourceGroup.Name, registry.Name).Apply(values =>
+ ListRegistryCredentials.InvokeAsync(new ListRegistryCredentialsArgs
+ {
+ ResourceGroupName = values.Item1,
+ RegistryName = values.Item2
+ }));
+ var adminUsername = credentials.Apply(c => c.Username ?? "");
+ var adminPassword = credentials.Apply(c => Output.CreateSecret(c.Passwords.First().Value ?? ""));
+
+ appSettings.Add(new NameValuePairArgs { Name = "DOCKER_REGISTRY_SERVER_URL", Value = Output.Format($"https://{registry.LoginServer}") });
+ appSettings.Add(new NameValuePairArgs { Name = "DOCKER_REGISTRY_SERVER_USERNAME", Value = adminUsername });
+ appSettings.Add(new NameValuePairArgs { Name = "DOCKER_REGISTRY_SERVER_PASSWORD", Value = adminPassword });
+ }
+
+ var app = new WebApp("hello-app", new()
+ {
+ ResourceGroupName = ResourceGroup.Name,
+ Location = plan.Location,
+ Name = "demo-app-pulumi",
+ ServerFarmId = plan.Id,
+ SiteConfig = new SiteConfigArgs
+ {
+ AppSettings = appSettings,
+ AlwaysOn = true,
+ LinuxFxVersion = Output.Format($"DOCKER|{imageName}")
+ },
+ HttpsOnly = true,
+ }, new() { Parent = this });
+
+ return Output.Format($"https://{app.DefaultHostName}");
+ }
+
+ private Output<string> DeployToCluster(AksCluster cluster, Output<string> imageName, Registry? registry)
+ {
+ var deploymentDeps = new InputList<Resource>();
+ if (registry != null)
+ {
+ var roleAssignment = new RoleAssignment("access-from-cluster", new()
+ {
+ Properties = new RoleAssignmentPropertiesArgs
+ {
+ PrincipalId = cluster.PrincipalId,
+ RoleDefinitionId = "/subscriptions/0282681f-7a9e-424b-80b2-96babd57a8a1/providers/Microsoft.Authorization/roleDefinitions/7f951dda-4ed3-4680-a7ca-43fe172d538d",
+ },
+ RoleAssignmentName = "7f95abcd-4ed3-4680-a7ca-43fe172d538d",
+ Scope = registry.Id,
+ }, new() { Parent = this });
+ deploymentDeps.Add(roleAssignment);
+ }
+
+ var provider = new Pulumi.Kubernetes.Provider("k8s-provider", new()
+ {
+ KubeConfig = cluster.KubeConfig,
+ }, new() { Parent = this });
+
+ var labels = new InputMap<string> { { "app.kubernetes.io/component", "app" } };
+
+ var appDeployment = new Pulumi.Kubernetes.Apps.V1.Deployment("app", new()
+ {
+ Metadata = new ObjectMetaArgs
+ {
+ Namespace = "default",
+ Labels = labels
+ },
+ Spec = new DeploymentSpecArgs
+ {
+ Replicas = 1,
+ Selector = new LabelSelectorArgs { MatchLabels = labels },
+ Template = new PodTemplateSpecArgs
+ {
+ Metadata = new ObjectMetaArgs { Labels = labels },
+ Spec = new PodSpecArgs
+ {
+ Containers =
+ {
+ new ContainerArgs
+ {
+ Name = "web-app",
+ Image = imageName
+ }
+ }
+ }
+ }
+ }
+ }, new CustomResourceOptions
+ {
+ Provider = provider,
+ Parent = this,
+ DependsOn = deploymentDeps
+ });
+
+ var appService = new Service("app", new()
+ {
+ Metadata = new ObjectMetaArgs
+ {
+ Namespace = "default",
+ Labels = labels
+ },
+ Spec = new ServiceSpecArgs
+ {
+ Type = "LoadBalancer",
+ Ports =
+ {
+ new ServicePortArgs
+ {
+ Port = 80,
+ TargetPort = 80
+ }
+ },
+ Selector = labels
+ }
+ }, new CustomResourceOptions
+ {
+ Provider = provider,
+ Parent = this,
+ DependsOn = { appDeployment }
+ });
+
+ var appAddress = appService.Status.Apply(s => s.LoadBalancer.Ingress).GetAt(0).Apply(i => i.Ip);
+ return Output.Format($"http://{appAddress}");
+ }
+}

ADD
azure-nextgen-cs-net5-aks-webapp/app/.dockerignore
+node_modules
+npm-debug.log
\ No newline at end of file

ADD
azure-nextgen-cs-net5-aks-webapp/app/Dockerfile
+FROM node:8.9.3-alpine
+RUN mkdir -p /usr/src/app
+COPY ./app/* /usr/src/app/
+WORKDIR /usr/src/app
+RUN npm install
+CMD node /usr/src/app/index.js
\ No newline at end of file

ADD
azure-nextgen-cs-net5-aks-webapp/app/app/index.js
+const express = require('express');
+const morgan = require('morgan');
+
+const app = express();
+app.use(morgan('combined'));
+
+app.get('/', (req, res) => {
+ let place = "Kubernetes"; // this toy example assumes Kubernetes by default
+ if (process.env.WEBSITE_HOSTNAME) {
+ place = "Azure App Service";
+ }
+ res.send(`<html><body><h1>Your custom docker image is running in ${place}!</h1></body></html>`);
+});
+
+var listener = app.listen(process.env.PORT || 80, function() {
+ console.log('listening on port ' + listener.address().port);
+});
\ No newline at end of file

ADD
azure-nextgen-cs-net5-aks-webapp/app/app/package.json
+{
+ "name": "node-helloworld",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "dependencies": {
+ "express": "^4.14.0",
+ "morgan": "^1.8.2"
+ },
+ "devDependencies": {},
+ "author": ""
+}

Example of .NET 5 and C# 9 (#833)