Repository: pulumi/pulumi-azure-native
Pull request #4032: Add an integration test for parameterization and moving from default resource to parameterized
Author: login: thomas11
Created: 2025-03-17T16:59:15Z | Merged: 2025-03-18T14:27:57Z

This integration test proves that
1. `pulumi package add` works and produces a usable SDK,
2. users can migrate from a default resource (here, a storage account) to a parameterized one without replacement, and
3. the default provider and parameterized ones can be used in the same program without issues.

**This PR contains and requires #4030, otherwise the test will fail.**


Diff:
diff --git a/examples/examples_nodejs_test.go b/examples/examples_nodejs_test.go
index 71c56f62ff9a..cc4a19ab23f1 100644
--- a/examples/examples_nodejs_test.go
+++ b/examples/examples_nodejs_test.go
@@ -7,11 +7,17 @@ import (
 	"context"
 	"encoding/json"
 	"os"
+	"os/exec"
 	"path/filepath"
 	"strings"
 	"testing"
 
+	"github.com/pulumi/providertest/pulumitest"
+	"github.com/pulumi/providertest/pulumitest/opttest"
 	"github.com/pulumi/pulumi/pkg/v3/testing/integration"
+	"github.com/pulumi/pulumi/sdk/v3/go/auto/debug"
+	"github.com/pulumi/pulumi/sdk/v3/go/auto/optrefresh"
+	"github.com/pulumi/pulumi/sdk/v3/go/auto/optup"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 
@@ -412,13 +418,103 @@ func TestAccPIMRoleManagementPolicies(t *testing.T) {
 	integration.ProgramTest(t, &test)
 }
 
+// This test uses pulumitest instead of integration.ProgramTest because it needs to parameterize the provider in between
+// steps. The high-level test sequence is:
+// 1. Run `pulumi up` on the program with the default provider and default API version.
+// 2. Parameterize with a different API version to generate the new SDK and install it in package.json.
+// 3. The program using the new parameterized provider already exist in folder "2-...", update the test source with it.
+// 4. Run `pulumi refresh` and `pulumi up` on the program with the new provider and new API version.
+// 5. Copy the default version of the program back and run refresh+up again.
+// There should be no changes at any point if the default and the parameterized resources are aliased correctly.
+func TestParameterizeApiVersion(t *testing.T) {
+	cwd := getCwd(t)
+	location := getLocation(t)
+
+	pt := pulumitest.NewPulumiTest(t,
+		filepath.Join(cwd, "parameterize"),
+		opttest.YarnLink("@pulumi/azure-native"),
+	)
+	pt.SetConfig(t, "azure-native:location", location)
+
+	pt.Up(t)
+
+	// generate the new SDK and install it in package.json
+	pulumiPackageAdd(t, pt, "storage", "v20240101")
+
+	pt.UpdateSource(t, cwd, "parameterize", "2-explicit-version")
+	pt.Refresh(t, optrefresh.ExpectNoChanges())
+	pt.Up(t, optup.ExpectNoChanges())
+
+	// Pending #4019
+	// pt.UpdateSource(t, cwd, "parameterize", "3-back-to-default")
+	// pt.Refresh(t, optrefresh.ExpectNoChanges())
+	// pt.Up(t, optup.ExpectNoChanges())
+
+	pt.Destroy(t)
+}
+
+// A helper to use with optup.DebugLogging() and friends to get verbose logging.
+func debugLogging() debug.LoggingOptions {
+	var level uint = 9
+	return debug.LoggingOptions{
+		LogLevel:      &level,
+		Debug:         true,
+		FlowToPlugins: true,
+		LogToStdErr:   true,
+	}
+}
+
+func pulumiPackageAdd(t *testing.T, pt *pulumitest.PulumiTest, args ...string) {
+	var provider string
+	if _, debugMode := os.LookupEnv("PULUMI_DEBUG_PROVIDERS"); debugMode {
+		provider = "azure-native"
+	} else {
+		providerBinaryPath, err := exec.LookPath("pulumi-resource-azure-native")
+		require.NoError(t, err)
+		provider, err = filepath.Abs(providerBinaryPath)
+		require.NoError(t, err)
+	}
+
+	runPulumiPackageAdd(t, pt, provider, args...)
+}
+
+// runPulumiPackageAdd runs `pulumi package add` with the given args. `provider` can be the name of a provider for the
+// engine to resolve, or a path to a provider binary.
+func runPulumiPackageAdd(
+	t *testing.T,
+	pt *pulumitest.PulumiTest,
+	provider string,
+	args ...string,
+) {
+	ctx := context.Background()
+	allArgs := append([]string{"package", "add", provider}, args...)
+	stdout, stderr, exitCode, err := pt.CurrentStack().Workspace().PulumiCommand().Run(
+		ctx,
+		pt.WorkingDir(),
+		nil, /* reader */
+		nil, /* additionalOutput */
+		nil, /* additionalErrorOutput */
+		nil, /* additionalEnv */
+		allArgs...,
+	)
+	if err != nil || exitCode != 0 {
+		t.Fatalf("Failed to run pulumi package add\nExit code: %d\nError: %v\n%s\n%s",
+			exitCode, err, stdout, stderr)
+	}
+
+	sdkPath := filepath.Join(pt.WorkingDir(), "sdks", "azure-native_storage_v20240101")
+	if _, err := os.Stat(sdkPath); os.IsNotExist(err) {
+		t.Fatalf("generated SDK directory not found at path: %s", sdkPath)
+	}
+}
+
 func getJSBaseOptions(t *testing.T) integration.ProgramTestOptions {
 	base := getBaseOptions(t)
 	baseJS := base.With(integration.ProgramTestOptions{
 		Dependencies: []string{
 			"@pulumi/azure-native",
 		},
-		// Show the diff instead of just the non-actionable error: no changes were expected but changes were proposed"
+		// Show the diff instead of just the non-actionable error "no changes were expected but changes were proposed"
 		PreviewCommandlineFlags: []string{"--diff"},
 	})
 
diff --git a/examples/go.mod b/examples/go.mod
index 226abe9734fc..4bd3512cdda0 100644
--- a/examples/go.mod
+++ b/examples/go.mod
@@ -6,6 +6,7 @@ require (
 	github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0
 	github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/authorization/armauthorization/v2 v2.2.0
 	github.com/pulumi/examples/misc/test v0.0.0-20240224092907-9f1d92c3e166
+	github.com/pulumi/providertest v0.1.5
 	github.com/pulumi/pulumi-azure-native-sdk/resources/v2 v2.28.0
 	github.com/pulumi/pulumi-azure-native-sdk/servicebus/v2 v2.28.0
 	github.com/pulumi/pulumi/pkg/v3 v3.144.1
@@ -65,6 +66,7 @@ require (
 	github.com/edsrzf/mmap-go v1.1.0 // indirect
 	github.com/emirpasic/gods v1.18.1 // indirect
 	github.com/felixge/httpsnoop v1.0.4 // indirect
+	github.com/fsnotify/fsnotify v1.7.0 // indirect
 	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
 	github.com/go-git/go-billy/v5 v5.5.0 // indirect
 	github.com/go-git/go-git/v5 v5.12.0 // indirect
@@ -119,6 +121,7 @@ require (
 	github.com/muesli/reflow v0.3.0 // indirect
 	github.com/muesli/termenv v0.15.2 // indirect
 	github.com/natefinch/atomic v1.0.1 // indirect
+	github.com/nxadm/tail v1.4.11 // indirect
 	github.com/opentracing/basictracer-go v1.1.0 // indirect
 	github.com/opentracing/opentracing-go v1.2.0 // indirect
 	github.com/pgavlin/fx v0.1.6 // indirect
@@ -177,6 +180,7 @@ require (
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
 	google.golang.org/grpc v1.67.1 // indirect
 	google.golang.org/protobuf v1.35.1 // indirect
+	gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 	lukechampine.com/frand v1.4.2 // indirect
diff --git a/examples/go.sum b/examples/go.sum
index 47f36e3ca899..522da9e894a1 100644
--- a/examples/go.sum
+++ b/examples/go.sum
@@ -144,6 +144,15 @@ github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
 github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
 github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
+github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
+github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
+github.com/gkampitakis/ciinfo v0.3.0 h1:gWZlOC2+RYYttL0hBqcoQhM7h1qNkVqvRCV1fOvpAv8=
+github.com/gkampitakis/ciinfo v0.3.0/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo=
+github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M=
+github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk=
+github.com/gkampitakis/go-snaps v0.4.9 h1:x6+GEQeYWC+cnLNsHK5uXXgEQADmlH/1EqMrjfXjzk8=
+github.com/gkampitakis/go-snaps v0.4.9/go.mod h1:8HW4KX3JKV8M0GSw69CvT+Jqhd1AlBPMPpBfjBI3bdY=
 github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
 github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
 github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
@@ -314,6 +323,8 @@ github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo
 github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
 github.com/natefinch/atomic v1.0.1 h1:ZPYKxkqQOx3KZ+RsbnP/YsgvxWQPGxjC0oBt2AhwV0A=
 github.com/natefinch/atomic v1.0.1/go.mod h1:N/D/ELrljoqDyT3rZrsUmtsuzvHkeB/wWjHV22AZRbM=
+github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
+github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
 github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
 github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
 github.com/opentracing/basictracer-go v1.1.0 h1:Oa1fTSBvAl8pa3U+IJYqrKm0NALwH9OsgwOqDv4xJW0=
@@ -343,6 +354,8 @@ github.com/pulumi/esc v0.10.0 h1:jzBKzkLVW0mePeanDRfqSQoCJ5yrkux0jIwAkUxpRKE=
 github.com/pulumi/esc v0.10.0/go.mod h1:2Bfa+FWj/xl8CKqRTWbWgDX0SOD4opdQgvYSURTGK2c=
 github.com/pulumi/examples/misc/test v0.0.0-20240224092907-9f1d92c3e166 h1:fIZjnWYQtiD4vpiqn2SKkMUDv54sfQnG6q5n3qtz0E8=
 github.com/pulumi/examples/misc/test v0.0.0-20240224092907-9f1d92c3e166/go.mod h1:vKBM5kVs1wlhtx2nuLHycEwEaNFD01pSAUyUUdgc/zo=
+github.com/pulumi/providertest v0.1.5 h1:35PH+SBNzWjkfO/rP5mSjrfuTUJZ5nxeo2Cb845EoYU=
+github.com/pulumi/providertest v0.1.5/go.mod h1:bU07oPwpgNjGuuIT6CPnI/eLXaGHqhypFrxX5ZsABo4=
 github.com/pulumi/pulumi-azure-native-sdk/resources/v2 v2.28.0 h1:Yyg3DJvQ5NXCI07otHvrznlGh6hmwJSbPzfMLohNr+s=
 github.com/pulumi/pulumi-azure-native-sdk/resources/v2 v2.28.0/go.mod h1:82ILMDA7qO9hg0VNgOsdOuVZqWK1LyaYA6CCGAr/OUo=
 github.com/pulumi/pulumi-azure-native-sdk/servicebus/v2 v2.28.0 h1:BRLIeyy7s5grcIXZ+WvN5+C/BfJktPwPi91CcJkMfsw=
@@ -375,6 +388,8 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
 github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
+github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
+github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
 github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@@ -397,6 +412,14 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
 github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 github.com/texttheater/golang-levenshtein v1.0.1 h1:+cRNoVrfiwufQPhoMzB6N0Yf/Mqajr6t1lOv8GyGE2U=
 github.com/texttheater/golang-levenshtein v1.0.1/go.mod h1:PYAKrbF5sAiq9wd+H82hs7gNaen0CplQ9uvm6+enD/8=
+github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
+github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
+github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
+github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
+github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
 github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o=
 github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
 github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg=
@@ -515,6 +538,7 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -608,6 +632,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
 gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/examples/parameterize/.gitignore b/examples/parameterize/.gitignore
new file mode 100644
index 000000000000..c6958891dd23
--- /dev/null
+++ b/examples/parameterize/.gitignore
@@ -0,0 +1,2 @@
+/bin/
+/node_modules/
diff --git a/examples/parameterize/2-explicit-version/index.ts b/examples/parameterize/2-explicit-version/index.ts
new file mode 100644
index 000000000000..e19cc353251d
--- /dev/null
+++ b/examples/parameterize/2-explicit-version/index.ts
@@ -0,0 +1,16 @@
+import * as resources from "@pulumi/azure-native/resources";
+import * as storage_v20240101 from "@pulumi/azure-native_storage_v20240101";
+
+// Create an Azure Resource Group
+const resourceGroup = new resources.ResourceGroup("resourceGroup");
+
+// Create an Azure resource (Storage Account)
+const storageAccount = new storage_v20240101.storage.v20240101.StorageAccount("sa", {
+    resourceGroupName: resourceGroup.name,
+    sku: {
+        name: storage_v20240101.storage.v20240101.SkuName.Standard_LRS,
+    },
+    kind: storage_v20240101.storage.v20240101.Kind.StorageV2,
+});
+
+export const storageAccountName = storageAccount.name;
diff --git a/examples/parameterize/3-back-to-default/index.ts b/examples/parameterize/3-back-to-default/index.ts
new file mode 100644
index 000000000000..095c8b3375a6
--- /dev/null
+++ b/examples/parameterize/3-back-to-default/index.ts
@@ -0,0 +1,19 @@
+// Copyright 2025, Pulumi Corporation.
+
+import * as pulumi from "@pulumi/pulumi";
+import * as resources from "@pulumi/azure-native/resources";
+import * as storage from "@pulumi/azure-native/storage";
+
+// Create an Azure Resource Group
+const resourceGroup = new resources.ResourceGroup("resourceGroup");
+
+// Create an Azure resource (Storage Account)
+const storageAccount = new storage.StorageAccount("sa", {
+    resourceGroupName: resourceGroup.name,
+    sku: {
+        name: storage.SkuName.Standard_LRS,
+    },
+    kind: storage.Kind.StorageV2,
+});
+
+export const storageAccountName = storageAccount.name;
diff --git a/examples/parameterize/Pulumi.yaml b/examples/parameterize/Pulumi.yaml
new file mode 100644
index 000000000000..ded2598f80e1
--- /dev/null
+++ b/examples/parameterize/Pulumi.yaml
@@ -0,0 +1,10 @@
+name: parameterize
+description: A minimal Azure Native TypeScript Pulumi program
+runtime:
+  name: nodejs
+  options:
+    packagemanager: yarn
+config:
+  pulumi:tags:
+    value:
+      pulumi:template: azure-typescript
diff --git a/examples/parameterize/index.ts b/examples/parameterize/index.ts
new file mode 100644
index 000000000000..095c8b3375a6
--- /dev/null
+++ b/examples/parameterize/index.ts
@@ -0,0 +1,19 @@
+// Copyright 2025, Pulumi Corporation.
+
+import * as pulumi from "@pulumi/pulumi";
+import * as resources from "@pulumi/azure-native/resources";
+import * as storage from "@pulumi/azure-native/storage";
+
+// Create an Azure Resource Group
+const resourceGroup = new resources.ResourceGroup("resourceGroup");
+
+// Create an Azure resource (Storage Account)
+const storageAccount = new storage.StorageAccount("sa", {
+    resourceGroupName: resourceGroup.name,
+    sku: {
+        name: storage.SkuName.Standard_LRS,
+    },
+    kind: storage.Kind.StorageV2,
+});
+
+export const storageAccountName = storageAccount.name;
diff --git a/examples/parameterize/package.json b/examples/parameterize/package.json
new file mode 100644
index 000000000000..48a4840c81a9
--- /dev/null
+++ b/examples/parameterize/package.json
@@ -0,0 +1,12 @@
+{
+    "name": "parameterize",
+    "main": "index.ts",
+    "devDependencies": {
+        "@types/node": "^18",
+        "typescript": "^5.0.0"
+    },
+    "dependencies": {
+        "@pulumi/azure-native": "^2.0.0",
+        "@pulumi/pulumi": "^3.113.0"
+    }
+}
diff --git a/examples/parameterize/tsconfig.json b/examples/parameterize/tsconfig.json
new file mode 100644
index 000000000000..f960d5171527
--- /dev/null
+++ b/examples/parameterize/tsconfig.json
@@ -0,0 +1,18 @@
+{
+    "compilerOptions": {
+        "strict": true,
+        "outDir": "bin",
+        "target": "es2020",
+        "module": "commonjs",
+        "moduleResolution": "node",
+        "sourceMap": true,
+        "experimentalDecorators": true,
+        "pretty": true,
+        "noFallthroughCasesInSwitch": true,
+        "noImplicitReturns": true,
+        "forceConsistentCasingInFileNames": true
+    },
+    "files": [
+        "index.ts"
+    ]
+}


Reviews:
user: login: danielrbradley
body: Overall looks good 👍 

Looks like it includes the same changes as https://github.com/pulumi/pulumi-azure-native/pull/4030 .. won't repeat my comments on those bits.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-17T17:37:01Z

Review comments:
diff_hunk: @@ -412,13 +418,103 @@ func TestAccPIMRoleManagementPolicies(t *testing.T) {
 	integration.ProgramTest(t, &test)
 }
 
+// This test uses pulumitest instead of integration.ProgramTest because it needs to parameterize the provider in between
+// steps. The high-level test sequence is:
+// 1. Run `pulumi up` on the program with the default provider and default API version.
+// 2. Parameterize with a different API version to generate the new SDK and install it in package.json.
+// 3. The program using the new parameterized provider already exist in folder "2-...", update the test source with it.
+// 4. Run `pulumi refresh` and `pulumi up` on the program with the new provider and new API version.
+// 5. Copy the default version of the program back and run refresh+up again.
+// There should be no changes at any point if the default and the parameterized resources are aliased correctly.
+func TestParameterizeApiVersion(t *testing.T) {
+	cwd := getCwd(t)
+	location := getLocation(t)
+
+	pt := pulumitest.NewPulumiTest(t,
+		filepath.Join(cwd, "parameterize"),
+		opttest.YarnLink("@pulumi/azure-native"),
+	)
+	pt.SetConfig(t, "azure-native:location", location)
+
+	pt.Up(t)
+
+	// generate the new SDK and install it in package.json
+	pulumiPackageAdd(t, pt, "storage", "v20240101")
+
+	pt.UpdateSource(t, cwd, "parameterize", "2-explicit-version")
+	pt.Refresh(t, optrefresh.ExpectNoChanges())
+	pt.Up(t, optup.ExpectNoChanges())
+
+	// Pending #4019
+	// pt.UpdateSource(t, cwd, "parameterize", "3-back-to-default")
+	// pt.Refresh(t, optrefresh.ExpectNoChanges())
+	// pt.Up(t, optup.ExpectNoChanges())
+
+	pt.Destroy(t)
+}
+
+// A helper to use with optup.DebugLogging() and friends to get verbose logging.
+func debugLogging() debug.LoggingOptions {
path: examples/examples_nodejs_test.go
user: login: danielrbradley
body: Unused?
created_at: 2025-03-17T17:33:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -412,13 +418,103 @@ func TestAccPIMRoleManagementPolicies(t *testing.T) {
 	integration.ProgramTest(t, &test)
 }
 
+// This test uses pulumitest instead of integration.ProgramTest because it needs to parameterize the provider in between
+// steps. The high-level test sequence is:
+// 1. Run `pulumi up` on the program with the default provider and default API version.
+// 2. Parameterize with a different API version to generate the new SDK and install it in package.json.
+// 3. The program using the new parameterized provider already exist in folder "2-...", update the test source with it.
+// 4. Run `pulumi refresh` and `pulumi up` on the program with the new provider and new API version.
+// 5. Copy the default version of the program back and run refresh+up again.
+// There should be no changes at any point if the default and the parameterized resources are aliased correctly.
+func TestParameterizeApiVersion(t *testing.T) {
+	cwd := getCwd(t)
+	location := getLocation(t)
+
+	pt := pulumitest.NewPulumiTest(t,
+		filepath.Join(cwd, "parameterize"),
+		opttest.YarnLink("@pulumi/azure-native"),
+	)
+	pt.SetConfig(t, "azure-native:location", location)
+
+	pt.Up(t)
+
+	// generate the new SDK and install it in package.json
+	pulumiPackageAdd(t, pt, "storage", "v20240101")
+
+	pt.UpdateSource(t, cwd, "parameterize", "2-explicit-version")
+	pt.Refresh(t, optrefresh.ExpectNoChanges())
+	pt.Up(t, optup.ExpectNoChanges())
+
+	// Pending #4019
+	// pt.UpdateSource(t, cwd, "parameterize", "3-back-to-default")
+	// pt.Refresh(t, optrefresh.ExpectNoChanges())
+	// pt.Up(t, optup.ExpectNoChanges())
+
+	pt.Destroy(t)
+}
+
+// A helper to use with optup.DebugLogging() and friends to get verbose logging.
+func debugLogging() debug.LoggingOptions {
+	var level uint = 9
+	return debug.LoggingOptions{
+		LogLevel:      &level,
+		Debug:         true,
+		FlowToPlugins: true,
+		LogToStdErr:   true,
+	}
+}
+
+func pulumiPackageAdd(t *testing.T, pt *pulumitest.PulumiTest, args ...string) {
+	var provider string
+	if _, debugMode := os.LookupEnv("PULUMI_DEBUG_PROVIDERS"); debugMode {
+		provider = "azure-native"
+	} else {
+		providerBinaryPath, err := exec.LookPath("pulumi-resource-azure-native")
+		require.NoError(t, err)
+		provider, err = filepath.Abs(providerBinaryPath)
+		require.NoError(t, err)
+	}
+
+	runPulumiPackageAdd(t, pt, provider, args...)
+}
+
+// runPulumiPackageAdd runs `pulumi package add` with the given args. `provider` can be the name of a provider for the
+// engine to resolve, or a path to a provider binary.
+func runPulumiPackageAdd(
+	t *testing.T,
+	pt *pulumitest.PulumiTest,
+	provider string,
+	args ...string,
+) {
+	ctx := context.Background()
+	allArgs := append([]string{"package", "add", provider}, args...)
+	stdout, stderr, exitCode, err := pt.CurrentStack().Workspace().PulumiCommand().Run(
path: examples/examples_nodejs_test.go
user: login: danielrbradley
body: That's pretty nice rather than just using the ambient `pulumi` binary 👍 
created_at: 2025-03-17T17:34:27Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-17T17:05:20Z
body: ### Does the PR have any schema changes?

Looking good! No breaking changes found.
No new resources/functions.
<!-- thollander/actions-comment-pull-request "schema-check" -->
author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-03-17T17:15:17Z
body: ## [Codecov](https://app.codecov.io/gh/pulumi/pulumi-azure-native/pull/4032?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pulumi) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 57.89%. Comparing base [(`1bf7e9b`)](https://app.codecov.io/gh/pulumi/pulumi-azure-native/commit/1bf7e9b5f6cef4afacc5d5a3f04b3e867a30d74a?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pulumi) to head [(`6667b3d`)](https://app.codecov.io/gh/pulumi/pulumi-azure-native/commit/6667b3d6b0e93f8463419f36122f1941f291cd2c?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pulumi).
> Report is 1 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           master    #4032   +/-   ##
=======================================
  Coverage   57.89%   57.89%           
=======================================
  Files          83       83           
  Lines       13422    13422           
=======================================
  Hits         7770     7770           
  Misses       5050     5050           
  Partials      602      602           
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/pulumi/pulumi-azure-native/pull/4032?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pulumi).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pulumi).

<details><summary>🚀 New features to boost your workflow: </summary>

- ❄ [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- 📦 [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: pulumi-bot
created_at: 2025-03-18T17:32:02Z
body: This PR has been shipped in [release v2.89.2](https://github.com/pulumi/pulumi-azure-native/releases/tag/v2.89.2).
author_association: CONTRIBUTOR