Programmatic deployment of Marketplace products

This article explains how to use Azure CLI, Azure PowerShell and Terraform to deploy Marketplace products resources to Azure.

Prerequisites

You need to install Azure PowerShell and connect to Azure:

The deployment commands changed in Azure CLI version 2.2.0. The examples in this article require Azure CLI version 2.20.0 or later.

To run this sample, install the latest version of the Azure CLI. To start, run az sign-in to create a connection with Azure.

How to find Marketplace product identifiers for publisher, offer and plan

To programmatically deploy Marketplace product you must first obtain the unique identifiers of a Marketplace product.

To find the unique identifiers:

  1. Open the Azure portal and navigate to the Marketplace experience.
  2. Search for the Marketplace product you want to deploy
  3. Open the product details page by the selecting the product name.
  4. Navigate to the Usage Information + Support tab. In Usage Information, the Publisher ID, Product ID, and Plan ID appear.

Screenshot of product ID page.

Note

In some APIs, Product ID is also known as Offer ID, and Plan ID is also known as SKU ID.

Virtual Machine from Azure Marketplace

To deploy third party VMs from Azure Marketplace, you need to first accept the End User License Agreement (EULA) for the VM image that is being deployed. Once the EULA is accepted one time in an Azure subscription, you should be able to deploy the same VM offer again without needing to accept the terms again. If you're deploying the VM from Azure portal, the terms are accepted there. However, when you do the deployment programmatically, you need to accept the terms using the az vm image terms accept --publisher X --offer Y --plan Z or by using ARM.

If the terms aren't yet accepted, the following error is shown:

Code : MarketplacePurchaseEligibilityFailed
Message: Marketplace purchase eligibility check returned errors. See inner errors for details
Details: Offer with PublisherId: '<PublisherId>', OfferId: '<OfferId>' cannot be purchased due to validation errors. For more information see details. Correlation Id: '11111111-1111-1111-1111-111111111111' You have not accepted the legal terms on this subscription: '11111111-1111-1111-1111-111111111111' for this plan. Before the subscription can be used, you need to accept the legal terms of the image. To read and accept legal terms, use the Azure CLI commands described at https://go.microsoft.com/fwlink/?linkid=2110637 or the PowerShell commands available at https://go.microsoft.com/fwlink/?linkid=862451. Alternatively, deploying via the Azure portal provides a UI experience for reading and accepting the legal terms.

Deploy VM from Azure Marketplace using Azure CLI

Once the terms are accepted, you can deploy the VM using the regular methods such as ARM/Bicep template, Azure CLI, Terraform, etc.

To learn more on finding VM images, Accepting the terms and deploying the VM using CLI, see Find and use marketplace purchase plan information using the CLI.

Deploy VM from Azure Marketplace using Terraform

Instructions on how to deploy Virtual Machines using Terraform for Windows VM or Linux VM.

To use Terraform to deploy Marketplace VMs you must perform the following actions:

  1. Accept the VM product legal terms using azurerm_marketplace_agreement

  2. Specify the plan block in the azurerm_virtual_machine provider

Note

If the plan block isn't specified, the deployment will fail with the following error:

Code: VMMarketplaceInvalidInput Message: Creating a virtual machine from Marketplace image or a custom image sourced from a Marketplace image requires Plan information in the request. VM: '/subscriptions/<Subscription ID>/resourceGroups/myResourceGroup/providers/Microsoft.Compute/virtualMachines/myVM

Note

azurerm_marketplace_agreement is treated as a Terraform resource, therefore, the first time you create a specific Marketplace VM product, a unique resource is created to represent the fact the legal terms were accepted. However, the next time you try to deploy another VM (with the same Publisher ID and Offer ID) under the same Azure Subscription, you will get an error:

A resource with the ID "/subscriptions/11111111-1111-1111-1111-111111111111 /providers/Microsoft.MarketplaceOrdering/agreements/<Publisher ID>/offers/<Product ID>/plans/<Plan ID>" already exists - to be managed via Terraform this resource needs to be imported into the State. Please see the resource documentation for "azurerm_marketplace_agreement" for more information

You need to run Terraform state list to see if you have azurerm_marketplace_agreement resource state, and if not you need to import the resource state to your Terraform state.

