Repository: pulumi/pulumi-awsx
Pull request #806 (2022-04-29 16:11:56)

Add ECS python and dotnet tests


Patch:
diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml
index 392b3d36..f9b26586 100644
--- a/.github/workflows/pre-release.yml
+++ b/.github/workflows/pre-release.yml
@@ -10,6 +10,7 @@ env:
   SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
   PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
   PULUMI_LOCAL_NUGET: ${{ github.workspace }}/nuget
+  IS_PRERELEASE: true
 jobs:
   lint:
     name: lint
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index dd5d1663..be08ac18 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -410,3 +410,4 @@ name: release
   push:
     tags:
       - v*.*.*
+      - '!v*.*.*-**'
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 14ec47cd..ab811888 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,15 +1,36 @@
-# Contributing to Pulumi
+# Contributing to the Pulumi ecosystem
 
-Do you want to hack on Pulumi?  Awesome!  We are so happy to have you.
+Do you want to contribute to Pulumi? Awesome! We are so happy to have you.
+We have a few tips and housekeeping items to help you get up and running.
+
+## Code of Conduct
+
+Please make sure to read and observe our [Code of Conduct](./CODE-OF-CONDUCT.md)
+
+## Community Expectations
+
+Please read about our [contribution guidelines here.](https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md#communications)
+
+## Setting up your development environment
+
+### Pulumi prerequisites
 
 Please refer to the [main Pulumi repo](https://github.com/pulumi/pulumi/)'s [CONTRIBUTING.md file](
-https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md) for details on how to do so.
+https://github.com/pulumi/pulumi/blob/master/CONTRIBUTING.md#developing) for details on how to get set up with Pulumi.
+
+## Committing Generated Code
+
+You must generate and check in the SDKs on each pull request containing a code change, e.g. adding a new resource to `resources.go`.
+
+1. Run `make build_sdks` from the root of this repository
+1. Open a pull request containing all changes
+1. *Note:* If a large number of seemingly-unrelated diffs are produced by `make build_sdks` (for example, lots of changes to comments unrelated to the change you are making), ensure that the latest dependencies for the provider are installed by running `go mod tidy` in the `provider/` directory of this repository.
 
 ## Running Integration Tests
 
 The examples and integration tests in this repository will create and destroy real AWS
 cloud resources while running. Before running these tests, make sure that you have
-[configured Pulumi with AWS](https://www.pulumi.com/docs/get-started/aws/) successfully once before.
+[configured Pulumi with AWS](https://pulumi.io/install/aws.html) successfully once before.
 
 The only additional step you need to take to run tests in this repo is to set the
 `AWS_REGION` environment variable to the region you'd like to create test resources in.
@@ -17,4 +38,4 @@ The integration tests do try to clean up after themselves by deleting everything
 created, but in the event of bugs or test failures you may need to go into the AWS Console
 and delete resources yourself.
 
-Once you have set `AWS_REGION` and configured your AWS credentials, `make test_all` will run all integration tests.
+Once you have set `AWS_REGION` and configured your AWS credentials, `make test` will run your integration tests.
diff --git a/Makefile b/Makefile
index ca429ef1..289716bd 100644
--- a/Makefile
+++ b/Makefile
@@ -69,7 +69,10 @@ build_go:: AWS_VERSION := $(shell node -e 'console.log(require("./awsx/package.j
 build_go:: schema
 	rm -rf sdk/go
 	cd schemagen/cmd/$(CODEGEN) && go run . go ../../../sdk/go $(WORKING_DIR)/$(PACK)/schema.json $(VERSION)
-	cd sdk && go get github.com/pulumi/pulumi-aws/sdk/v5@v$(AWS_VERSION) && go mod tidy
+	cd sdk && \
+		go get github.com/pulumi/pulumi-aws/sdk/v5@v$(AWS_VERSION) && \
+		go mod tidy && \
+		go test -v ./... -check.vv
 
 build_dotnet:: DOTNET_VERSION := $(shell pulumictl get version --language dotnet)
 build_dotnet:: schema
diff --git a/README.md b/README.md
index 62c08dbc..a85d95e5 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,10 @@
-[![Build Status](https://travis-ci.com/pulumi/pulumi-awsx.svg?token=eHg7Zp5zdDDJfTjY8ejq&branch=master)](https://travis-ci.com/pulumi/pulumi-awsx)
+[![Actions Status](https://github.com/pulumi/pulumi-awsx/workflows/master/badge.svg)](https://github.com/pulumi/pulumi-awsx/actions)
+[![Slack](http://www.pulumi.com/images/docs/badges/slack.svg)](https://slack.pulumi.com)
+[![NPM version](https://badge.fury.io/js/%40pulumi%2Fawsx.svg)](https://www.npmjs.com/package/@pulumi/awsx)
+[![Python version](https://badge.fury.io/py/pulumi-awsx.svg)](https://pypi.org/project/pulumi-awsx)
+[![NuGet version](https://badge.fury.io/nu/pulumi.awsx.svg)](https://badge.fury.io/nu/pulumi.awsx)
+[![PkgGoDev](https://pkg.go.dev/badge/github.com/pulumi/pulumi-awsx/sdk/go)](https://pkg.go.dev/github.com/pulumi/pulumi-awsx/sdk/go)
+[![License](https://img.shields.io/npm/l/%40pulumi%2Fpulumi.svg)](https://github.com/pulumi/pulumi-awsx/blob/master/LICENSE)
 
 ## Pulumi AWS Infrastructure Components
 
@@ -11,27 +17,34 @@ The AWS Infrastructure package is intended to provide [component](https://pulumi
 
 The AWS Infrastructure package undergoes constant improvements and additions.  While we will strive to maintain backward compatability here, we will occasionally make breaks here as appropriate if it helps improve the overall quality of this package.
 
-The AWS Infrastructure package exposes many high level abstractions.  Including, but not limited to:
+The AWS Infrastructure package exposes many high level abstractions.  Including:
 
-* [`apigateway`](https://github.com/pulumi/pulumi-awsx/blob/master/nodejs/awsx/apigateway). A module that makes creating API Gateway RestApis easily.  It includes native support to easily route incoming requests to AWS Lambdas, S3 buckets, or proxying requests to internal Load Balancer endpoints.
+* [`ec2`](https://github.com/pulumi/pulumi-awsx/blob/master/awsx/ec2).  A module that makes it easier to work with your AWS network, subnets, and security groups.  By default, the resources in the package follow the [AWS Best Practices](
+  https://aws.amazon.com/answers/networking/aws-single-vpc-design/), but can be configured as desired in whatever ways you want.  Most commonly, this package is used to acquire the default Vpc for a region (using `awsx.ec2.DefaultNetwork`).  However, it can also be used to easily create or augment an existing Vpc.
 
-* [`autoscaling`](https://github.com/pulumi/pulumi-awsx/blob/master/nodejs/awsx/autoscaling). A module that makes creating auto-scaling launch configurations and launch groups easier.   Note: given an awsx `ecs.Cluster`, the easiest way to create an auto-scaling group is to just call [`createAutoScalingGroup`](https://www.pulumi.com/docs/reference/pkg/nodejs/pulumi/awsx/autoscaling/#creating-an-autoscalinggroup) on it.  This will produce an auto-scaling group already property configured for that cluster.
+* [`ecs`](https://github.com/pulumi/pulumi-awsx/blob/master/awsx/ecs).  A module that makes it easy to create and configure clusters, tasks and services for running containers. Convenience resources are created to make the common tasks of creating EC2 or Fargate services and tasks much simpler.
 
-* [`ec2`](https://github.com/pulumi/pulumi-awsx/blob/master/nodejs/awsx/ec2).  A module that makes it easier to work with your AWS network, subnets, and security groups.  By default, the resources in the package follow the [AWS Best Practices](
-  https://aws.amazon.com/answers/networking/aws-single-vpc-design/), but can be configured as desired in whatever ways you want.  Most commonly, this package is used to acquire the default Vpc for a region (using [`Vpc.getDefault`](https://www.pulumi.com/docs/reference/pkg/nodejs/pulumi/awsx/ec2/#the-default-vpc)).  However, it can also be used to easily create or augment an existing Vpc.
+* [`lb`](https://github.com/pulumi/pulumi-awsx/tree/master/awsx/lb).  A module for simply setting up [Elastic Load Balancing](https://aws.amazon.com/elasticloadbalancing/). This module provides convenient ways to set up either `Network` or `Application` load balancers, along with the appropriate ELB Target Groups and Listeners in order to have a high availability, automatically-scaled service.  These ELB components also work well with the other awsx components.  For example, an `lb.defaultTarget` can be passed in directly as the `portMapping` target of an `ecs.FargateService`.
 
-* [`ecs`](https://github.com/pulumi/pulumi-awsx/blob/master/nodejs/awsx/ecs).  A module that makes it easy to create and configure clusters, tasks and services for running containers. Convenience resources are created to make the common tasks of creating EC2 or Fargate services and tasks much simpler.
+The library also exposes the classic AWSx (the previous TypeScript) implementation. You can find these modules in the `classic` namespace. These modules include:
 
-* [`lb`](https://github.com/pulumi/pulumi-awsx/tree/master/nodejs/awsx/lb).  A module for simply setting up [Elastic Load Balancing](https://aws.amazon.com/elasticloadbalancing/). This module provides convenient ways to set up either `Network` or `Application` load balancers, along with the appropriate ELB Target Groups and Listeners in order to have a high availability, automatically-scaled service.  These ELB components also work well with the other awsx components.  For example, an [`lb.TargetGroup`](https://www.pulumi.com/docs/reference/pkg/nodejs/pulumi/awsx/lb/#TargetGroup) can be passed in directly as the `portMapping` target of an [`ecs.Container`](https://www.pulumi.com/docs/reference/pkg/nodejs/pulumi/awsx/ecs/#clusters).
+* `apigateway`
+* `autoscaling`
+* `ecs`
+* `ec2`
+* `cloudwatch`
+* `lb`
+* `cloudtrail`
 
-* ~~`Network`~~. Deprecated.  Use [ec2.VPC](https://www.pulumi.com/docs/reference/pkg/nodejs/pulumi/awsx/ec2/#custom-vpcs) instead.
+### Installing
 
-* ~~`Cluster`~~. Deprecated.  Use [ecs.Cluster](https://www.pulumi.com/docs/reference/pkg/nodejs/pulumi/awsx/ecs/#clusters) instead.
+## Installing
 
+This package is available in many languages in the standard packaging formats.
 
-### Installing
+### Node.js (Java/TypeScript)
 
-This package is available in JavaScript/TypeScript for use with Node.js.  Install it using either `npm`:
+To use from JavaScript or TypeScript in Node.js, install using either `npm`:
 
     $ npm install @pulumi/awsx
 
@@ -39,7 +52,29 @@ or `yarn`:
 
     $ yarn add @pulumi/awsx
 
-### Reference
+### Python
+
+To use from Python, install using `pip`:
+
+    $ pip install pulumi_awsx
+
+### Go
+
+To use from Go, use `go get` to grab the latest version of the library
+
+    $ go get github.com/pulumi/pulumi-awsx/sdk
+
+### .NET
+
+To use from .NET, install using `dotnet add package`:
+
+    $ dotnet add package Pulumi.Awsx
+
+### Configuration
+
+The configuration options available for this provider  mirror those of the [Pulumi AWS Classic Provider](https://github.com/pulumi/pulumi-aws#configuration)
+
+## Reference
 
-For detailed reference documentation, please visit [the API docs](
-https://pulumi.io/reference/pkg/nodejs/@pulumi/awsx/index.html).
+For further information, visit [AWS in the Pulumi Registry](https://www.pulumi.com/registry/packages/awsx/)
+or for detailed API reference documentation, visit [AWS API Docs in the Pulumi Registry](https://www.pulumi.com/registry/packages/awsx/api-docs/).
diff --git a/awsx/ec2/vpc.ts b/awsx/ec2/vpc.ts
index a41cb440..9f0af357 100644
--- a/awsx/ec2/vpc.ts
+++ b/awsx/ec2/vpc.ts
@@ -86,6 +86,8 @@ export class Vpc extends schema.Vpc<VpcData> {
             availabilityZones,
             args.subnetSpecs,
         );
+        validateSubnets(subnetSpecs, getOverlappingSubnets);
+
         validateNatGatewayStrategy(natGatewayStrategy, subnetSpecs);
 
         const vpc = new aws.ec2.Vpc(
@@ -390,3 +392,34 @@ export function compareSubnetSpecs(
     // Isolated is the only remaining case, and they always come last.
     return 1;
 }
+
+export interface OverlappingSubnet {
+    cidrBlock: string;
+    subnetName: string;
+}
+
+export function getOverlappingSubnets(specs: OverlappingSubnet[]): OverlappingSubnet[] {
+    const ipAddress = require("ip-address");
+
+    const overlaps = (spec1: OverlappingSubnet, spec2: OverlappingSubnet) => {
+        const ip1 = new ipAddress.Address4(spec1.cidrBlock);
+        const ip2 = new ipAddress.Address4(spec2.cidrBlock);
+
+        return ip1.isInSubnet(ip2) || ip2.isInSubnet(ip1);
+    };
+
+    return specs.filter(x => specs.filter(y => x !== y && overlaps(x, y)).length > 0);
+}
+
+export function validateSubnets(specs: SubnetSpec[], getOverlappingSubnets: (specs: OverlappingSubnet[]) => OverlappingSubnet[]) {
+    const overlappingSubnets = getOverlappingSubnets(specs);
+
+    if (overlappingSubnets.length > 0) {
+        let msg = "The following subnets overlap with at least one other subnet. Make the CIDR for the VPC larger, reduce the size of the subnets per AZ, or use less Availability Zones:\n\n";
+        for (let i = 0; i < overlappingSubnets.length; i++) {
+            msg += `${i + 1}. ${overlappingSubnets[i].subnetName}: ${overlappingSubnets[i].cidrBlock}\n`;
+        }
+
+        throw new Error(msg);
+    }
+}
diff --git a/examples/ecs/dotnet/.gitignore b/examples/ecs/dotnet/.gitignore
new file mode 100644
index 00000000..e6452706
--- /dev/null
+++ b/examples/ecs/dotnet/.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/examples/ecs/dotnet/MyStack.cs b/examples/ecs/dotnet/MyStack.cs
new file mode 100644
index 00000000..48a726dc
--- /dev/null
+++ b/examples/ecs/dotnet/MyStack.cs
@@ -0,0 +1,36 @@
+using Pulumi;
+using Aws = Pulumi.Aws;
+using Awsx = Pulumi.Awsx;
+
+class MyStack : Stack
+{
+    public MyStack()
+    {
+        var cluster = new Aws.Ecs.Cluster("default-cluster");
+
+        var lb = new Awsx.Lb.ApplicationLoadBalancer("nginx-lb");
+
+        var service = new Awsx.Ecs.FargateService("my-service", new Awsx.Ecs.FargateServiceArgs
+        {
+            Cluster = cluster.Arn,
+            TaskDefinitionArgs = new Awsx.Ecs.Inputs.FargateServiceTaskDefinitionArgs
+            {
+                Container = new Awsx.Ecs.Inputs.TaskDefinitionContainerDefinitionArgs
+                {
+                    Image = "nginx:latest",
+                    Cpu = 512,
+                    Memory = 128,
+                    Essential = true,
+                    PortMappings = {new Awsx.Ecs.Inputs.TaskDefinitionPortMappingArgs
+                    {
+                        TargetGroup = lb.DefaultTargetGroup,
+                    }},
+                }
+            }
+        });
+
+        this.Url = lb.LoadBalancer.Apply(lb => lb.DnsName);
+    }
+
+    [Output] public Output<string> Url { get; set; }
+}
diff --git a/examples/ecs/dotnet/Program.cs b/examples/ecs/dotnet/Program.cs
new file mode 100644
index 00000000..b5c1d038
--- /dev/null
+++ b/examples/ecs/dotnet/Program.cs
@@ -0,0 +1,7 @@
+﻿using System.Threading.Tasks;
+using Pulumi;
+
+class Program
+{
+    static Task<int> Main() => Deployment.RunAsync<MyStack>();
+}
diff --git a/examples/ecs/dotnet/Pulumi.yaml b/examples/ecs/dotnet/Pulumi.yaml
new file mode 100644
index 00000000..e9e39077
--- /dev/null
+++ b/examples/ecs/dotnet/Pulumi.yaml
@@ -0,0 +1,3 @@
+name: ecs-dotnet
+runtime: dotnet
+description: A minimal C# Pulumi program
diff --git a/examples/ecs/dotnet/ecs-dotnet.csproj b/examples/ecs/dotnet/ecs-dotnet.csproj
new file mode 100644
index 00000000..86dfcd08
--- /dev/null
+++ b/examples/ecs/dotnet/ecs-dotnet.csproj
@@ -0,0 +1,13 @@
+﻿<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Pulumi" Version="3.*" />
+  </ItemGroup>
+
+</Project>
diff --git a/examples/ecs/python/.gitignore b/examples/ecs/python/.gitignore
new file mode 100644
index 00000000..a3807e5b
--- /dev/null
+++ b/examples/ecs/python/.gitignore
@@ -0,0 +1,2 @@
+*.pyc
+venv/
diff --git a/examples/ecs/python/Pulumi.yaml b/examples/ecs/python/Pulumi.yaml
new file mode 100644
index 00000000..7be73439
--- /dev/null
+++ b/examples/ecs/python/Pulumi.yaml
@@ -0,0 +1,6 @@
+name: ecs-python
+runtime:
+  name: python
+  options:
+    virtualenv: venv
+description: A minimal AWS Python Pulumi program to create an AWSX ECS service
diff --git a/examples/ecs/python/__main__.py b/examples/ecs/python/__main__.py
new file mode 100644
index 00000000..43731923
--- /dev/null
+++ b/examples/ecs/python/__main__.py
@@ -0,0 +1,24 @@
+"""A Python Pulumi program"""
+
+import pulumi
+import pulumi_aws as aws
+import pulumi_awsx as awsx
+
+cluster = aws.ecs.Cluster("default-cluster")
+
+lb = awsx.lb.ApplicationLoadBalancer("nginx-lb")
+
+service = awsx.ecs.FargateService("my-service",
+    cluster=cluster.arn,
+    task_definition_args=awsx.ecs.FargateServiceTaskDefinitionArgs(
+        container=awsx.ecs.TaskDefinitionContainerDefinitionArgs(
+            image="nginx:latest",
+            cpu=512,
+            memory=128,
+            essential=True,
+            port_mappings=[awsx.ecs.TaskDefinitionPortMappingArgs(
+                target_group=lb.default_target_group
+            )],
+        )
+    )
+)
diff --git a/examples/ecs/python/requirements.txt b/examples/ecs/python/requirements.txt
new file mode 100644
index 00000000..557b513b
--- /dev/null
+++ b/examples/ecs/python/requirements.txt
@@ -0,0 +1,2 @@
+pulumi>=3.0.0,<4.0.0
+pulumi_aws>=5.0.0,<6.0.0
diff --git a/schemagen/go.mod b/schemagen/go.mod
index 64938d92..aa8fad87 100644
--- a/schemagen/go.mod
+++ b/schemagen/go.mod
@@ -4,8 +4,8 @@ go 1.17
 
 require (
 	github.com/pkg/errors v0.9.1
-	github.com/pulumi/pulumi/pkg/v3 v3.30.0
-	github.com/pulumi/pulumi/sdk/v3 v3.30.0
+	github.com/pulumi/pulumi/pkg/v3 v3.30.1-0.20220429024829-810756e7b968
+	github.com/pulumi/pulumi/sdk/v3 v3.30.1-0.20220429024829-810756e7b968
 )
 
 require (
diff --git a/schemagen/go.sum b/schemagen/go.sum
index ecd32815..9360eb13 100644
--- a/schemagen/go.sum
+++ b/schemagen/go.sum
@@ -560,8 +560,12 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
 github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
 github.com/pulumi/pulumi/pkg/v3 v3.30.0 h1:tdhcndy6pc+aV76wC76r+i5Z5E5nUvc78JaNyeA66JY=
 github.com/pulumi/pulumi/pkg/v3 v3.30.0/go.mod h1:U1BGP/1QmebZE35OfFAT96zN2lhexAktkGLAbnAv+lI=
+github.com/pulumi/pulumi/pkg/v3 v3.30.1-0.20220429024829-810756e7b968 h1:u2iB+atQW0hqEfB2MVOcKpf3oA61qjPnhYP42c05uSU=
+github.com/pulumi/pulumi/pkg/v3 v3.30.1-0.20220429024829-810756e7b968/go.mod h1:U1BGP/1QmebZE35OfFAT96zN2lhexAktkGLAbnAv+lI=
 github.com/pulumi/pulumi/sdk/v3 v3.30.0 h1:0X5gSUS3x82XzenpCCW8EdJrmOzoQqHSdQd0O6QSMQQ=
 github.com/pulumi/pulumi/sdk/v3 v3.30.0/go.mod h1:hGo/+AL1L4sPL9Ukd/i5bNFM3WHs3dHcA+GKEW7M3RA=
+github.com/pulumi/pulumi/sdk/v3 v3.30.1-0.20220429024829-810756e7b968 h1:XaNCfbgmedFUnWLVj+3jvENiwpndSrNmtdh1Ir8sGrQ=
+github.com/pulumi/pulumi/sdk/v3 v3.30.1-0.20220429024829-810756e7b968/go.mod h1:hGo/+AL1L4sPL9Ukd/i5bNFM3WHs3dHcA+GKEW7M3RA=
 github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
 github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
 github.com/rjeczalik/notify v0.9.2 h1:MiTWrPj55mNDHEiIX5YUSKefw/+lCQVoAFmD6oQm5w8=
diff --git a/sdk/go/awsx/cloudtrail/trail.go b/sdk/go/awsx/cloudtrail/trail.go
index 5384f8c8..ce8e9744 100644
--- a/sdk/go/awsx/cloudtrail/trail.go
+++ b/sdk/go/awsx/cloudtrail/trail.go
@@ -78,7 +78,7 @@ type TrailArgs struct {
 	// Specifies an advanced event selector for enabling data event logging. Fields documented below. Conflicts with `event_selector`.
 	AdvancedEventSelectors cloudtrail.TrailAdvancedEventSelectorArrayInput
 	// Log group to which CloudTrail logs will be delivered.
-	CloudWatchLogsGroup *awsx.OptionalLogGroupArgs
+	CloudWatchLogsGroup *awsx.OptionalLogGroup
 	// Whether log file integrity validation is enabled. Defaults to `false`.
 	EnableLogFileValidation pulumi.BoolPtrInput
 	// Enables logging for the trail. Defaults to `true`. Setting this to `false` will pause logging.
@@ -98,7 +98,7 @@ type TrailArgs struct {
 	// Specifies the name of the advanced event selector.
 	Name pulumi.StringPtrInput
 	// S3 bucket designated for publishing log files.
-	S3Bucket *awsx.RequiredBucketArgs
+	S3Bucket *awsx.RequiredBucket
 	// S3 key prefix that follows the name of the bucket you have designated for log file delivery.
 	S3KeyPrefix pulumi.StringPtrInput
 	// Name of the Amazon SNS topic defined for notification of log file delivery.
diff --git a/sdk/go/awsx/ec2/vpc.go b/sdk/go/awsx/ec2/vpc.go
index 0e85024b..5df7e241 100644
--- a/sdk/go/awsx/ec2/vpc.go
+++ b/sdk/go/awsx/ec2/vpc.go
@@ -127,11 +127,11 @@ type VpcArgs struct {
 	// Netmask length to request from IPAM Pool. Conflicts with `ipv6_cidr_block`. This can be omitted if IPAM pool as a `allocation_default_netmask_length` set. Valid values: `56`.
 	Ipv6NetmaskLength pulumi.IntPtrInput
 	// Configuration for NAT Gateways. Optional. If private and public subnets are both specified, defaults to one gateway per availability zone. Otherwise, no gateways will be created.
-	NatGateways *NatGatewayConfigurationArgs
+	NatGateways *NatGatewayConfiguration
 	// A number of availability zones to which the subnets defined in subnetSpecs will be deployed. Optional, defaults to the first 3 AZs in the current region.
 	NumberOfAvailabilityZones *int
 	// A list of subnet specs that should be deployed to each AZ specified in availabilityZoneNames. Optional. Defaults to a (smaller) public subnet and a (larger) private subnet based on the size of the CIDR block for the VPC.
-	SubnetSpecs []SubnetSpecArgs
+	SubnetSpecs []SubnetSpec
 	// A map of tags to assign to the resource. If configured with a provider `default_tags` configuration block present, tags with matching keys will overwrite those defined at the provider-level.
 	Tags pulumi.StringMapInput
 }
diff --git a/sdk/go/awsx/ecr/repository.go b/sdk/go/awsx/ecr/repository.go
index 46745753..cfe89435 100644
--- a/sdk/go/awsx/ecr/repository.go
+++ b/sdk/go/awsx/ecr/repository.go
@@ -64,7 +64,7 @@ type RepositoryArgs struct {
 	// The tag mutability setting for the repository. Must be one of: `MUTABLE` or `IMMUTABLE`. Defaults to `MUTABLE`.
 	ImageTagMutability pulumi.StringPtrInput
 	// A lifecycle policy consists of one or more rules that determine which images in a repository should be expired. If not provided, this will default to untagged images expiring after 1 day.
-	LifecyclePolicy *LifecyclePolicyArgs
+	LifecyclePolicy *LifecyclePolicy
 	// Name of the repository.
 	Name pulumi.StringPtrInput
 	// A map of tags to assign to the resource. If configured with a provider `default_tags` configuration block present, tags with matching keys will overwrite those defined at the provider-level.
diff --git a/sdk/go/awsx/ecs/ec2service.go b/sdk/go/awsx/ecs/ec2service.go
index d4431e27..b55041ff 100644
--- a/sdk/go/awsx/ecs/ec2service.go
+++ b/sdk/go/awsx/ecs/ec2service.go
@@ -141,7 +141,7 @@ type EC2ServiceArgs struct {
 	// Family and revision (`family:revision`) or full ARN of the task definition that you want to run in your service. Either [taskDefinition] or [taskDefinitionArgs] must be provided.
 	TaskDefinition pulumi.StringPtrInput
 	// The args of task definition that you want to run in your service. Either [taskDefinition] or [taskDefinitionArgs] must be provided.
-	TaskDefinitionArgs *EC2ServiceTaskDefinitionArgs
+	TaskDefinitionArgs *EC2ServiceTaskDefinition
 }
 
 func (EC2ServiceArgs) ElementType() reflect.Type {
diff --git a/sdk/go/awsx/ecs/ec2taskDefinition.go b/sdk/go/awsx/ecs/ec2taskDefinition.go
index 17958b86..32772787 100644
--- a/sdk/go/awsx/ecs/ec2taskDefinition.go
+++ b/sdk/go/awsx/ecs/ec2taskDefinition.go
@@ -102,19 +102,19 @@ type EC2TaskDefinitionArgs struct {
 	// multiple containers, especially when creating a TaskDefinition to call [run] on.
 	//
 	// Either [container] or [containers] must be provided.
-	Container *TaskDefinitionContainerDefinitionArgs
+	Container *TaskDefinitionContainerDefinition
 	// All the containers to make a TaskDefinition from.  Useful when creating a Service that will
 	// contain many containers within.
 	//
 	// Either [container] or [containers] must be provided.
-	Containers map[string]TaskDefinitionContainerDefinitionArgs
+	Containers map[string]TaskDefinitionContainerDefinition
 	// The number of cpu units used by the task. If not provided, a default will be computed based on the cumulative needs specified by [containerDefinitions]
 	Cpu pulumi.StringPtrInput
 	// The amount of ephemeral storage to allocate for the task. This parameter is used to expand the total amount of ephemeral storage available, beyond the default amount, for tasks hosted on AWS Fargate. See Ephemeral Storage.
 	EphemeralStorage ecs.TaskDefinitionEphemeralStoragePtrInput
 	// The execution role that the Amazon ECS container agent and the Docker daemon can assume.
 	// Will be created automatically if not defined.
-	ExecutionRole *awsx.DefaultRoleWithPolicyArgs
+	ExecutionRole *awsx.DefaultRoleWithPolicy
 	// An optional unique name for your task definition. If not specified, then a default will be created.
 	Family pulumi.StringPtrInput
 	// Configuration block(s) with Inference Accelerators settings. Detailed below.
@@ -122,7 +122,7 @@ type EC2TaskDefinitionArgs struct {
 	// IPC resource namespace to be used for the containers in the task The valid values are `host`, `task`, and `none`.
 	IpcMode pulumi.StringPtrInput
 	// A set of volume blocks that containers in your task may use.
-	LogGroup *awsx.DefaultLogGroupArgs
+	LogGroup *awsx.DefaultLogGroup
 	// The amount (in MiB) of memory used by the task.  If not provided, a default will be computed
 	// based on the cumulative needs specified by [containerDefinitions]
 	Memory pulumi.StringPtrInput
@@ -141,7 +141,7 @@ type EC2TaskDefinitionArgs struct {
 	Tags pulumi.StringMapInput
 	// IAM role that allows your Amazon ECS container task to make calls to other AWS services.
 	// Will be created automatically if not defined.
-	TaskRole *awsx.DefaultRoleWithPolicyArgs
+	TaskRole *awsx.DefaultRoleWithPolicy
 	// Configuration block for volumes that containers in your task may use. Detailed below.
 	Volumes ecs.TaskDefinitionVolumeArrayInput
 }
diff --git a/sdk/go/awsx/ecs/fargateService.go b/sdk/go/awsx/ecs/fargateService.go
index d9eb2e2a..eeebe155 100644
--- a/sdk/go/awsx/ecs/fargateService.go
+++ b/sdk/go/awsx/ecs/fargateService.go
@@ -133,7 +133,7 @@ type FargateServiceArgs struct {
 	// Family and revision (`family:revision`) or full ARN of the task definition that you want to run in your service. Either [taskDefinition] or [taskDefinitionArgs] must be provided.
 	TaskDefinition pulumi.StringPtrInput
 	// The args of task definition that you want to run in your service. Either [taskDefinition] or [taskDefinitionArgs] must be provided.
-	TaskDefinitionArgs *FargateServiceTaskDefinitionArgs
+	TaskDefinitionArgs *FargateServiceTaskDefinition
 }
 
 func (FargateServiceArgs) ElementType() reflect.Type {
diff --git a/sdk/go/awsx/ecs/fargateTaskDefinition.go b/sdk/go/awsx/ecs/fargateTaskDefinition.go
index b3d0ed2f..7dd744ff 100644
--- a/sdk/go/awsx/ecs/fargateTaskDefinition.go
+++ b/sdk/go/awsx/ecs/fargateTaskDefinition.go
@@ -100,19 +100,19 @@ type FargateTaskDefinitionArgs struct {
 	// multiple containers, especially when creating a TaskDefinition to call [run] on.
 	//
 	// Either [container] or [containers] must be provided.
-	Container *TaskDefinitionContainerDefinitionArgs
+	Container *TaskDefinitionContainerDefinition
 	// All the containers to make a TaskDefinition from.  Useful when creating a Service that will
 	// contain many containers within.
 	//
 	// Either [container] or [containers] must be provided.
-	Containers map[string]TaskDefinitionContainerDefinitionArgs
+	Containers map[string]TaskDefinitionContainerDefinition
 	// The number of cpu units used by the task. If not provided, a default will be computed based on the cumulative needs specified by [containerDefinitions]
 	Cpu pulumi.StringPtrInput
 	// The amount of ephemeral storage to allocate for the task. This parameter is used to expand the total amount of ephemeral storage available, beyond the default amount, for tasks hosted on AWS Fargate. See Ephemeral Storage.
 	EphemeralStorage ecs.TaskDefinitionEphemeralStoragePtrInput
 	// The execution role that the Amazon ECS container agent and the Docker daemon can assume.
 	// Will be created automatically if not defined.
-	ExecutionRole *awsx.DefaultRoleWithPolicyArgs
+	ExecutionRole *awsx.DefaultRoleWithPolicy
 	// An optional unique name for your task definition. If not specified, then a default will be created.
 	Family pulumi.StringPtrInput
 	// Configuration block(s) with Inference Accelerators settings. Detailed below.
@@ -120,7 +120,7 @@ type FargateTaskDefinitionArgs struct {
 	// IPC resource namespace to be used for the containers in the task The valid values are `host`, `task`, and `none`.
 	IpcMode pulumi.StringPtrInput
 	// A set of volume blocks that containers in your task may use.
-	LogGroup *awsx.DefaultLogGroupArgs
+	LogGroup *awsx.DefaultLogGroup
 	// The amount (in MiB) of memory used by the task.  If not provided, a default will be computed
 	// based on the cumulative needs specified by [containerDefinitions]
 	Memory pulumi.StringPtrInput
@@ -137,7 +137,7 @@ type FargateTaskDefinitionArgs struct {
 	Tags pulumi.StringMapInput
 	// IAM role that allows your Amazon ECS container task to make calls to other AWS services.
 	// Will be created automatically if not defined.
-	TaskRole *awsx.DefaultRoleWithPolicyArgs
+	TaskRole *awsx.DefaultRoleWithPolicy
 	// Configuration block for volumes that containers in your task may use. Detailed below.
 	Volumes ecs.TaskDefinitionVolumeArrayInput
 }
diff --git a/sdk/go/awsx/ecs/pulumiTypes.go b/sdk/go/awsx/ecs/pulumiTypes.go
index 9af0ad9b..0d6d2491 100644
--- a/sdk/go/awsx/ecs/pulumiTypes.go
+++ b/sdk/go/awsx/ecs/pulumiTypes.go
@@ -5,7 +5,6 @@ package ecs
 
 import (
 	"github.com/pulumi/pulumi-aws/sdk/v5/go/aws/ecs"
-	"github.com/pulumi/pulumi-aws/sdk/v5/go/aws/iam"
 	"github.com/pulumi/pulumi-aws/sdk/v5/go/aws/lb"
 	"github.com/pulumi/pulumi-awsx/sdk/go/awsx/awsx"
 )
diff --git a/sdk/go/awsx/lb/applicationLoadBalancer.go b/sdk/go/awsx/lb/applicationLoadBalancer.go
index db941e93..62a1a3c2 100644
--- a/sdk/go/awsx/lb/applicationLoadBalancer.go
+++ b/sdk/go/awsx/lb/applicationLoadBalancer.go
@@ -37,7 +37,7 @@ func NewApplicationLoadBalancer(ctx *pulumi.Context,
 	}
 
 	if args.DefaultSecurityGroup != nil {
-		args.DefaultSecurityGroup = args.DefaultSecurityGroup.ToDefaultSecurityGroupPtrOutput().ApplyT(func(v *awsx.DefaultSecurityGroup) *awsx.DefaultSecurityGroup { return v.Defaults() }).(awsx.DefaultSecurityGroupPtrOutput)
+		args.DefaultSecurityGroup = args.DefaultSecurityGroup.Defaults()
 	}
 	var resource ApplicationLoadBalancer
 	err := ctx.RegisterRemoteComponentResource("awsx:lb:ApplicationLoadBalancer", name, args, &resource, opts...)
@@ -104,9 +104,9 @@ type ApplicationLoadBalancerArgs struct {
 	// The ID of the customer owned ipv4 pool to use for this load balancer.
 	CustomerOwnedIpv4Pool pulumi.StringPtrInput
 	// Options for creating a default security group if [securityGroups] not specified.
-	DefaultSecurityGroup *awsx.DefaultSecurityGroupArgs
+	DefaultSecurityGroup *awsx.DefaultSecurityGroup
 	// Options creating a default target group.
-	DefaultTargetGroup *TargetGroupArgs
+	DefaultTargetGroup *TargetGroup
 	// Determines how the load balancer handles requests that might pose a security risk to an application due to HTTP desync. Valid values are `monitor`, `defensive` (default), `strictest`.
 	DesyncMitigationMode pulumi.StringPtrInput
 	// Indicates whether HTTP headers with header fields that are not valid are removed by the load balancer (true) or routed to targets (false). The default is false. Elastic Load Balancing requires that message header names contain only alphanumeric characters and hyphens. Only valid for Load Balancers of type `application`.
@@ -125,9 +125,9 @@ type ApplicationLoadBalancerArgs struct {
 	// The type of IP addresses used by the subnets for your load balancer. The possible values are `ipv4` and `dualstack`
 	IpAddressType pulumi.StringPtrInput
 	// A listener to create. Only one of [listener] and [listeners] can be specified.
-	Listener *ListenerArgs
+	Listener *Listener
 	// List of listeners to create. Only one of [listener] and [listeners] can be specified.
-	Listeners []ListenerArgs
+	Listeners []Listener
 	// The name of the LB. This name must be unique within your AWS account, can have a maximum of 32 characters,
 	// must contain only alphanumeric characters or hyphens, and must not begin or end with a hyphen. If not specified,
 	// this provider will autogenerate a name beginning with `tf-lb`.
diff --git a/sdk/go/awsx/lb/networkLoadBalancer.go b/sdk/go/awsx/lb/networkLoadBalancer.go
index d8cb210b..0a59e5f9 100644
--- a/sdk/go/awsx/lb/networkLoadBalancer.go
+++ b/sdk/go/awsx/lb/networkLoadBalancer.go
@@ -95,7 +95,7 @@ type NetworkLoadBalancerArgs struct {
 	// The ID of the customer owned ipv4 pool to use for this load balancer.
 	CustomerOwnedIpv4Pool pulumi.StringPtrInput
 	// Options creating a default target group.
-	DefaultTargetGroup *TargetGroupArgs
+	DefaultTargetGroup *TargetGroup
 	// Determines how the load balancer handles requests that might pose a security risk to an application due to HTTP desync. Valid values are `monitor`, `defensive` (default), `strictest`.
 	DesyncMitigationMode pulumi.StringPtrInput
 	// Indicates whether HTTP headers with header fields that are not valid are removed by the load balancer (true) or routed to targets (false). The default is false. Elastic Load Balancing requires that message header names contain only alphanumeric characters and hyphens. Only valid for Load Balancers of type `application`.
@@ -115,9 +115,9 @@ type NetworkLoadBalancerArgs struct {
 	// The type of IP addresses used by the subnets for your load balancer. The possible values are `ipv4` and `dualstack`
 	IpAddressType pulumi.StringPtrInput
 	// A listener to create. Only one of [listener] and [listeners] can be specified.
-	Listener *ListenerArgs
+	Listener *Listener
 	// List of listeners to create. Only one of [listener] and [listeners] can be specified.
-	Listeners []ListenerArgs
+	Listeners []Listener
 	// The name of the LB. This name must be unique within your AWS account, can have a maximum of 32 characters,
 	// must contain only alphanumeric characters or hyphens, and must not begin or end with a hyphen. If not specified,
 	// this provider will autogenerate a name beginning with `tf-lb`.
diff --git a/sdk/python/README.md b/sdk/python/README.md
index 62c08dbc..a85d95e5 100644
--- a/sdk/python/README.md
+++ b/sdk/python/README.md
@@ -1,4 +1,10 @@
-[![Build Status](https://travis-ci.com/pulumi/pulumi-awsx.svg?token=eHg7Zp5zdDDJfTjY8ejq&branch=master)](https://travis-ci.com/pulumi/pulumi-awsx)
+[![Actions Status](https://github.com/pulumi/pulumi-awsx/workflows/master/badge.svg)](https://github.com/pulumi/pulumi-awsx/actions)
+[![Slack](http://www.pulumi.com/images/docs/badges/slack.svg)](https://slack.pulumi.com)
+[![NPM version](https://badge.fury.io/js/%40pulumi%2Fawsx.svg)](https://www.npmjs.com/package/@pulumi/awsx)
+[![Python version](https://badge.fury.io/py/pulumi-awsx.svg)](https://pypi.org/project/pulumi-awsx)
+[![NuGet version](https://badge.fury.io/nu/pulumi.awsx.svg)](https://badge.fury.io/nu/pulumi.awsx)
+[![PkgGoDev](https://pkg.go.dev/badge/github.com/pulumi/pulumi-awsx/sdk/go)](https://pkg.go.dev/github.com/pulumi/pulumi-awsx/sdk/go)
+[![License](https://img.shields.io/npm/l/%40pulumi%2Fpulumi.svg)](https://github.com/pulumi/pulumi-awsx/blob/master/LICENSE)
 
 ## Pulumi AWS Infrastructure Components
 
@@ -11,27 +17,34 @@ The AWS Infrastructure package is intended to provide [component](https://pulumi
 
 The AWS Infrastructure package undergoes constant improvements and additions.  While we will strive to maintain backward compatability here, we will occasionally make breaks here as appropriate if it helps improve the overall quality of this package.
 
-The AWS Infrastructure package exposes many high level abstractions.  Including, but not limited to:
+The AWS Infrastructure package exposes many high level abstractions.  Including:
 
-* [`apigateway`](https://github.com/pulumi/pulumi-awsx/blob/master/nodejs/awsx/apigateway). A module that makes creating API Gateway RestApis easily.  It includes native support to easily route incoming requests to AWS Lambdas, S3 buckets, or proxying requests to internal Load Balancer endpoints.
+* [`ec2`](https://github.com/pulumi/pulumi-awsx/blob/master/awsx/ec2).  A module that makes it easier to work with your AWS network, subnets, and security groups.  By default, the resources in the package follow the [AWS Best Practices](
+  https://aws.amazon.com/answers/networking/aws-single-vpc-design/), but can be configured as desired in whatever ways you want.  Most commonly, this package is used to acquire the default Vpc for a region (using `awsx.ec2.DefaultNetwork`).  However, it can also be used to easily create or augment an existing Vpc.
 
-* [`autoscaling`](https://github.com/pulumi/pulumi-awsx/blob/master/nodejs/awsx/autoscaling). A module that makes creating auto-scaling launch configurations and launch groups easier.   Note: given an awsx `ecs.Cluster`, the easiest way to create an auto-scaling group is to just call [`createAutoScalingGroup`](https://www.pulumi.com/docs/reference/pkg/nodejs/pulumi/awsx/autoscaling/#creating-an-autoscalinggroup) on it.  This will produce an auto-scaling group already property configured for that cluster.
+* [`ecs`](https://github.com/pulumi/pulumi-awsx/blob/master/awsx/ecs).  A module that makes it easy to create and configure clusters, tasks and services for running containers. Convenience resources are created to make the common tasks of creating EC2 or Fargate services and tasks much simpler.
 
-* [`ec2`](https://github.com/pulumi/pulumi-awsx/blob/master/nodejs/awsx/ec2).  A module that makes it easier to work with your AWS network, subnets, and security groups.  By default, the resources in the package follow the [AWS Best Practices](
-  https://aws.amazon.com/answers/networking/aws-single-vpc-design/), but can be configured as desired in whatever ways you want.  Most commonly, this package is used to acquire the default Vpc for a region (using [`Vpc.getDefault`](https://www.pulumi.com/docs/reference/pkg/nodejs/pulumi/awsx/ec2/#the-default-vpc)).  However, it can also be used to easily create or augment an existing Vpc.
+* [`lb`](https://github.com/pulumi/pulumi-awsx/tree/master/awsx/lb).  A module for simply setting up [Elastic Load Balancing](https://aws.amazon.com/elasticloadbalancing/). This module provides convenient ways to set up either `Network` or `Application` load balancers, along with the appropriate ELB Target Groups and Listeners in order to have a high availability, automatically-scaled service.  These ELB components also work well with the other awsx components.  For example, an `lb.defaultTarget` can be passed in directly as the `portMapping` target of an `ecs.FargateService`.
 
-* [`ecs`](https://github.com/pulumi/pulumi-awsx/blob/master/nodejs/awsx/ecs).  A module that makes it easy to create and configure clusters, tasks and services for running containers. Convenience resources are created to make the common tasks of creating EC2 or Fargate services and tasks much simpler.
+The library also exposes the classic AWSx (the previous TypeScript) implementation. You can find these modules in the `classic` namespace. These modules include:
 
-* [`lb`](https://github.com/pulumi/pulumi-awsx/tree/master/nodejs/awsx/lb).  A module for simply setting up [Elastic Load Balancing](https://aws.amazon.com/elasticloadbalancing/). This module provides convenient ways to set up either `Network` or `Application` load balancers, along with the appropriate ELB Target Groups and Listeners in order to have a high availability, automatically-scaled service.  These ELB components also work well with the other awsx components.  For example, an [`lb.TargetGroup`](https://www.pulumi.com/docs/reference/pkg/nodejs/pulumi/awsx/lb/#TargetGroup) can be passed in directly as the `portMapping` target of an [`ecs.Container`](https://www.pulumi.com/docs/reference/pkg/nodejs/pulumi/awsx/ecs/#clusters).
+* `apigateway`
+* `autoscaling`
+* `ecs`
+* `ec2`
+* `cloudwatch`
+* `lb`
+* `cloudtrail`
 
-* ~~`Network`~~. Deprecated.  Use [ec2.VPC](https://www.pulumi.com/docs/reference/pkg/nodejs/pulumi/awsx/ec2/#custom-vpcs) instead.
+### Installing
 
-* ~~`Cluster`~~. Deprecated.  Use [ecs.Cluster](https://www.pulumi.com/docs/reference/pkg/nodejs/pulumi/awsx/ecs/#clusters) instead.
+## Installing
 
+This package is available in many languages in the standard packaging formats.
 
-### Installing
+### Node.js (Java/TypeScript)
 
-This package is available in JavaScript/TypeScript for use with Node.js.  Install it using either `npm`:
+To use from JavaScript or TypeScript in Node.js, install using either `npm`:
 
     $ npm install @pulumi/awsx
 
@@ -39,7 +52,29 @@ or `yarn`:
 
     $ yarn add @pulumi/awsx
 
-### Reference
+### Python
+
+To use from Python, install using `pip`:
+
+    $ pip install pulumi_awsx
+
+### Go
+
+To use from Go, use `go get` to grab the latest version of the library
+
+    $ go get github.com/pulumi/pulumi-awsx/sdk
+
+### .NET
+
+To use from .NET, install using `dotnet add package`:
+
+    $ dotnet add package Pulumi.Awsx
+
+### Configuration
+
+The configuration options available for this provider  mirror those of the [Pulumi AWS Classic Provider](https://github.com/pulumi/pulumi-aws#configuration)
+
+## Reference
 
-For detailed reference documentation, please visit [the API docs](
-https://pulumi.io/reference/pkg/nodejs/@pulumi/awsx/index.html).
+For further information, visit [AWS in the Pulumi Registry](https://www.pulumi.com/registry/packages/awsx/)
+or for detailed API reference documentation, visit [AWS API Docs in the Pulumi Registry](https://www.pulumi.com/registry/packages/awsx/api-docs/).


Test patch:
diff --git a/awsx/ec2/vpc.test.ts b/awsx/ec2/vpc.test.ts
index 37161175..49f98065 100644
--- a/awsx/ec2/vpc.test.ts
+++ b/awsx/ec2/vpc.test.ts
@@ -12,43 +12,60 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import { getOverlappingSubnets } from ".";
 import { NatGatewayStrategyInputs, SubnetTypeInputs } from "../schema-types";
+import { getSubnetSpecs } from "./subnetDistributor";
 import {
     compareSubnetSpecs,
+    OverlappingSubnet,
     shouldCreateNatGateway,
     validateEips,
     validateNatGatewayStrategy,
+    validateSubnets,
 } from "./vpc";
 
 describe("validateEips", () => {
     it("should throw an exception if NAT Gateway strategy is None and EIPs are supplied", () => {
-        expect(() => validateEips("None", ["abc123"]))
-            .toThrowError("cannot be specified");
+        expect(() => validateEips("None", ["abc123"])).toThrowError(
+            "cannot be specified",
+        );
     });
 
     it("should throw an exception if NAT Gateway strategy is Single and more than 1 EIP is supplied", () => {
-        expect(() => validateEips("Single", ["abc123", "def456"]))
-            .toThrowError("Exactly one");
+        expect(() => validateEips("Single", ["abc123", "def456"])).toThrowError(
+            "Exactly one",
+        );
     });
 
     it("should throw an exception if NAT Gateway strategy is OnePerAz and too few EIPs are supplied", () => {
-        expect(() => validateEips("OnePerAz", ["abc123", "def456"], ["us-east-1a", "us-east-1b", "us-east-1c"]))
-            .toThrowError("must match the number");
+        expect(() =>
+            validateEips(
+                "OnePerAz",
+                ["abc123", "def456"],
+                ["us-east-1a", "us-east-1b", "us-east-1c"],
+            ),
+        ).toThrowError("must match the number");
     });
 
     it("should throw an exception if NAT Gateway strategy is OnePerAz and too many EIPs are supplied", () => {
-        expect(() => validateEips("OnePerAz", ["abc123", "def456", "ghi789"], ["us-east-1a", "us-east-1b"]))
-            .toThrowError("must match the number");
+        expect(() =>
+            validateEips(
+                "OnePerAz",
+                ["abc123", "def456", "ghi789"],
+                ["us-east-1a", "us-east-1b"],
+            ),
+        ).toThrowError("must match the number");
     });
 });
 
 describe("validateNatGatewayStrategy", () => {
     const runTest = (
         strategy: NatGatewayStrategyInputs,
-        inputs: SubnetTypeInputs[], expectException: boolean,
+        inputs: SubnetTypeInputs[],
+        expectException: boolean,
         expectedMessage?: string,
     ) => {
-        const specs = inputs.map(x => {
+        const specs = inputs.map((x) => {
             return {
                 type: x,
                 subnetName: "dummy",
@@ -58,80 +75,233 @@ describe("validateNatGatewayStrategy", () => {
         });
 
         if (expectException) {
-            expect(() => validateNatGatewayStrategy(strategy, specs))
-                .toThrowError(expectedMessage);
+            expect(() =>
+                validateNatGatewayStrategy(strategy, specs),
+            ).toThrowError(expectedMessage);
         } else {
-            expect(() => validateNatGatewayStrategy(strategy, specs))
-                .not.toThrowError();
+            expect(() =>
+                validateNatGatewayStrategy(strategy, specs),
+            ).not.toThrowError();
         }
     };
 
-    describe
-        .each<NatGatewayStrategyInputs>([
-            "OnePerAz",
-            "Single",
-        ])("strategy is OnePerAz or Single", (strategy: NatGatewayStrategyInputs) => {
-            it("should succeed if there's public and private subnets",
-                () => runTest(strategy, ["Public", "Private"], false));
+    describe.each<NatGatewayStrategyInputs>(["OnePerAz", "Single"])(
+        "strategy is OnePerAz or Single",
+        (strategy: NatGatewayStrategyInputs) => {
+            it("should succeed if there's public and private subnets", () =>
+                runTest(strategy, ["Public", "Private"], false));
 
-            it("should throw an exception if there are only isolated subnets",
-                () => runTest(strategy, ["Isolated"], true, "both private and public subnets"));
+            it("should throw an exception if there are only isolated subnets", () =>
+                runTest(
+                    strategy,
+                    ["Isolated"],
+                    true,
+                    "both private and public subnets",
+                ));
 
-            it("should throw an exception if there are only private subnets",
-                () => runTest(strategy, ["Private"], true, "both private and public subnets"));
+            it("should throw an exception if there are only private subnets", () =>
+                runTest(
+                    strategy,
+                    ["Private"],
+                    true,
+                    "both private and public subnets",
+                ));
 
-            it("should throw an exception if there are only public subnets",
-                () => runTest(strategy, ["Public"], true, "both private and public subnets"));
-        });
+            it("should throw an exception if there are only public subnets", () =>
+                runTest(
+                    strategy,
+                    ["Public"],
+                    true,
+                    "both private and public subnets",
+                ));
+        },
+    );
 
     describe("strategy is None", () => {
-        it("should throw an exception if any private subnets are specified",
-            () => runTest("None", ["Private"], true, "cannot be 'None'"));
+        it("should throw an exception if any private subnets are specified", () =>
+            runTest("None", ["Private"], true, "cannot be 'None'"));
 
-        it("should succeed if only public and isolated subnets are specified",
-            () => runTest("None", ["Public", "Isolated"], false));
+        it("should succeed if only public and isolated subnets are specified", () =>
+            runTest("None", ["Public", "Isolated"], false));
     });
 });
 
 describe("shouldCreateNatGateway", () => {
-    describe
-        .each([
-            { strategy: "OnePerAz", numGateways: 0, azIndex: 0, expected: true },
-            { strategy: "OnePerAz", numGateways: 1, azIndex: 0, expected: false },
-            { strategy: "OnePerAz", numGateways: 1, azIndex: 1, expected: true },
-            { strategy: "OnePerAz", numGateways: 2, azIndex: 1, expected: false },
-            { strategy: "Single", numGateways: 0, azIndex: 0, expected: true },
-            { strategy: "Single", numGateways: 1, azIndex: 0, expected: false },
-            { strategy: "Single", numGateways: 1, azIndex: 1, expected: false },
-            { strategy: "None", numGateways: 0, azIndex: 0, expected: false },
-        ])(
-            "based off strategy, number of NAT Gateways already created, and the current AZ index",
-            ({ strategy, numGateways, azIndex, expected }) => {
-                const result = shouldCreateNatGateway(strategy as NatGatewayStrategyInputs, numGateways, azIndex);
-                expect(result).toBe(expected);
-            });
+    describe.each([
+        { strategy: "OnePerAz", numGateways: 0, azIndex: 0, expected: true },
+        { strategy: "OnePerAz", numGateways: 1, azIndex: 0, expected: false },
+        { strategy: "OnePerAz", numGateways: 1, azIndex: 1, expected: true },
+        { strategy: "OnePerAz", numGateways: 2, azIndex: 1, expected: false },
+        { strategy: "Single", numGateways: 0, azIndex: 0, expected: true },
+        { strategy: "Single", numGateways: 1, azIndex: 0, expected: false },
+        { strategy: "Single", numGateways: 1, azIndex: 1, expected: false },
+        { strategy: "None", numGateways: 0, azIndex: 0, expected: false },
+    ])(
+        "based off strategy, number of NAT Gateways already created, and the current AZ index",
+        ({ strategy, numGateways, azIndex, expected }) => {
+            const result = shouldCreateNatGateway(
+                strategy as NatGatewayStrategyInputs,
+                numGateways,
+                azIndex,
+            );
+            expect(result).toBe(expected);
+        },
+    );
 });
 
 describe("compareSubnetSpecs", () => {
-    describe
-        .each([
-            { input: ["Private", "Public"], expected: ["Public", "Private"] },
-            { input: ["Isolated", "Public"], expected: ["Public", "Isolated"] },
-            { input: ["Private", "Public", "Public"], expected: ["Public", "Public", "Private"] },
-            { input: ["Isolated", "Public", "Private"], expected: ["Public", "Private", "Isolated"] },
-            { input: ["Private", "Public", "Public"], expected: ["Public", "Public", "Private"] },
-        ])("should sort Public first, then Private, then Isolated", (data) => {
-            const specs = data.input.map(x => {
-                return {
-                    type: x as SubnetTypeInputs,
-                    azName: "dummy",
-                    subnetName: "dummy",
-                    cidrBlock: "dummy",
-                };
-            });
-
-            const result = specs.sort(compareSubnetSpecs).map(x => x.type);
-
-            expect(result).toEqual(data.expected.map(x => x as SubnetTypeInputs));
+    describe.each([
+        { input: ["Private", "Public"], expected: ["Public", "Private"] },
+        { input: ["Isolated", "Public"], expected: ["Public", "Isolated"] },
+        {
+            input: ["Private", "Public", "Public"],
+            expected: ["Public", "Public", "Private"],
+        },
+        {
+            input: ["Isolated", "Public", "Private"],
+            expected: ["Public", "Private", "Isolated"],
+        },
+        {
+            input: ["Private", "Public", "Public"],
+            expected: ["Public", "Public", "Private"],
+        },
+    ])("should sort Public first, then Private, then Isolated", (data) => {
+        const specs = data.input.map((x) => {
+            return {
+                type: x as SubnetTypeInputs,
+                azName: "dummy",
+                subnetName: "dummy",
+                cidrBlock: "dummy",
+            };
         });
+
+        const result = specs.sort(compareSubnetSpecs).map((x) => x.type);
+
+        expect(result).toEqual(data.expected.map((x) => x as SubnetTypeInputs));
+    });
+});
+
+describe("getOverlappingSubnets", () => {
+    const subnet1 = {
+        cidrBlock: "10.0.0.0/16",
+        subnetName: "subnet1",
+    };
+
+    // Named thus because it's contained in subnet1:
+    const subnet1a = {
+        cidrBlock: "10.0.0.0/24",
+        subnetName: "subnet1",
+    };
+
+    const subnet2 = {
+        cidrBlock: "10.1.0.0/16",
+        subnetName: "subnet2",
+    };
+
+    const subnet2a = {
+        cidrBlock: "10.1.0.0/20",
+        subnetName: "subnet2",
+    };
+
+    const subnet3 = {
+        cidrBlock: "10.2.0.0/16",
+        subnetName: "subnet2",
+    };
+
+    it("should return nothing for subnets that do not overlap", () => {
+        expect(getOverlappingSubnets([subnet1, subnet2])).toEqual([]);
+    });
+
+    it("should return both subnets if they overlap", () => {
+        expect(getOverlappingSubnets([subnet1, subnet1a])).toEqual([
+            subnet1,
+            subnet1a,
+        ]);
+    });
+
+    it("should return all subnets that overlap with each other in a deterministic order when give 5 subnets", () => {
+        expect(
+            getOverlappingSubnets([
+                subnet1,
+                subnet2,
+                subnet3,
+                subnet1a,
+                subnet2a,
+            ]),
+        ).toEqual([subnet1, subnet2, subnet1a, subnet2a]);
+
+        expect(
+            getOverlappingSubnets([
+                subnet3,
+                subnet2a,
+                subnet1,
+                subnet1a,
+                subnet2,
+            ]),
+        ).toEqual([subnet2a, subnet1, subnet1a, subnet2]);
+    });
+
+    // This is technically an integration test, but it's probably better to just call getSubnetSpecs than it is to restate the default subnet specs as a literal:
+    it("should return no overlapping subnets for the default subnet specs for this component", () => {
+        const defaultSpecs = getSubnetSpecs("dummy", "10.0.0.0/16", [
+            "us-east-1a",
+            "us-east-1b",
+            "us-east-1c",
+        ]);
+        expect(getOverlappingSubnets(defaultSpecs)).toEqual([]);
+    });
+});
+
+describe("validateSubnets", () => {
+    it("should not throw an error no overlapping subnets are returned", () => {
+        const subnets = [
+            {
+                type: "Public" as SubnetTypeInputs,
+                cidrBlock: "10.0.0.0/16",
+                subnetName: "subnet1",
+                azName: "us-east-1a",
+            },
+            {
+                type: "Private" as SubnetTypeInputs,
+                cidrBlock: "10.0.1.0/16",
+                subnetName: "subnet2",
+                azName: "us-east-1a",
+            },
+        ];
+
+        const mockFunc = (input: OverlappingSubnet[]) => {
+            return [];
+        };
+
+        expect(() => validateSubnets(subnets, mockFunc)).not.toThrow();
+    });
+
+    it("should have a detailed error when overlapping subnets are returned", () => {
+        const subnets = [
+            {
+                type: "Public" as SubnetTypeInputs,
+                cidrBlock: "10.0.0.0/16",
+                subnetName: "subnet1",
+                azName: "us-east-1a",
+            },
+            {
+                type: "Private" as SubnetTypeInputs,
+                cidrBlock: "10.0.0.0/24",
+                subnetName: "subnet2",
+                azName: "us-east-1a",
+            },
+        ];
+
+        const mockFunc = (input: OverlappingSubnet[]) => {
+            return subnets;
+        };
+
+        // TODO: Change this assertion from a literal string to a regex with set of words, all of which must match:
+        const msg =
+            "The following subnets overlap with at least one other subnet. Make the CIDR for the VPC larger, reduce the size of the subnets per AZ, or use less Availability Zones:\n\n" +
+            "1. subnet1: 10.0.0.0/16\n" +
+            "2. subnet2: 10.0.0.0/24\n";
+
+        expect(() => validateSubnets(subnets, mockFunc)).toThrow(msg);
+    });
 });
diff --git a/examples/examples_dotnet_test.go b/examples/examples_dotnet_test.go
index 4b02e1d4..8a542990 100644
--- a/examples/examples_dotnet_test.go
+++ b/examples/examples_dotnet_test.go
@@ -32,6 +32,16 @@ func TestAccTrailDotnet(t *testing.T) {
 	integration.ProgramTest(t, &test)
 }
 
+func TestAccEcsDotnet(t *testing.T) {
+	test := getDotnetBaseOptions(t).
+		With(integration.ProgramTestOptions{
+			RunUpdateTest: false,
+			Dir:           filepath.Join(getCwd(t), "ecs", "dotnet"),
+		})
+
+	integration.ProgramTest(t, &test)
+}
+
 func getDotnetBaseOptions(t *testing.T) integration.ProgramTestOptions {
 	region := getEnvRegion(t)
 	base := getBaseOptions(t)
diff --git a/examples/examples_python_test.go b/examples/examples_python_test.go
index 9ee3e1f8..3e95b1bb 100644
--- a/examples/examples_python_test.go
+++ b/examples/examples_python_test.go
@@ -32,6 +32,16 @@ func TestAccTrailPython(t *testing.T) {
 	integration.ProgramTest(t, &test)
 }
 
+func TestAccEcsPython(t *testing.T) {
+	test := getPythonBaseOptions(t).
+		With(integration.ProgramTestOptions{
+			RunUpdateTest: false,
+			Dir:           filepath.Join(getCwd(t), "ecs", "python"),
+		})
+
+	integration.ProgramTest(t, &test)
+}
+
 func getPythonBaseOptions(t *testing.T) integration.ProgramTestOptions {
 	region := getEnvRegion(t)
 	base := getBaseOptions(t)