Migrate from classic Azure to Azure-Native
Pulumi currently has two providers to manage resources in Microsoft Azure: classic Pulumi Azure and native Pulumi Azure. This guide explains the differences between the two providers, our recommendations about using them, and a migration guide from the classic Azure provider to the native Azure provider.
Key Difference between Azure and Azure-Native
The classic Pulumi Azure provider is based on the Terraform AzureRM provider. Every resource in this provider is manually implemented using the Azure Go SDK. The API of each resource is defined by its developer and doesn’t have to match the Microsoft Azure API.
The native Pulumi Azure provider is based on the Open API specifications of Azure Resource Manager published by Microsoft. Pulumi generates resources automatically from those API specifications. This approach ensures higher quality and higher fidelity with the Azure platform. Every property of each resource is always represented in the SDKs.
Because of this difference, the SDKs of the two providers are not compatible with each other. This guide aims to help you understand and adopt the native provider in your Pulumi projects.
Configuration
Configurations of the native Azure provider and the classic Azure provider are similar. However, for the new provider, each setting has to start with azure-native
instead of azure
. For instance, the active subscription is configured with azure-native:subscriptionId
instead of azure:subscriptionId
.
You can find all the configuration settings in the Configuration section of Azure-Native.
Use the native Azure provider for new projects
The native Azure provider for Pulumi is generally available. We recommend using it for any new Pulumi project targeting Microsoft Azure.
Mix and match in the same project
It is possible to use both Azure and Azure-Native providers in the same Pulumi program. You can reference both packages, use a mix of resources from both of them, and flow outputs of any resource to any other resource.
For example, the following snippet defines a Resource Group with Pulumi Azure and a Storage Account with Pulumi Azure-Native. The snippets below are shown in TypeScript but the same process applies to all other runtimes.
import * as azure from '@pulumi/azure';
import * as azure_native from '@pulumi/azure-native';
const rg = new azure.core.ResourceGroup("my-rg");
const account = new azure_native.storage.StorageAccount("sa", {
resourceGroupName: rg.name,
sku: {
name: azure_native.storage.SkuName.Standard_LRS,
},
kind: azure_native.storage.Kind.StorageV2,
});
This approach is practical as the first step of adopting the native Azure provider in existing projects relying on the classic Azure provider. The classic Azure provider isn’t deprecated, and we will keep supporting it indefinitely. You can keep using it in your existing infrastructure while embracing the native Azure provider for any newly created resources.
Move resources from classic Azure to Azure-Native
As explained above, the two provider APIs are not compatible with each other. Therefore, there is no automatic way to migrate existing resources from classic Azure to Azure-Native.
In this section, we give an example of migrating a Pulumi program from Azure to Azure-Native. We start with two resources managed by the old provider and move them one-by-one to the new provider. The process is mostly manual, but it does not require any disruption in your actual Microsoft Azure cloud environment.
Let’s start with the following existing program which defines two Azure resources. Imagine this is your existing stack with resources deployed to your cloud account.
import * as azure from '@pulumi/azure';
const resourceGroup = new azure.core.ResourceGroup("my-rg");
const account = new azure.storage.Account("storage", {
resourceGroupName: resourceGroup.name,
accountTier: "Standard",
accountReplicationType: "LRS",
});
Reference Azure-Native
Begin the migration by referencing the Azure-Native package
npm install @pulumi/azure-native
Now, open your stack configuration file (e.g., Pulumi.dev.yaml). Copy all settings that start with azure: and redefine them with the azure-native:
prefix. In a simple case, the result may look like this
config:
azure:location: westus
azure-native:location: westus
Migrate the Resource Group
Let’s run through the steps to migrate our first resource: the resource group.
Identify the type of the new resource in the native provider. In our case, it’s going to be a ResourceGroup resource in the resources module. Note that both names can be different from the names in the Azure provider. We can find the new resource in the API Reference docs.
Scroll down the API Reference doc to the Import section and copy the import command:
pulumi import azure-native:resources/latest:ResourceGroup my-resource-group /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/my-resource-group
Run the command
pulumi stack export
and open the resulting JSON in a text editor. Find the entry with typeazure:core/resourceGroup:ResourceGroup
and note itsid
andurn
property values. Theid
should look like this:/subscriptions/01234567-89ab-cdef-0123-456789abcdef/resourceGroups/my-rgca05c9f8
and theurn
should be likeurn:pulumi:dev::ts::azure:core/resourceGroup:ResourceGroup::my-rg
Use this ID and your desired logical resource name to compose the import command. For the example above, it will be
pulumi import azure-native:resources/latest:ResourceGroup my-rg /subscriptions/01234567-89ab-cdef-0123-456789abcdef/resourceGroups/my-rgca05c9f8
Run the command to start managing the existing resource group with the native Azure provider and confirm the import operation. The following output should be printed:
Importing (dev) Type Name Status pulumi:pulumi:Stack ts-dev = └─ azure-native:resources:ResourceGroup my-rg imported Resources: = 1 imported 4 unchanged Duration: 7s Please copy the following code into your Pulumi application. Not doing so will cause Pulumi to report that an update will happen on the next update command. Please note that the imported resources are marked as protected. To destroy them you will need to remove the `protect` option and run `pulumi update` *before* the destroy will take effect. import * as pulumi from "@pulumi/pulumi"; import * as azure_native from "@pulumi/azure-native"; const my_rg = new azure_native.resources.ResourceGroup("my-rg", { location: "westeurope", resourceGroupName: "my-rgca05c9f8", }, { protect: true, });
Copy the code from the output to your program. Now the resource is managed by the native Azure provider.
Adjust the program so that the Storage Account depends on the new resource
my_rg
instead of the old resourceresourceGroup
:import * as azure from '@pulumi/azure'; import * as azure_native from "@pulumi/azure-native"; const resourceGroup = new azure.core.ResourceGroup("my-rg"); const my_rg = new azure_native.resources.ResourceGroup("my-rg", { location: "westus", resourceGroupName: "my-rgca05c9f8", }, { protect: true, }); const account = new azure.storage.Account("storage", { resourceGroupName: my_rg.name, accountTier: "Standard", accountReplicationType: "LRS", });
Run
pulumi up
and confirm the action, even though the preview shows no changes. This will override the dependency from the Storage Account to the new Resource Group resource definition.Now, we are ready to remove the state of the resource group managed by the classic Azure provider. Run the following command with the
urn
noted at step 3:pulumi state delete urn:pulumi:dev::ts::azure:core/resourceGroup:ResourceGroup::my-rg warning: This command will edit your stack's state directly. Confirm? Yes Resource deleted successfully
Finally, remove the code for the old resourceGroup resource. You may also refactor the code to use the same name for the new resource and unprotect it. Feel free to remove the location attribute if it matches your stack configuration. The resulting code is shown below:
import * as azure from '@pulumi/azure'; import * as azure_native from "@pulumi/azure-native"; const resourceGroup = new azure_native.resources.ResourceGroup("my-rg", { resourceGroupName: "my-rgca05c9f9", }); const account = new azure.storage.Account("storage", { resourceGroupName: resourceGroup.name, accountTier: "Standard", accountReplicationType: "LRS", });
Migrate the Storage Account
Follow the same steps to migrate the storage account.
Locate the resource in the API docs.
Compose and run the import command:
pulumi import azure-native:storage/latest:StorageAccount storage /subscriptions/01234567-89ab-cdef-0123-456789abcdef/resourceGroups/my-rgca05c9f8/providers/Microsoft.Storage/storageAccounts/storagea791686
and paste the code to your program.
Note that the printed code is quite verbose: Pulumi imports all properties reported by Azure explicitly.
const storage = new azure_native.storage.StorageAccount("storage", {
accessTier: "Hot",
accountName: "storagea791686",
allowBlobPublicAccess: false,
enableHttpsTrafficOnly: true,
encryption: {
keySource: "Microsoft.Storage",
services: {
blob: {
enabled: true,
keyType: "Account",
},
file: {
enabled: true,
keyType: "Account",
},
},
},
isHnsEnabled: false,
kind: "StorageV2",
location: "westeurope",
minimumTlsVersion: "TLS1_0",
networkRuleSet: {
bypass: "AzureServices",
defaultAction: "Allow",
},
resourceGroupName: "my-rgca05c9f8",
sku: {
name: "Standard_LRS",
},
}, {
protect: true,
});
No other resource depends on this storage account in our program, so you can go ahead and remove the old resource from the state
pulumi state delete urn:pulumi:dev::ts::azure:storage/account:Account::storage
Finally, remove the old resource from the code, remove the reference to @pulumi/azure
, and refactor the imported code be more concise and to reference the resource group resource explicitly:
import * as native from "@pulumi/azure-native";
const resourceGroup = new native.resources.ResourceGroup("my-rg", {
resourceGroupName: "my-rgca05c9f8",
});
const storage = new native.storage.StorageAccount("storage", {
resourceGroupName: resourceGroup.name,
accountName: "storagea791686",
isHnsEnabled: false,
kind: "StorageV2",
sku: {
name: "Standard_LRS",
},
});
You have to run pulumi up
to apply these changes to the Storage Account (without replacing it).
The migration is now complete!