terraform import azurerm_marketplace_agreement.<TerraformResourceName> /subscriptions/<AzureSubscriptionId>/providers/Microsoft.MarketplaceOrdering/agreements/<Publisher ID>/offers/<Product ID>/plans/<Plan ID>

SaaS offer from Azure Marketplace

SaaS Offers are usually deployed by customers via the Azure portal. After the SaaS offer is deployed using Azure portal, the customer uses the "Configure account now" button to visit the SaaS ISV's landing page and finish configuring the SaaS offer. After the offer is configured, the SaaS ISV activates it using SaaS Fulfillment API.

When you deploy a SaaS offer through the Azure portal, it creates an ARM template and assigns specific parameter values for the deployment. One of the parameters is termId, which identifies the subscription term for the offer. The termId value isn't static, but depends on the offer configuration and the time of deployment. Therefore, you can't use a fixed value for termId in your ARM template. Instead, you need to find out the current termId value by following these steps:

  1. Deploy the offer manually through the Azure portal.
  2. Go to the resource group where the offer is deployed.
  3. Select the deployment name under the Deployments section.
  4. View the input parameters and copy the value of termId.

If a given SaaS offer was never deployed in the Azure subscription, the programmatic deployment fails with an error like the following:

code: DeploymentFailed

message: At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/DeployOperations for usage

Details: Failed to process eligibility check with error Purchase has failed due to signature verification on Marketplace legal agreement. Please retry. If error persists use different Azure subscription, or contact support with correlation-id '11111111-1111-1111-1111-111111111111' and this error message

Deploy SaaS offer using ARM template and Azure CLI

See the ARM example template.

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "name": {
            "type": "string"
        },
        "planId": {
            "type": "string"
        },
        "offerId": {
            "type": "string"
        },
        "publisherId": {
            "type": "string"
        },
        "quantity": {
            "type": "int"
        },
        "termId": {
            "type": "string"
        },
        "azureSubscriptionId": {
            "type": "string"
        },
        "publisherTestEnvironment": {
            "type": "string",
            "defaultValue": ""
        },
        "autoRenew": {
            "type": "bool"
        }
    },
    "resources": [
        {
            "type": "Microsoft.SaaS/resources",
            "apiVersion": "2018-03-01-beta",
            "name": "[parameters('name')]",
            "location": "global",
            "properties": {
                "saasResourceName": "[parameters('name')]",
                "publisherId": "[parameters('publisherId')]",
                "SKUId": "[parameters('planId')]",
                "offerId": "[parameters('offerId')]",
                "quantity": "[parameters('quantity')]",
                "termId": "[parameters('termId')]",
                "autoRenew": "[parameters('autoRenew')]",
                "paymentChannelType": "SubscriptionDelegated",
                "paymentChannelMetadata": {
                    "AzureSubscriptionId": "[parameters('azureSubscriptionId')]"
                },
                "publisherTestEnvironment": "[parameters('publisherTestEnvironment')]",
                "storeFront": "AzurePortal"
            }
        }
    ]
}

  • Save the above as SaaS-ARM.json
  • Run the following commands:
az group create --resource-group <ResourceGroupName> --location <Location>

az deployment group create --resource-group <Resource Group Name> --template-file ./SaaS-ARM.json --parameters name=<SaaS Resource Name> publisherId=<Publisher ID> offerId=<Product ID> planId=<Plan ID> termId=<termId> quantity=1 azureSubscriptionId=11111111-1111-1111-1111-11111111 autoRenew=true

After the SaaS offer resource is provisioned, you can invoke the following ARM API to view its properties:

az rest --method get --uri /subscriptions/<AzureSubscriptionId>/resourceGroups/<ResourceGroupName>/providers/Microsoft.SaaS/resources/<SaaS Resource Name>?api-version=2018-03-01-beta -o json

Now you can make a POST call to get the marketplace token and landing page URL. This URL can be used to browse the SaaS ISV's landing page to finish configuring and activating the SaaS offer.

az rest --method post --uri /subscriptions/<AzureSubscriptionId>/resourceGroups/<ResourceGroupName> /providers/Microsoft.SaaS/resources/<SaaS Resource Name>/listAccessToken?api-version=2018-03-01-beta -o json

