I’ve been working in a client for a few weeks where the idea was to create some Logic Apps and expose them as API’s to be consumed by
After the development was almost completed, we started planning the migration to the test and productions environments and that’s when we started looking at options to copy everything that we have in the API Management development environment to these new ones.
For those that have been
Extracting the ARM Template
The first thing we need to do is to extract the ARM template and to do so we can just use the Azure Portal to that as you can see below.
The problem with this approach is that the ARM template here does not expose everything you need to have another instance of it running right away and you will have to add a lot of missing information. So when looking for more ways of doing this, I found a blog post made by Matias Logdberg where he created a PowerShell module that enables us to extract everything related to the API Management instance we want to deploy in other environments.
You can find this blog post here: http://mlogdberg.com/apimanagement/arm-template-creator.
So using this module I did the following steps:
- Downloaded the PowerShell module code and compiled it
at: https://github.com/MLogdberg/APIManagementARMTemplateCreator - Executed the PowerShell script as the code below
1 2 3 4 5 6 7 8 9 10 |
Import-Module "<Install Location>\APIManagementTemplate.dll" $apimName = 'apimName' $rgName = 'rgName' $subId = 'subId' $tenant = 'tenant.onmicrosoft.com' $filename = '<Repository Location>\' + $apimName + '.json' Get-APIManagementTemplate -APIManagement $apimName -ResourceGroup $rgName -SubscriptionId $subId -TenantName $tenant -ExportPIManagementInstance $false | Out-File $filename |
- Created a Visual Studio project to hold this ARM Template and parametrized for each environment that I needed. In the picture below DEV, TEST, and PROD.
The ARM template generated came with all API’s, operations, policies, products, named values and more. I had to add manually was the creation of the API Management instance itself, but maybe there is some parameter in the module to also expose that. Another thing that I had to do was to parametrize the values that I needed inside the policies and some other points as below:
1 2 3 4 5 6 7 8 |
<policies> <inbound> <base /> <rewrite-uri template=\"/manual/paths/invoke?api-version=2016-10-01&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=',parameters('api_b2c_orders').sig,'\" /> <set-method>POST</set-method> <set-backend-service base-url=\"https://',parameters('api_b2c_orders').server,'.logic.azure.com:443/workflows/',parameters('api_b2c_orders').workflowId,'/triggers\" /> </inbound> </policies> |
Below you will see the complete ARM template for one API with all its operations, policies and products.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 |
{ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { "service_companyglobal_name": { "type": "string" }, "api_b2c": { "type": "object" }, "api_b2c_orders": { "type": "object" }, "product_reporting_name": { "type": "string" }, "product_unlimited_name": { "type": "string" } }, "variables": {}, "resources": [ /*{ "type": "Microsoft.ApiManagement/service", "sku": { "name": "Basic", "capacity": 1 }, "name": "[parameters('service_companyglobal_name')]", "apiVersion": "2018-01-01", "location": "Central US", "tags": {}, "scale": null, "properties": { "publisherEmail": "user@company.com", "publisherName": "company", "notificationSenderEmail": "apimgmt-noreply@mail.windowsazure.com", "hostnameConfigurations": [], "additionalLocations": null, "virtualNetworkConfiguration": null, "customProperties": { "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls10": "False", "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Tls11": "False", "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Protocols.Ssl30": "False", "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Ciphers.TripleDes168": "False", "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls10": "False", "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Tls11": "False", "Microsoft.WindowsAzure.ApiManagement.Gateway.Security.Backend.Protocols.Ssl30": "False" }, "virtualNetworkType": "None", "certificates": null }, "dependsOn": [] },*/ { "type": "Microsoft.ApiManagement/service/apis", "name": "[concat(parameters('service_companyglobal_name'), '/' ,parameters('api_b2c').name)]", "apiVersion": "2017-03-01", "properties": { "displayName": "B2C", "apiRevision": "[parameters('api_b2c').apiRevision]", "description": "", "serviceUrl": "[parameters('api_b2c').serviceUrl]", "path": "b2c", "protocols": [ "https" ], "authenticationSettings": { "oAuth2": null, "openid": null }, "subscriptionKeyParameterNames": { "header": "Ocp-Apim-Subscription-Key", "query": "subscription-key" }, "isCurrent": "[parameters('api_b2c').isCurrent]" }, "resources": [ { "type": "Microsoft.ApiManagement/service/apis/operations", "name": "[concat(parameters('service_companyglobal_name'), '/', parameters('api_b2c').name, '/', 'orders')]", "apiVersion": "2017-03-01", "properties": { "displayName": "Orders", "method": "POST", "urlTemplate": "/orders", "templateParameters": [], "description": "", "request": { "queryParameters": [], "headers": [], "representations": [] }, "responses": [ { "statusCode": 200, "description": "", "representations": [], "headers": [] }, { "statusCode": 500, "description": "", "representations": [], "headers": [] }, { "statusCode": 400, "description": "", "representations": [], "headers": [] } ], "policies": null }, "resources": [ { "type": "Microsoft.ApiManagement/service/apis/operations/policies", "name": "[concat(parameters('service_companyglobal_name'), '/', parameters('api_b2c').name, '/', 'orders', '/', 'policy')]", "apiVersion": "2017-03-01", "properties": { "policyContent": "[Concat('<!--\r\n IMPORTANT:\r\n - Policy elements can appear only within the <inbound>, <outbound>, <backend> section elements.\r\n - Only the <forward-request> policy element can appear within the <backend> section element.\r\n - To apply a policy to the incoming request (before it is forwarded to the backend service), place a corresponding policy element within the <inbound> section element.\r\n - To apply a policy to the outgoing response (before it is sent back to the caller), place a corresponding policy element within the <outbound> section element.\r\n - To add a policy position the cursor at the desired insertion point and click on the round button associated with the policy.\r\n - To remove a policy, delete the corresponding policy statement from the policy document.\r\n - Position the <base> element within a section element to inherit all policies from the corresponding section element in the enclosing scope.\r\n - Remove the <base> element to prevent inheriting policies from the corresponding section element in the enclosing scope.\r\n - Policies are applied in the order of their appearance, from the top down.\r\n-->\r\n<policies>\r\n <inbound>\r\n <base />\r\n <rewrite-uri template=\"/manual/paths/invoke?api-version=2016-10-01&sp=%2Ftriggers%2Fmanual%2Frun&sv=1.0&sig=',parameters('api_b2c_orders').sig,'\" />\r\n <set-method>POST</set-method>\r\n <set-backend-service base-url=\"https://',parameters('api_b2c_orders').server,'.logic.azure.com:443/workflows/',parameters('api_b2c_orders').workflowId,'/triggers\" />\r\n <set-header name=\"Content-Type\" exists-action=\"override\">\r\n <value>application/xml</value>\r\n </set-header>\r\n </inbound>\r\n <backend>\r\n <base />\r\n </backend>\r\n <outbound>\r\n <base />\r\n </outbound>\r\n <on-error>\r\n <base />\r\n </on-error>\r\n</policies>')]" }, "resources": [], "dependsOn": [ "[resourceId('Microsoft.ApiManagement/service/apis', parameters('service_companyglobal_name') , parameters('api_b2c').name)]", "[resourceId('Microsoft.ApiManagement/service/apis/operations', parameters('service_companyglobal_name'), parameters('api_b2c').name, 'orders')]" ] } ], "dependsOn": [ "[resourceId('Microsoft.ApiManagement/service/apis', parameters('service_companyglobal_name'),parameters('api_b2c').name)]" ] }, { "type": "Microsoft.ApiManagement/service/apis/policies", "name": "[concat(parameters('service_companyglobal_name'), '/', parameters('api_b2c').name, '/', 'policy')]", "apiVersion": "2017-03-01", "properties": { "policyContent": "<!--\r\n IMPORTANT:\r\n - Policy elements can appear only within the <inbound>, <outbound>, <backend> section elements.\r\n - Only the <forward-request> policy element can appear within the <backend> section element.\r\n - To apply a policy to the incoming request (before it is forwarded to the backend service), place a corresponding policy element within the <inbound> section element.\r\n - To apply a policy to the outgoing response (before it is sent back to the caller), place a corresponding policy element within the <outbound> section element.\r\n - To add a policy position the cursor at the desired insertion point and click on the round button associated with the policy.\r\n - To remove a policy, delete the corresponding policy statement from the policy document.\r\n - Position the <base> element within a section element to inherit all policies from the corresponding section element in the enclosing scope.\r\n - Remove the <base> element to prevent inheriting policies from the corresponding section element in the enclosing scope.\r\n - Policies are applied in the order of their appearance, from the top down.\r\n-->\r\n<policies>\r\n <inbound>\r\n <base />\r\n <set-header name=\"Ocp-Apim-Subscription-Key\" exists-action=\"delete\" />\r\n </inbound>\r\n <backend>\r\n <base />\r\n </backend>\r\n <outbound>\r\n <base />\r\n </outbound>\r\n <on-error>\r\n <base />\r\n </on-error>\r\n</policies>" }, "resources": [], "dependsOn": [ "[resourceId('Microsoft.ApiManagement/service/apis', parameters('service_companyglobal_name') , parameters('api_b2c').name)]" ] } ], "dependsOn": [ //"[resourceId('Microsoft.ApiManagement/service', parameters('service_companyglobal_name') )]" ] }, { "type": "Microsoft.ApiManagement/service/products", "name": "[concat(parameters('service_companyglobal_name'), '/' ,parameters('product_reporting_name'))]", "apiVersion": "2017-03-01", "properties": { "displayName": "Reporting", "description": "Contains API's that extract reporting data from the company data sources", "terms": null, "subscriptionRequired": true, "approvalRequired": false, "subscriptionsLimit": 1, "state": "published" }, "resources": [ { "type": "Microsoft.ApiManagement/service/products/apis", "name": "[concat(parameters('service_companyglobal_name'), '/', parameters('product_reporting_name'), '/reporting')]", "apiVersion": "2017-03-01", "properties": { "displayName": "Reporting", "apiRevision": "1", "description": "", "serviceUrl": "https://xxxx-99.centralus.logic.azure.com/workflows/1234abcd1234/triggers", "path": "reporting", "protocols": [ "https" ], "authenticationSettings": null, "subscriptionKeyParameterNames": null, "isCurrent": true }, "resources": [], "dependsOn": [ "[resourceId('Microsoft.ApiManagement/service/products', parameters('service_companyglobal_name'), parameters('product_reporting_name'))]" ] } ], "dependsOn": [ //"[resourceId('Microsoft.ApiManagement/service', parameters('service_companyglobal_name') )]" ] }, { "type": "Microsoft.ApiManagement/service/products", "name": "[concat(parameters('service_companyglobal_name'), '/' ,parameters('product_unlimited_name'))]", "apiVersion": "2017-03-01", "properties": { "displayName": "Unlimited", "description": "Subscribers have completely unlimited access to the API. Administrator approval is required.", "terms": null, "subscriptionRequired": true, "approvalRequired": true, "subscriptionsLimit": 1, "state": "published" }, "resources": [ { "type": "Microsoft.ApiManagement/service/products/apis", "name": "[concat(parameters('service_companyglobal_name'), '/', parameters('product_unlimited_name'), '/b2c')]", "apiVersion": "2017-03-01", "properties": { "displayName": "B2C", "apiRevision": "1", "description": "", "serviceUrl": "https://xxxx-99.centralus.logic.azure.com/workflows/1234abcd1234/triggers", "path": "b2c", "protocols": [ "https" ], "authenticationSettings": null, "subscriptionKeyParameterNames": null, "isCurrent": true }, "resources": [], "dependsOn": [ "[resourceId('Microsoft.ApiManagement/service/products', parameters('service_companyglobal_name'), parameters('product_unlimited_name'))]" ] }, { "type": "Microsoft.ApiManagement/service/products/apis", "name": "[concat(parameters('service_companyglobal_name'), '/', parameters('product_unlimited_name'), '/dynamics')]", "apiVersion": "2017-03-01", "properties": { "displayName": "Dynamics", "apiRevision": "1", "description": "", "serviceUrl": "https://xxxx-99.centralus.logic.azure.com/workflows/1234abcd1234/triggers", "path": "dynamics", "protocols": [ "https" ], "authenticationSettings": null, "subscriptionKeyParameterNames": null, "isCurrent": true }, "resources": [], "dependsOn": [ "[resourceId('Microsoft.ApiManagement/service/products', parameters('service_companyglobal_name'), parameters('product_unlimited_name'))]" ] } ], "dependsOn": [ //"[resourceId('Microsoft.ApiManagement/service', parameters('service_companyglobal_name') )]" ] } ], "outputs": {} } |
Deployment in other environments
Once you have the ARM template completely parametrized, you can then use PowerShell, Visual Studio, VSTS, or the Azure Portal to deploy it to the environment you want.
From Visual Studio, you have the deploy command in the project as the picture below shows.
As described in the code of the ARM template, the creation of the API Management instance is commented out. I’ll just enable this part of the code
Summary
With this approach, will be a lot easier to maintain the instances of API Management in several environments in sync and also, I’ll be able to see what changes are being made in the code over time.