From b26e73af0cf9c453c8640ed6a40366ce440c6188 Mon Sep 17 00:00:00 2001 From: Christian Nunciato Date: Thu, 20 Oct 2022 19:39:11 -0700 Subject: [PATCH] Add container-azure-* templates (#401) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add container-azure-* templates * Make settings configurable, drop RandomPet * Tweak resource names * Return JSON in all apps, add comments * Add metadata * Make CPU and memory configurable in the TS template * Use 1 and 2 as defaults for CPU and memory * Make image tag configurable, templatize package.json * Drop emojis from responses 😢 --- container-azure-csharp/${PROJECT}.csproj | 17 + container-azure-csharp/.gitignore | 353 ++++++++++++++++++ container-azure-csharp/Program.cs | 120 ++++++ container-azure-csharp/Pulumi.yaml | 21 ++ container-azure-csharp/app/App.csproj | 8 + container-azure-csharp/app/Dockerfile | 15 + container-azure-csharp/app/Program.cs | 12 + .../app/Properties/launchSettings.json | 28 ++ .../app/appsettings.Development.json | 8 + container-azure-csharp/app/appsettings.json | 9 + container-azure-go/Pulumi.yaml | 21 ++ container-azure-go/app/Dockerfile | 10 + container-azure-go/app/go.mod | 3 + container-azure-go/app/main.go | 34 ++ container-azure-go/go.mod | 84 +++++ container-azure-go/main.go | 155 ++++++++ container-azure-python/.gitignore | 2 + container-azure-python/Pulumi.yaml | 24 ++ container-azure-python/__main__.py | 125 +++++++ container-azure-python/app/Dockerfile | 9 + container-azure-python/app/app.py | 7 + container-azure-python/app/requirements.txt | 1 + container-azure-python/requirements.txt | 4 + container-azure-typescript/.gitignore | 2 + container-azure-typescript/Pulumi.yaml | 22 ++ container-azure-typescript/app/.dockerignore | 2 + container-azure-typescript/app/Dockerfile | 9 + container-azure-typescript/app/index.js | 13 + container-azure-typescript/app/package.json | 11 + container-azure-typescript/index.ts | 110 ++++++ container-azure-typescript/package.json | 12 + container-azure-typescript/tsconfig.json | 18 + metadata/groups/container-service-azure.yaml | 11 + 33 files changed, 1280 insertions(+) create mode 100644 container-azure-csharp/${PROJECT}.csproj create mode 100644 container-azure-csharp/.gitignore create mode 100644 container-azure-csharp/Program.cs create mode 100644 container-azure-csharp/Pulumi.yaml create mode 100644 container-azure-csharp/app/App.csproj create mode 100644 container-azure-csharp/app/Dockerfile create mode 100644 container-azure-csharp/app/Program.cs create mode 100644 container-azure-csharp/app/Properties/launchSettings.json create mode 100644 container-azure-csharp/app/appsettings.Development.json create mode 100644 container-azure-csharp/app/appsettings.json create mode 100644 container-azure-go/Pulumi.yaml create mode 100644 container-azure-go/app/Dockerfile create mode 100644 container-azure-go/app/go.mod create mode 100644 container-azure-go/app/main.go create mode 100644 container-azure-go/go.mod create mode 100644 container-azure-go/main.go create mode 100644 container-azure-python/.gitignore create mode 100644 container-azure-python/Pulumi.yaml create mode 100644 container-azure-python/__main__.py create mode 100644 container-azure-python/app/Dockerfile create mode 100644 container-azure-python/app/app.py create mode 100644 container-azure-python/app/requirements.txt create mode 100644 container-azure-python/requirements.txt create mode 100644 container-azure-typescript/.gitignore create mode 100644 container-azure-typescript/Pulumi.yaml create mode 100644 container-azure-typescript/app/.dockerignore create mode 100644 container-azure-typescript/app/Dockerfile create mode 100644 container-azure-typescript/app/index.js create mode 100644 container-azure-typescript/app/package.json create mode 100644 container-azure-typescript/index.ts create mode 100644 container-azure-typescript/package.json create mode 100644 container-azure-typescript/tsconfig.json create mode 100644 metadata/groups/container-service-azure.yaml diff --git a/container-azure-csharp/${PROJECT}.csproj b/container-azure-csharp/${PROJECT}.csproj new file mode 100644 index 000000000..d2bf051ce --- /dev/null +++ b/container-azure-csharp/${PROJECT}.csproj @@ -0,0 +1,17 @@ + + + Exe + net6.0 + enable + + + app/**;$(DefaultItemExcludes) + + + + + + + + + diff --git a/container-azure-csharp/.gitignore b/container-azure-csharp/.gitignore new file mode 100644 index 000000000..e64527066 --- /dev/null +++ b/container-azure-csharp/.gitignore @@ -0,0 +1,353 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# 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/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ diff --git a/container-azure-csharp/Program.cs b/container-azure-csharp/Program.cs new file mode 100644 index 000000000..acb7c31fa --- /dev/null +++ b/container-azure-csharp/Program.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using Pulumi; +using AzureNative = Pulumi.AzureNative; +using Docker = Pulumi.Docker; +using Random = Pulumi.Random; + +return await Pulumi.Deployment.RunAsync(() => +{ + // Import the program's configuration settings. + var config = new Config(); + var appPath = config.Get("appPath") ?? "./app"; + var imageName = config.Get("imageName") ?? "my-app"; + var imageTag = config.Get("imageTag") ?? "latest"; + var containerPort = config.GetInt32("containerPort") ?? 80; + var cpu = config.GetInt32("cpu") ?? 1; + var memory = config.GetInt32("memory") ?? 2; + + // Create a resource group for the container registry. + var resourceGroup = new AzureNative.Resources.ResourceGroup("resource-group"); + + // Create a container registry. + var registry = new AzureNative.ContainerRegistry.Registry("registry", new() + { + ResourceGroupName = resourceGroup.Name, + AdminUserEnabled = true, + Sku = new AzureNative.ContainerRegistry.Inputs.SkuArgs { + Name = AzureNative.ContainerRegistry.SkuName.Basic, + }, + }); + + // Fetch login credentials for the registry. + var credentials = AzureNative.ContainerRegistry.ListRegistryCredentials.Invoke(new() + { + ResourceGroupName = resourceGroup.Name, + RegistryName = registry.Name, + }); + var registryUsername = credentials.Apply(result => result.Username!); + var registryPassword = credentials.Apply(result => result.Passwords[0]!.Value!); + + // Create a container image for the service. + var image = new Docker.Image("image", new() + { + ImageName = Pulumi.Output.Format($"{registry.LoginServer}/{imageName}:{imageTag}"), + Build = new Docker.DockerBuild { + Context = appPath, + }, + Registry = new Docker.ImageRegistry { + Server = registry.LoginServer, + Username = registryUsername, + Password = registryPassword, + }, + }); + + // Use a random string to give the service a unique DNS name. + var dnsName = new Random.RandomString("dns-name", new() + { + Length = 8, + Special = false, + }).Result.Apply(result => $"{imageName}-{result.ToLower()}"); + + // Create a container group for the service that makes it publicly accessible. + var containerGroup = new AzureNative.ContainerInstance.ContainerGroup("container-group", new() + { + ResourceGroupName = resourceGroup.Name, + OsType = "linux", + RestartPolicy = "always", + ImageRegistryCredentials = new AzureNative.ContainerInstance.Inputs.ImageRegistryCredentialArgs { + Server = registry.LoginServer, + Username = registryUsername, + Password = registryPassword, + }, + Containers = new[] + { + new AzureNative.ContainerInstance.Inputs.ContainerArgs { + Name = imageName, + Image = image.ImageName, + Ports = new[] + { + new AzureNative.ContainerInstance.Inputs.ContainerPortArgs { + Port = containerPort, + Protocol = "tcp", + }, + }, + EnvironmentVariables = new[] + { + new AzureNative.ContainerInstance.Inputs.EnvironmentVariableArgs { + Name = "ASPNETCORE_URLS", + Value = $"http://0.0.0.0:{containerPort}", + }, + }, + Resources = new AzureNative.ContainerInstance.Inputs.ResourceRequirementsArgs { + Requests = new AzureNative.ContainerInstance.Inputs.ResourceRequestsArgs { + Cpu = cpu, + MemoryInGB = memory, + }, + }, + }, + }, + IpAddress = new AzureNative.ContainerInstance.Inputs.IpAddressArgs { + Type = AzureNative.ContainerInstance.ContainerGroupIpAddressType.Public, + DnsNameLabel = dnsName, + Ports = new[] + { + new AzureNative.ContainerInstance.Inputs.PortArgs { + Port = containerPort, + Protocol = "tcp", + }, + }, + } + }); + + // Export the service's IP address, hostname, and fully-qualified URL. + return new Dictionary + { + ["hostname"] = containerGroup.IpAddress.Apply(addr => addr!.Fqdn), + ["ip"] = containerGroup.IpAddress.Apply(addr => addr!.Ip), + ["url"] = containerGroup.IpAddress.Apply(addr => $"http://{addr!.Fqdn}:{containerPort}"), + }; +}); diff --git a/container-azure-csharp/Pulumi.yaml b/container-azure-csharp/Pulumi.yaml new file mode 100644 index 000000000..a4959b38a --- /dev/null +++ b/container-azure-csharp/Pulumi.yaml @@ -0,0 +1,21 @@ +name: ${PROJECT} +description: ${DESCRIPTION} +runtime: dotnet +template: + description: A containerized service on Azure Container Instances + config: + azure-native:location: + description: The Azure region to deploy into + default: WestUS + appPath: + description: The path to the container application to deploy + default: app + containerPort: + description: The port to expose on the container + default: 80 + cpu: + description: The number of CPU cores to allocate on the container + default: 1 + memory: + description: The amount of memory, in GB, to allocate on the container + default: 2 diff --git a/container-azure-csharp/app/App.csproj b/container-azure-csharp/app/App.csproj new file mode 100644 index 000000000..855c69078 --- /dev/null +++ b/container-azure-csharp/app/App.csproj @@ -0,0 +1,8 @@ + + + net6.0 + enable + enable + + + diff --git a/container-azure-csharp/app/Dockerfile b/container-azure-csharp/app/Dockerfile new file mode 100644 index 000000000..f1b8147d2 --- /dev/null +++ b/container-azure-csharp/app/Dockerfile @@ -0,0 +1,15 @@ +FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +WORKDIR /app + +COPY *.csproj ./ +RUN dotnet restore + +COPY . . +RUN dotnet publish -c Release -o out + +FROM mcr.microsoft.com/dotnet/aspnet:6.0 as base +WORKDIR /app + +COPY --from=build /app/out . + +ENTRYPOINT ["dotnet", "App.dll"] diff --git a/container-azure-csharp/app/Program.cs b/container-azure-csharp/app/Program.cs new file mode 100644 index 000000000..24b5f3422 --- /dev/null +++ b/container-azure-csharp/app/Program.cs @@ -0,0 +1,12 @@ +var builder = WebApplication.CreateBuilder(args); +var app = builder.Build(); + +app.MapGet("/", async (context) => +{ + await context.Response.WriteAsJsonAsync(new + { + message = "Hello, world!" + }); +}); + +app.Run(); diff --git a/container-azure-csharp/app/Properties/launchSettings.json b/container-azure-csharp/app/Properties/launchSettings.json new file mode 100644 index 000000000..9911d5af1 --- /dev/null +++ b/container-azure-csharp/app/Properties/launchSettings.json @@ -0,0 +1,28 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:33060", + "sslPort": 44325 + } + }, + "profiles": { + "container_azure": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:7264;http://localhost:5219", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/container-azure-csharp/app/appsettings.Development.json b/container-azure-csharp/app/appsettings.Development.json new file mode 100644 index 000000000..0e7f4af09 --- /dev/null +++ b/container-azure-csharp/app/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/container-azure-csharp/app/appsettings.json b/container-azure-csharp/app/appsettings.json new file mode 100644 index 000000000..a9a1bac2c --- /dev/null +++ b/container-azure-csharp/app/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/container-azure-go/Pulumi.yaml b/container-azure-go/Pulumi.yaml new file mode 100644 index 000000000..a348925f8 --- /dev/null +++ b/container-azure-go/Pulumi.yaml @@ -0,0 +1,21 @@ +name: ${PROJECT} +description: ${DESCRIPTION} +runtime: go +template: + description: A containerized service on Azure Container Instances + config: + azure-native:location: + description: The Azure region to deploy into + default: WestUS + appPath: + description: The path to the container application to deploy + default: app + containerPort: + description: The port to expose on the container + default: 80 + cpu: + description: The number of CPU cores to allocate on the container + default: 1 + memory: + description: The amount of memory, in GB, to allocate on the container + default: 2 diff --git a/container-azure-go/app/Dockerfile b/container-azure-go/app/Dockerfile new file mode 100644 index 000000000..9ed7d590c --- /dev/null +++ b/container-azure-go/app/Dockerfile @@ -0,0 +1,10 @@ +FROM golang +WORKDIR /usr/src/app + +COPY go.* ./ +RUN go mod download + +COPY . . +RUN go build -o /app + +ENTRYPOINT ["/app"] diff --git a/container-azure-go/app/go.mod b/container-azure-go/app/go.mod new file mode 100644 index 000000000..b16f0d7c5 --- /dev/null +++ b/container-azure-go/app/go.mod @@ -0,0 +1,3 @@ +module app + +go 1.18 diff --git a/container-azure-go/app/main.go b/container-azure-go/app/main.go new file mode 100644 index 000000000..586ae4b82 --- /dev/null +++ b/container-azure-go/app/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "os" +) + +type AppResponse struct { + Message string `json:"message"` +} + +func main() { + + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + response := AppResponse{ + Message: "Hello, world!", + } + + body, err := json.Marshal(response) + if err != nil { + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + } + + w.Header().Set("Content-Type", "application/json") + fmt.Fprint(w, string(body)) + }) + + if err := http.ListenAndServe(fmt.Sprintf(":%s", os.Getenv("PORT")), nil); err != nil { + log.Fatal(err) + } +} diff --git a/container-azure-go/go.mod b/container-azure-go/go.mod new file mode 100644 index 000000000..2225672a8 --- /dev/null +++ b/container-azure-go/go.mod @@ -0,0 +1,84 @@ +module azure-container-go + +go 1.17 + +require ( + github.com/pulumi/pulumi-azure-native/sdk v1.74.0 + github.com/pulumi/pulumi-random/sdk v1.7.0 + github.com/pulumi/pulumi-random/sdk/v4 v4.8.2 + github.com/pulumi/pulumi/sdk/v3 v3.42.0 +) + +require ( + github.com/Masterminds/semver v1.5.0 // indirect + github.com/acomagu/bufpipe v1.0.3 // indirect + github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect + github.com/blang/semver v3.5.1+incompatible // indirect + github.com/go-git/gcfg v1.5.0 // indirect + github.com/go-git/go-billy/v5 v5.3.1 // indirect + github.com/go-git/go-git/v5 v5.4.2 // indirect + github.com/gofrs/flock v0.7.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-cmp v0.5.8 // indirect + github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pkg/term v1.1.0 // indirect + github.com/pulumi/pulumi/sdk v1.13.1 // indirect + github.com/spf13/cast v1.3.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/src-d/gcfg v1.4.0 // indirect + github.com/tweekmonster/luser v0.0.0-20161003172636-3fa38070dbd7 // indirect + github.com/xanzy/ssh-agent v0.3.2 // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/tools v0.1.12 // indirect + google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect + gopkg.in/src-d/go-git.v4 v4.13.1 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + lukechampine.com/frand v1.4.2 // indirect +) + +require ( + github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect + github.com/Microsoft/go-winio v0.6.0 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20220930113650-c6815a8c17ad // indirect + github.com/cheggaaa/pb v1.0.29 // indirect + github.com/cloudflare/circl v1.2.0 // indirect + github.com/djherbis/times v1.5.0 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/gofrs/uuid v4.3.0+incompatible // indirect + github.com/golang/glog v1.0.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/imdario/mergo v0.3.13 // indirect + github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect + github.com/opentracing/basictracer-go v1.1.0 // indirect + github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/pulumi/pulumi-docker/sdk/v3 v3.4.1 + github.com/rivo/uniseg v0.4.2 // indirect + github.com/rogpeppe/go-internal v1.9.0 // indirect + github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect + github.com/santhosh-tekuri/jsonschema/v5 v5.0.1 // indirect + github.com/sergi/go-diff v1.2.0 // indirect + github.com/spf13/cobra v1.5.0 // indirect + github.com/texttheater/golang-levenshtein v1.0.1 // indirect + github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect + github.com/uber/jaeger-lib v2.4.1+incompatible // indirect + go.uber.org/atomic v1.10.0 // indirect + golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b // indirect + golang.org/x/net v0.0.0-20221004154528-8021a29435af // indirect + golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 // indirect + golang.org/x/term v0.0.0-20220919170432-7a66f970e087 // indirect + google.golang.org/genproto v0.0.0-20220930163606-c98284e70a91 // indirect + google.golang.org/grpc v1.50.0 // indirect + sourcegraph.com/sourcegraph/appdash v0.0.0-20211028080628-e2786a622600 // indirect +) diff --git a/container-azure-go/main.go b/container-azure-go/main.go new file mode 100644 index 000000000..13d53f7af --- /dev/null +++ b/container-azure-go/main.go @@ -0,0 +1,155 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/pulumi/pulumi-azure-native/sdk/go/azure/containerinstance" + "github.com/pulumi/pulumi-azure-native/sdk/go/azure/containerregistry" + "github.com/pulumi/pulumi-azure-native/sdk/go/azure/resources" + "github.com/pulumi/pulumi-docker/sdk/v3/go/docker" + "github.com/pulumi/pulumi-random/sdk/v4/go/random" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi/config" +) + +func main() { + pulumi.Run(func(ctx *pulumi.Context) error { + + // Import the program's configuration settings. + cfg := config.New(ctx, "") + appPath := "./app" + if param := cfg.Get("appPath"); param != "" { + appPath = param + } + imageName := "my-app" + if param := cfg.Get("imageName"); param != "" { + imageName = param + } + imageTag := "latest" + if param := cfg.Get("imageTag"); param != "" { + imageName = param + } + containerPort := 80 + if param := cfg.GetInt("containerPort"); param != 0 { + containerPort = param + } + cpu := 1 + if param := cfg.GetInt("cpu"); param != 0 { + cpu = param + } + memory := 2 + if param := cfg.GetInt("memory"); param != 0 { + memory = param + } + + // Create a resource group for the container registry. + resourceGroup, err := resources.NewResourceGroup(ctx, "resource-group", nil) + if err != nil { + return err + } + + // Create a container registry. + registry, err := containerregistry.NewRegistry(ctx, "registry", &containerregistry.RegistryArgs{ + ResourceGroupName: resourceGroup.Name, + AdminUserEnabled: pulumi.Bool(true), + Sku: &containerregistry.SkuArgs{ + Name: pulumi.String(containerregistry.SkuNameBasic), + }, + }) + if err != nil { + return err + } + + // Fetch login credentials for the registry. + credentials := containerregistry.ListRegistryCredentialsOutput(ctx, containerregistry.ListRegistryCredentialsOutputArgs{ + ResourceGroupName: resourceGroup.Name, + RegistryName: registry.Name, + }) + registryUsername := credentials.Username().Elem() + registryPassword := credentials.Passwords().Index(pulumi.Int(0)).Value().Elem() + + // Create a container image for the service. + image, err := docker.NewImage(ctx, "image", &docker.ImageArgs{ + ImageName: pulumi.Sprintf("%s/%s:%s", registry.LoginServer, imageName, imageTag), + Build: docker.DockerBuildArgs{ + Context: pulumi.String(appPath), + }, + Registry: docker.ImageRegistryArgs{ + Server: registry.LoginServer, + Username: registryUsername, + Password: registryPassword, + }, + }) + if err != nil { + return err + } + + // Use a random string to give the service a unique DNS name. + dnsNameSuffix, err := random.NewRandomString(ctx, "dns-name-suffix", &random.RandomStringArgs{ + Length: pulumi.Int(8), + Special: pulumi.Bool(false), + }) + if err != nil { + return err + } + dnsName := dnsNameSuffix.Result.ApplyT(func(result string) string { + return fmt.Sprintf("%s-%s", imageName, strings.ToLower(result)) + }).(pulumi.StringOutput) + + // Create a container group for the service that makes it publicly accessible. + containerGroup, err := containerinstance.NewContainerGroup(ctx, "container-group", &containerinstance.ContainerGroupArgs{ + ResourceGroupName: resourceGroup.Name, + OsType: pulumi.String("linux"), + RestartPolicy: pulumi.String("always"), + ImageRegistryCredentials: containerinstance.ImageRegistryCredentialArray{ + containerinstance.ImageRegistryCredentialArgs{ + Server: registry.LoginServer, + Username: registryUsername, + Password: registryPassword, + }, + }, + Containers: containerinstance.ContainerArray{ + containerinstance.ContainerArgs{ + Name: pulumi.String(imageName), + Image: image.ImageName, + Ports: containerinstance.ContainerPortArray{ + containerinstance.ContainerPortArgs{ + Port: pulumi.Int(containerPort), + Protocol: pulumi.String("tcp"), + }, + }, + EnvironmentVariables: containerinstance.EnvironmentVariableArray{ + containerinstance.EnvironmentVariableArgs{ + Name: pulumi.String("PORT"), + Value: pulumi.Sprintf("%d", containerPort), + }, + }, + Resources: containerinstance.ResourceRequirementsArgs{ + Requests: containerinstance.ResourceRequestsArgs{ + Cpu: pulumi.Float64(cpu), + MemoryInGB: pulumi.Float64(memory), + }, + }, + }, + }, + IpAddress: containerinstance.IpAddressArgs{ + Type: pulumi.String("public"), + DnsNameLabel: dnsName, + Ports: containerinstance.PortArray{ + containerinstance.PortArgs{ + Port: pulumi.Int(containerPort), + Protocol: pulumi.String("tcp"), + }, + }, + }, + }) + + // Export the service's IP address, hostname, and fully-qualified URL. + ctx.Export("ip", containerGroup.IpAddress.Elem().Ip()) + ctx.Export("hostname", containerGroup.IpAddress.Elem().Fqdn()) + ctx.Export("url", pulumi.Sprintf("http://%s:%d", containerGroup.IpAddress.Elem().Fqdn(), containerPort)) + + return nil + }) +} diff --git a/container-azure-python/.gitignore b/container-azure-python/.gitignore new file mode 100644 index 000000000..a3807e5bd --- /dev/null +++ b/container-azure-python/.gitignore @@ -0,0 +1,2 @@ +*.pyc +venv/ diff --git a/container-azure-python/Pulumi.yaml b/container-azure-python/Pulumi.yaml new file mode 100644 index 000000000..4352fc81f --- /dev/null +++ b/container-azure-python/Pulumi.yaml @@ -0,0 +1,24 @@ +name: ${PROJECT} +description: ${DESCRIPTION} +runtime: + name: python + options: + virtualenv: venv +template: + description: A containerized service on Azure Container Instances + config: + azure-native:location: + description: The Azure region to deploy into + default: WestUS + appPath: + description: The path to the container application to deploy + default: app + containerPort: + description: The port to expose on the container + default: 80 + cpu: + description: The number of CPU cores to allocate on the container + default: 1 + memory: + description: The amount of memory, in GB, to allocate on the container + default: 2 diff --git a/container-azure-python/__main__.py b/container-azure-python/__main__.py new file mode 100644 index 000000000..7547bfd3f --- /dev/null +++ b/container-azure-python/__main__.py @@ -0,0 +1,125 @@ +import pulumi +import pulumi_docker as docker +import pulumi_random as random +from pulumi_azure_native import resources, containerregistry, containerinstance + +# Import the program's configuration settings. +config = pulumi.Config() +app_path = config.get("appPath", "./app") +image_name = config.get("imageName", "my-app") +image_tag = config.get("imageTag", "latest") +container_port = config.get_int("containerPort", 80) +cpu = config.get_int("cpu", 1) +memory = config.get_int("memory", 2) + +# Create a resource group for the container registry. +resource_group = resources.ResourceGroup("resource-group") + +# Create a container registry. +registry = containerregistry.Registry( + "registry", + containerregistry.RegistryArgs( + resource_group_name=resource_group.name, + admin_user_enabled=True, + sku=containerregistry.SkuArgs( + name=containerregistry.SkuName.BASIC, + ), + ), +) + +# Fetch login credentials for the registry. +credentials = containerregistry.list_registry_credentials_output( + resource_group_name=resource_group.name, + registry_name=registry.name, +) + +registry_username = credentials.apply(lambda creds: creds.username) +registry_password = credentials.apply(lambda creds: creds.passwords[0].value) + +# Create a container image for the service. +image = docker.Image( + "image", + image_name=pulumi.Output.concat(registry.login_server, f"/{image_name}:{image_tag}"), + build=docker.DockerBuild( + context=app_path, + ), + registry=docker.ImageRegistry( + server=registry.login_server, + username=registry_username, + password=registry_password, + ), +) + +# Use a random string to give the service a unique DNS name. +dns_name = random.RandomString( + "dns-name", + random.RandomStringArgs( + length=8, + special=False, + ), +).result.apply(lambda result: f"{image_name}-{result.lower()}") + +# Create a container group for the service that makes it publicly accessible. +container_group = containerinstance.ContainerGroup( + "container-group", + containerinstance.ContainerGroupArgs( + resource_group_name=resource_group.name, + os_type="linux", + restart_policy="always", + image_registry_credentials=[ + containerinstance.ImageRegistryCredentialArgs( + server=registry.login_server, + username=registry_username, + password=registry_password, + ), + ], + containers=[ + containerinstance.ContainerArgs( + name=image_name, + image=image.image_name, + ports=[ + containerinstance.ContainerPortArgs( + port=container_port, + protocol="tcp", + ), + ], + environment_variables=[ + containerinstance.EnvironmentVariableArgs( + name="FLASK_RUN_PORT", + value=str(container_port), + ), + containerinstance.EnvironmentVariableArgs( + name="FLASK_RUN_HOST", + value="0.0.0.0", + ), + ], + resources=containerinstance.ResourceRequirementsArgs( + requests=containerinstance.ResourceRequestsArgs( + cpu=cpu, + memory_in_gb=memory, + ), + ), + ), + ], + ip_address=containerinstance.IpAddressArgs( + type=containerinstance.ContainerGroupIpAddressType.PUBLIC, + dns_name_label=dns_name, + ports=[ + containerinstance.PortArgs( + port=container_port, + protocol="tcp", + ), + ], + ), + ), +) + +# Export the service's IP address, hostname, and fully-qualified URL. +pulumi.export("hostname", container_group.ip_address.apply(lambda addr: addr.fqdn)) +pulumi.export("ip", container_group.ip_address.apply(lambda addr: addr.ip)) +pulumi.export( + "url", + container_group.ip_address.apply( + lambda addr: f"http://{addr.fqdn}:{container_port}" + ), +) diff --git a/container-azure-python/app/Dockerfile b/container-azure-python/app/Dockerfile new file mode 100644 index 000000000..2b2d4db60 --- /dev/null +++ b/container-azure-python/app/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.10-alpine +WORKDIR /usr/src/app + +COPY requirements.txt ./ +RUN pip install -r requirements.txt + +COPY . . + +CMD ["flask", "run"] diff --git a/container-azure-python/app/app.py b/container-azure-python/app/app.py new file mode 100644 index 000000000..64a0c1dd4 --- /dev/null +++ b/container-azure-python/app/app.py @@ -0,0 +1,7 @@ +from flask import Flask + +app = Flask(__name__) + +@app.route("/") +def index(): + return { "message": "Hello, world!" } diff --git a/container-azure-python/app/requirements.txt b/container-azure-python/app/requirements.txt new file mode 100644 index 000000000..e3e9a71d9 --- /dev/null +++ b/container-azure-python/app/requirements.txt @@ -0,0 +1 @@ +Flask diff --git a/container-azure-python/requirements.txt b/container-azure-python/requirements.txt new file mode 100644 index 000000000..150701af6 --- /dev/null +++ b/container-azure-python/requirements.txt @@ -0,0 +1,4 @@ +pulumi>=3.0.0,<4.0.0 +pulumi-azure-native>=1.0.0,<2.0.0 +pulumi-random>=4.0.0,<5.0.0 +pulumi-docker>=3.0.0,<4.0.0 diff --git a/container-azure-typescript/.gitignore b/container-azure-typescript/.gitignore new file mode 100644 index 000000000..c6958891d --- /dev/null +++ b/container-azure-typescript/.gitignore @@ -0,0 +1,2 @@ +/bin/ +/node_modules/ diff --git a/container-azure-typescript/Pulumi.yaml b/container-azure-typescript/Pulumi.yaml new file mode 100644 index 000000000..a46b063fc --- /dev/null +++ b/container-azure-typescript/Pulumi.yaml @@ -0,0 +1,22 @@ +name: ${PROJECT} +description: ${DESCRIPTION} +runtime: + name: nodejs +template: + description: A containerized service on Azure Container Instances + config: + azure-native:location: + description: The Azure region to deploy into + default: WestUS + appPath: + description: The path to the container application to deploy + default: app + containerPort: + description: The port to expose on the container + default: 80 + cpu: + description: The number of CPU cores to allocate on the container + default: 1 + memory: + description: The amount of memory, in GB, to allocate on the container + default: 2 diff --git a/container-azure-typescript/app/.dockerignore b/container-azure-typescript/app/.dockerignore new file mode 100644 index 000000000..93f136199 --- /dev/null +++ b/container-azure-typescript/app/.dockerignore @@ -0,0 +1,2 @@ +node_modules +npm-debug.log diff --git a/container-azure-typescript/app/Dockerfile b/container-azure-typescript/app/Dockerfile new file mode 100644 index 000000000..1d41c7e42 --- /dev/null +++ b/container-azure-typescript/app/Dockerfile @@ -0,0 +1,9 @@ +FROM node:16 +WORKDIR /usr/src/app + +COPY package*.json ./ +RUN npm install + +COPY . . + +CMD [ "node", "index.js" ] diff --git a/container-azure-typescript/app/index.js b/container-azure-typescript/app/index.js new file mode 100644 index 000000000..ccf779d1e --- /dev/null +++ b/container-azure-typescript/app/index.js @@ -0,0 +1,13 @@ +"use strict"; + +const express = require("express"); +const app = express(); + +app.get("/", (req, res) => { + res.json({ message: "Hello, world!" }); +}); + +const port = process.env.PORT; +app.listen(port, () => { + console.log(`Listening on port ${port}`); +}); diff --git a/container-azure-typescript/app/package.json b/container-azure-typescript/app/package.json new file mode 100644 index 000000000..74a933871 --- /dev/null +++ b/container-azure-typescript/app/package.json @@ -0,0 +1,11 @@ +{ + "name": "my-app", + "version": "0.1.0", + "main": "index.js", + "scripts": { + "start": "node index.js" + }, + "dependencies": { + "express": "^4.16.1" + } +} diff --git a/container-azure-typescript/index.ts b/container-azure-typescript/index.ts new file mode 100644 index 000000000..cc71fba1d --- /dev/null +++ b/container-azure-typescript/index.ts @@ -0,0 +1,110 @@ +import * as pulumi from "@pulumi/pulumi"; +import * as resources from "@pulumi/azure-native/resources"; +import * as containerregistry from "@pulumi/azure-native/containerregistry"; +import * as containerinstance from "@pulumi/azure-native/containerinstance"; +import * as random from "@pulumi/random"; +import * as docker from "@pulumi/docker"; + +// Import the program's configuration settings. +const config = new pulumi.Config(); +const appPath = config.get("appPath") || "./app"; +const imageName = config.get("imageName") || "my-app"; +const imageTag = config.get("imageTag") || "latest"; +const containerPort = config.getNumber("containerPort") || 80; +const cpu = config.getNumber("cpu") || 1; +const memory = config.getNumber("memory") || 2; + +// Create a resource group for the container registry. +const resourceGroup = new resources.ResourceGroup("resource-group"); + +// Create a container registry. +const registry = new containerregistry.Registry("registry", { + resourceGroupName: resourceGroup.name, + adminUserEnabled: true, + sku: { + name: containerregistry.SkuName.Basic, + }, +}); + +// Fetch login credentials for the registry. +const credentials = containerregistry.listRegistryCredentialsOutput({ + resourceGroupName: resourceGroup.name, + registryName: registry.name, +}).apply(creds => { + return { + username: creds.username!, + password: creds.passwords![0].value!, + }; +}); + +// Create a container image for the service. +const image = new docker.Image("image", { + imageName: pulumi.interpolate`${registry.loginServer}/${imageName}:${imageTag}`, + build: { + context: appPath, + }, + registry: { + server: registry.loginServer, + username: credentials.username, + password: credentials.password, + }, +}); + +// Use a random string to give the service a unique DNS name. +const dnsName = new random.RandomString("dns-name", { + length: 8, + special: false, +}).result.apply(result => `${imageName}-${result.toLowerCase()}`); + +// Create a container group for the service that makes it publicly accessible. +const containerGroup = new containerinstance.ContainerGroup("container-group", { + resourceGroupName: resourceGroup.name, + osType: "linux", + restartPolicy: "always", + imageRegistryCredentials: [ + { + server: registry.loginServer, + username: credentials.username, + password: credentials.password, + }, + ], + containers: [ + { + name: imageName, + image: image.imageName, + ports: [ + { + port: containerPort, + protocol: "tcp", + }, + ], + environmentVariables: [ + { + name: "PORT", + value: containerPort.toString(), + }, + ], + resources: { + requests: { + cpu: cpu, + memoryInGB: memory, + }, + }, + }, + ], + ipAddress: { + type: containerinstance.ContainerGroupIpAddressType.Public, + dnsNameLabel: dnsName, + ports: [ + { + port: containerPort, + protocol: "tcp", + }, + ], + }, +}); + +// Export the service's IP address, hostname, and fully-qualified URL. +export const hostname = containerGroup.ipAddress.apply(addr => addr!.fqdn!); +export const ip = containerGroup.ipAddress.apply(addr => addr!.ip!); +export const url = containerGroup.ipAddress.apply(addr => `http://${addr!.fqdn!}:${containerPort}`); diff --git a/container-azure-typescript/package.json b/container-azure-typescript/package.json new file mode 100644 index 000000000..ec4ad0690 --- /dev/null +++ b/container-azure-typescript/package.json @@ -0,0 +1,12 @@ +{ + "name": "${PROJECT}", + "devDependencies": { + "@types/node": "^14" + }, + "dependencies": { + "@pulumi/azure-native": "^1.0.0", + "@pulumi/docker": "^3.4.1", + "@pulumi/pulumi": "^3.0.0", + "@pulumi/random": "^4.8.2" + } +} diff --git a/container-azure-typescript/tsconfig.json b/container-azure-typescript/tsconfig.json new file mode 100644 index 000000000..ab65afa61 --- /dev/null +++ b/container-azure-typescript/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "strict": true, + "outDir": "bin", + "target": "es2016", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "experimentalDecorators": true, + "pretty": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.ts" + ] +} diff --git a/metadata/groups/container-service-azure.yaml b/metadata/groups/container-service-azure.yaml new file mode 100644 index 000000000..d1704a47b --- /dev/null +++ b/metadata/groups/container-service-azure.yaml @@ -0,0 +1,11 @@ +name: Container Service on Azure +kind: architecture +parent: container-service +slug: azure +clouds: + - azure +templates: + - container-azure-typescript + - container-azure-python + - container-azure-go + - container-azure-csharp