More information can be found here - specification of the Microsoft.SaaS resource provider.

Deploy SaaS offer from Azure Marketplace using Terraform

Review the section above describing how to deploy SaaS offer using ARM since Terraform deployment would use the same as using the ARM template.

Azure Application from Azure Marketplace

Azure Application product type is unique offering that allows the publisher to create an ARM template that includes a set of Azure resources and Marketplace products bundled and configured to provide fully functional multi resource applications, Azure Application has three plan type:

  • Solution Template - free offering, ARM template deployment
  • Managed Applications - free or paid offering, creates a Microsoft.Solutions/applications resource type

Azure portal generates an ARM template for Azure Application (Managed Application) deployment. This ARM template creates a resource of type Microsoft.Solutions/applications which points to a specific plan and passes in the application-specific parameters from the UI fields that the customer fills out in Azure portal.

Accept Azure Managed App terms

Like Virtual Machine offer, to deploy the Azure Application (Managed Application) programmatically using the ARM template into an Azure subscription, the subscription needs to accept the terms for the plan of the Azure Managed App. When deployed via the Azure portal, the terms acceptance happens implicitly and later programmatic deployments of the same plan in the same Azure subscription work without issues.

It's also possible to accept the terms of an Azure Application (Managed Application) offer using the same az vm image terms accept as described above in the VM section.

If the Azure Application (Managed Application) product is a paid product (for example, it uses monthly or metered billing), the Azure subscription that you use to deploy it must be associated with a valid payment method (for example, it can't be a free or sponsored subscription).

Deploy Azure Application (Managed Application) using ARM template and Azure CLI

Below is an ARM template example for deploying Managed Application.

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "location": {
            "type": "string",
            "allowedValues": [
                "westus2",
                "westeurope",
                "eastus",
                "northeurope",
                "centralus",
                "eastus2",
                "francecentral",
                "uksouth"
            ]
        },
        "applicationResourceName": {
            "type": "string"
        },
        "managedResourceGroupId": {
            "type": "string",
            "defaultValue": ""
        },
        "managedIdentity": {
            "type": "object",
            "defaultValue": {}
        },
        "initialConsulVersion": {
            "type": "string",
            "defaultValue": "v1.11.2"
        },
        "storageAccountName": {
            "type": "string",
            "defaultValue": "[concat('storage', uniqueString(resourceGroup().id, deployment().name))]"
        },
        "blobContainerName": {
            "type": "string",
            "defaultValue": "[concat('blob', uniqueString(resourceGroup().id, deployment().name))]"
        },
        "identityName": {
            "type": "string",
            "defaultValue": "[concat(parameters('clusterName'), '-identity')]"
        },
        "clusterMode": {
            "type": "string",
            "defaultValue": "PRODUCTION",
            "allowedValues": [
                "PRODUCTION",
                "DEVELOPMENT"
            ]
        },
        "clusterName": {
            "type": "string",
            "defaultValue": "cluster"
        },
        "consulDataCenter": {
            "type": "string",
            "defaultValue": "dc1"
        },
        "numServers": {
            "type": "string",
            "defaultValue": "3"
        },
        "numServersDevelopment": {
            "type": "string",
            "defaultValue": "1"
        },
        "automaticUpgrades": {
            "type": "string",
            "defaultValue": "disabled"
        },
        "consulConnect": {
            "type": "string",
            "defaultValue": "enabled"
        },
        "externalEndpoint": {
            "type": "string",
            "defaultValue": "enabled"
        },
        "snapshotInterval": {
            "type": "string",
            "defaultValue": "1d"
        },
        "snapshotRetention": {
            "type": "string",
            "defaultValue": "1m"
        },
        "consulVnetCidr": {
            "type": "string",
            "defaultValue": "172.25.16.0/24"
        },
        "providerBaseURL": {
            "defaultValue": "https://ama-api.hashicorp.cloud/consulama/2021-04-23",
            "type": "String",
            "metadata": {
                "description": "The URI of the custom provider API"
            }
        },
        "email": {
            "type": "string"
        },
        "federationToken": {
            "type": "string",
            "defaultValue": ""
        },
        "sourceChannel": {
            "type": "string",
            "defaultValue": "azure-portal"
        },
        "auditLoggingEnabled": {
            "type": "string",
            "defaultValue": "disabled",
            "allowedValues": [
                "enabled",
                "disabled"
            ]
        },
        "auditLogStorageContainerURL": {
            "type": "string",
            "defaultValue": ""
        }
    },
    "variables": {
    },
    "resources": [
        {
            "type": "Microsoft.Solutions/applications",
            "apiVersion": "2017-09-01",
            "name": "[parameters('applicationResourceName')]",
            "location": "[parameters('location')]",
            "kind": "MarketPlace",
            "identity": "[if(empty(parameters('managedIdentity')),json('null'),parameters('managedIdentity'))]",
            "plan": {
                "name": "<Plan ID>",
                "product": "<Product ID>",
                "publisher": "<Publisher ID>",
                "version": "<Version>"
            },
            "properties": {
                "managedResourceGroupId": "[parameters('managedResourceGroupId')]",
                "parameters": {
                    "initialConsulVersion": {
                        "value": "[parameters('initialConsulVersion')]"
                    },
                    "storageAccountName": {
                        "value": "[parameters('storageAccountName')]"
                    },
                    "blobContainerName": {
                        "value": "[parameters('blobContainerName')]"
                    },
                    "identityName": {
                        "value": "[parameters('identityName')]"
                    },
                    "clusterMode": {
                        "value": "[parameters('clusterMode')]"
                    },
                    "clusterName": {
                        "value": "[parameters('clusterName')]"
                    },
                    "consulDataCenter": {
                        "value": "[parameters('consulDataCenter')]"
                    },
                    "numServers": {
                        "value": "[parameters('numServers')]"
                    },
                    "numServersDevelopment": {
                        "value": "[parameters('numServersDevelopment')]"
                    },
                    "automaticUpgrades": {
                        "value": "[parameters('automaticUpgrades')]"
                    },
                    "consulConnect": {
                        "value": "[parameters('consulConnect')]"
                    },
                    "externalEndpoint": {
                        "value": "[parameters('externalEndpoint')]"
                    },
                    "snapshotInterval": {
                        "value": "[parameters('snapshotInterval')]"
                    },
                    "snapshotRetention": {
                        "value": "[parameters('snapshotRetention')]"
                    },
                    "consulVnetCidr": {
                        "value": "[parameters('consulVnetCidr')]"
                    },
                    "location": {
                        "value": "[parameters('location')]"
                    },
                    "providerBaseURL": {
                        "value": "[parameters('providerBaseURL')]"
                    },
                    "email": {
                        "value": "[parameters('email')]"
                    },
                    "federationToken": {
                        "value": "[parameters('federationToken')]"
                    },
                    "sourceChannel": {
                        "value": "[parameters('sourceChannel')]"
                    },
                    "auditLoggingEnabled": {
                        "value": "[parameters('auditLoggingEnabled')]"
                    },
                    "auditLogStorageContainerURL": {
                        "value": "[parameters('auditLogStorageContainerURL')]"
                    }
                },
                "jitAccessPolicy": null
            }
        }
    ]
}

Then run the following commands:

az group create --resource-group <Resource Group Name> --location <location>

az deployment group create --resource-group avmanagedapp100 --template-file <ARM Template JSON file> --parameters location=<location> applicationResourceName=<Resource Group Name> managedResourceGroupId=/subscriptions/<Subscription ID>/resourceGroups/<Resource Group Name>  email=<email address>

Deploy Azure Managed App from Azure Marketplace using Terraform

Review the section above describing how to deploy Azure Managed App offer using ARM since Terraform deployment would use the same ARM template.

Solution Template from Azure Marketplace

When deploying Solution Template (not Azure Managed App) offers from Azure Marketplace, the deployment is simply the ARM template that the ISV published with the corresponding UI fields passed as parameters. To deploy solution template offer programmatically, use Azure portal to do the deployment, copy the ARM template, and use it in the subsequent deployments. Since Solution Templates aren't "paid" offers, there are no special terms that need to be accepted. However, if Solution Template ARM template is referring to a VM image from Azure Marketplace, you need to first accept the terms of the VM offer as described for the VM offer.