How to deploy automatically an Azure API management? - azure

Within API management, I created an API that enables to call a serverless function app. Now I would like to deploy automatically this functionnality. Here are the possibilities I saw on internet :
Create and configure the api management through the portal (this is not what I call an automatic deployment)
Use Powershell command (unfortunally I am working with linux)
ARM (Azure Resource Manager): this is not easy and I did find how to create an API with Azure function app
Terraform: same as ARM, it is not clear for me how to create an API with Azure function app
If someone has an experience, links or ideas I would be very thankful.
Regards,

We are currently using Terraform for all our Azure infrastructure including API Management and I would strongly recommend it.
It is creating and updating everything we want, including api policies and has a relativity small learning curve.
You can start learning here:
https://learn.hashicorp.com/terraform?track=azure#azure
The docs for APIM are here:
https://www.terraform.io/docs/providers/azurerm/r/api_management.html
Once the initial learning curve is done the rest is easy and the benefits are many.

Azure Powershell is 100% cross platform now, so that's an option. Here are some samples: https://learn.microsoft.com/en-us/azure/api-management/powershell-samples
You can also use ARM Templates to spin it up. Configuring it is a lot harder. You can map any of these calls to the ARM Template.
Terraform - i think its still in the works. https://github.com/terraform-providers/terraform-provider-azurerm/issues/1177. But I wouldnt go that way.

ARM is the way to go.
You can combine it with:
Azure resource manager API for deployment
API Management API for things that ARM doesn't support (yet)

Have a look at the Azure API Management DevOps Resource Kit:
https://github.com/Azure/azure-api-management-devops-resource-kit

I believe the most convenient way for automating the deployment of Azure APIM is dotnet-apim. It's a cross-platform solution that you can easily use on your dev machine or ci/cd pipeline.
Make sure you have .NET Core installed.
Install dotnet-apim tool.
In a yaml file, you define the list of APIVersionSets, APIs, Products, Backends, Tags, etc. This YAML file defines what you want to deploy to APIM. You can have it on your source control to take the history of changes. The following YAML file defines 2 Sets, APIs, and Products along with their policies.
version: 0.0.1 # Required
apimServiceName: $(apimServiceName) # Required, must match name of an apim service deployed in the specified resource group
apiVersionSets:
- name: Set1
displayName: API Set 1
description: Contains Set 1 APIs.
versioningScheme: Segment
- name: Set2
displayName: API Set 2
description: Contains Set 2 APIs.
versioningScheme: Segment
apis:
- name: API1
displayName: API v1
openApiSpec: $(apimBasePath)\Apis\OpenApi.json # Required, can be url or local file
policy: $(apimBasePath)\Apis\ApiPolicy.xml
path: api/sample1
apiVersion: v1
apiVersionSetId: Set1
apiRevision: 1
products: AutomationTests, SystemMonitoring
protocols: https
subscriptionRequired: true
isCurrent: true
operations:
customer_get: # it's operation id
policy: $(apimBasePath)\Apis\HealthCheck\HealthCheckPolicy.xml:::BackendUrl=$(attachmentServiceUrl)
subscriptionKeyParameterNames:
header: ProviderKey
query: ProviderKey
- name: API2
displayName: API2 v1 [Staging]
openApiSpec: $(apimBasePath)\Apis\OpenApi.json # Required, can be url or local file
policy: $(apimBasePath)\Apis\ApiPolicy.xml
path: api/sample2
apiVersion: v1
apiVersionSetId: Set2
apiRevision: 1
products: AutomationTests, SystemMonitoring
protocols: https
subscriptionRequired: true
isCurrent: true
subscriptionKeyParameterNames:
header: ProviderKey
query: ProviderKey
products:
- name: AutomationTests
displayName: AutomationTests
description: Product for automation tests
subscriptionRequired: true
approvalRequired: true
subscriptionsLimit: 1
state: published
policy: $(apimBasePath)\Products\AutomationTests\policy.xml
- name: SystemMonitoring
displayName: SystemMonitoring
description: Product for system monitoring
subscriptionRequired: true
approvalRequired: true
subscriptionsLimit: 1
state: published
policy: $(apimBasePath)\Products\SystemMonitoring\policy.xml
outputLocation: $(apimBasePath)\output
linkedTemplatesBaseUrl : $(linkedTemplatesBaseUrl) # Required if 'linked' property is set to true
the $(variableName) is a syntax for defining variables inside the YAML file which makes customization easier in ci/cd scenarios.
The next step is to transform the YAML file to ARM which Azure can understand.
dotnet-apim --yamlConfig "c:/apim/definition.yml"
Then you have to deploy the generated ARM templates to Azure which is explained here.

Using the test-api (if you do the microsoft demo for API Mgmt, you should recognize it), here's a snippet of terraform that does work. does not include the resource group (thisrg)
resource "azurerm_api_management" "apimgmtinstance" {
name = "${var.base_apimgmt_name}-${var.env_name}-apim"
location = azurerm_resource_group.thisrg.location
resource_group_name = azurerm_resource_group.thisrg.name
publisher_name = "Marc Pub"
publisher_email = "marc#trash.com"
sku_name = var.apimgmt_size
/* policy {
xml_content = <<XML
<policies>
<inbound />
<backend />
<outbound />
<on-error />
</policies>
XML
} */
}
resource "azurerm_api_management_product" "apiMgmtProductContoso" {
product_id = "contoso-marc"
display_name = "Contoso Marc"
description = "this is a test"
subscription_required = true
approval_required = true
api_management_name = azurerm_api_management.apimgmtinstance.name
resource_group_name = azurerm_resource_group.thisrg.name
published = true
subscriptions_limit = 2
terms = "you better accept this or else... ;-)"
}
resource "azurerm_api_management_api" "testapi" {
description = "this is a mock test"
display_name = "Test API"
name = "test-api"
protocols = ["https"]
api_management_name = azurerm_api_management.apimgmtinstance.name
resource_group_name = azurerm_resource_group.thisrg.name
// version = "0.0.1"
revision = "1"
path = ""
subscription_required = true
}
data "azurerm_api_management_api" "testapi_data" {
name = azurerm_api_management_api.testapi.name
api_management_name = azurerm_api_management.apimgmtinstance.name
resource_group_name = azurerm_resource_group.thisrg.name
revision = "1"
}
resource "azurerm_api_management_api_operation" "testapi_getop" {
operation_id = "test-call"
api_name = data.azurerm_api_management_api.testapi_data.name
api_management_name = data.azurerm_api_management_api.testapi_data.api_management_name
resource_group_name = data.azurerm_api_management_api.testapi_data.resource_group_name
display_name = "Test call"
method = "GET"
url_template = "/test"
description = "test of call"
response {
status_code = 200
description = ""
representation {
content_type = "application/json"
sample = "{\"sampleField\": \"test\"}"
}
}
}
resource "azurerm_api_management_api_operation_policy" "testapi_getop_policy" {
api_name = azurerm_api_management_api_operation.testapi_getop.api_name
api_management_name = azurerm_api_management_api_operation.testapi_getop.api_management_name
resource_group_name = azurerm_api_management_api_operation.testapi_getop.resource_group_name
operation_id = azurerm_api_management_api_operation.testapi_getop.operation_id
xml_content = <<XML
<policies>
<inbound>
<mock-response status-code="200" content-type="application/json"/>
</inbound>
</policies>
XML
}

terraform now mostly supports azure api management. I've been implementing most of an existing azure api management into terraform using a combination of the https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/api_management along with terraform import (just do it in a separate folder, terraform import imports into a terraform.tfstate file, and if you mix up the resource you are importing along with the tf file(s) you are creating as a result's terraform.tfstate file (generated via terraform plan/apply), you could accidently delete the resource you are importing from. yay...
It mostly does it, except for an API where you modified for 'All operations". I can do it for specific operations (get blah, or post blahblah), but for all operations... I don't yet know.

Related

How do I add a new API version via Terraform in Azure API Manager?

I would like to add a new API version under the existing API that is already there. I can do that easily via the Portal UI however, can someone please guide me to how to achieve this via Terraform? Any sample snippet would be helpful.
I am trying to reverse engineer v1, v2 of this into Terraform. Thanks.
Terraform supports API Version Set and reference it in the API:
version - (Optional) The Version number of this API, if this API is versioned.
version_set_id - (Optional) The ID of the Version Set which this API is associated with.
NOTE:
When version is set, version_set_id must also be specified
resource "azurerm_api_management_api_version_set" "example" {
name = "example-apimapi"
resource_group_name = var.resource_group_name
api_management_name = var.apim_name
display_name = "ExampleAPIVersionSet"
versioning_scheme = "Segment"
}
resource "azurerm_api_management_api" "example" {
name = "example-api"
resource_group_name = var.resource_group_name
api_management_name = var.apim_name
revision = "1"
display_name = "Example API"
path = "example_me"
protocols = ["https"]
service_url = "https://conferenceapi.azurewebsites.net/"
version = "v1"
version_set_id = azurerm_api_management_api_version_set.example.id
import {
content_format = "swagger-link-json"
content_value = "http://conferenceapi.azurewebsites.net/?format=json"
}
}
There's a tutorial for doing it the Portal: Tutorial: Publish multiple versions of your API
When you create multiple versions, the Azure portal creates a version set, which represents a set of versions for a single logical API. Select the name of an API that has multiple versions. The Azure portal displays its Version set. You can customize the Name and Description of a virtual set.

Terraform: specify backend type in azure

Context: While manually deploying a backend service in AZURE,
I am prompted to select the type: custom, azure or service fabric.
How can I declare via terraform the type (I would like to select Azure resource) and say which app I want to use?
As per documentation it says to use a resource id of the app (that i generate at the start of the deployment) and I tried this:
resource "azurerm_api_management_backend" "polo-backend" {
name = "polo-backend"
resource_group_name = azurerm_resource_group.polo-rg.name
api_management_name = azurerm_api_management.polo-api-mgmt.name
protocol = "http"
url = "https://myurl"
resource_id = azurerm_windows_web_app.app-service.id
}
But it gives me this error:
Error: creating/updating Backend: (Name "polo-backend" / Service Name "polo-api-mgmt" / Resource Group "polo1-default-rg"): apimanagement.BackendClient#CreateOrUpdate: Failure responding to
request: StatusCode=400 -- Original Error: autorest/azure: Service returned an error. Status=400 Code="ValidationError" Message="One or more fields contain incorrect values:" Details=[{"code":"ValidationError","message":"Value should represent absolute http URL","target":"resourceId"}]
Furthermore.. if the app is generated with terraform how can I assign the URL dynamically in the URL section?
So I found the answer to my question:
Basically you have to pass to the resource id, the literal URL to the desired resource. passing it via arguments it's not supported as of now OR that argument I was trying to assign is wrong.
So what I managed to do was using the data module to "template-ify" the code as much as I can:
resource "azurerm_api_management_backend" "polo-backend" {
name = "polo-backend"
resource_group_name = azurerm_resource_group.polo-rg.name
api_management_name = azurerm_api_management.polo-api-mgmt.name
protocol = "http"
url = "https://${azurerm_windows_web_app.app-service.name}.azurewebsites.net"
resource_id = "https://management.azure.com/subscriptions/${data.azurerm_client_config.mysubid.subscription_id}/resourceGroups/${azurerm_resource_group.polo-rg.name}/providers/Microsoft.Web/sites/${azurerm_windows_web_app.app-service.name}"
}
IF anyone has a better solution than this please feel free to suggest!

Azure Terraform Build Generic Components using Infrastructure as Code

I am new to Terraform and Azure. I am trying to build a Resource Group / Resources using Terraform. Below is the design for the same.
I have written Terraform code to build Log Analytics workspace and Automation account.
Now below are my questions :
Cost Mgmt / Azure Monitor / Network Watcher / Defender for Cloud ? Can I build all these using Terraform code in this resource group or they need to manually built from Azure portal. When we create any resource on the left hand side options like Cost estimator / management are already available. Does that mean they can be easily selected from there on usage and no need to build from Terraform code ?
How does we apply Role Entitlement / Policy Assignment from Terraform code ?
Here is my code what I have written to build Automation account / Log Analytics
terraform {
required_version = ">=0.12"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~>2.0"
}
}
}
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "management" {
# Mandatory resource attributes
name = "k8s-log-analytics-test"
location = "eastus"
}
resource "random_id" "workspace" {
keepers = {
# Generate a new id each time we switch to a new resource group
group_name = azurerm_resource_group.management.name
}
byte_length = 8
}
resource "azurerm_log_analytics_workspace" "management" {
# Mandatory resource attributes
name = "k8s-workspace-${random_id.workspace.hex}"
location = azurerm_resource_group.management.location
resource_group_name = azurerm_resource_group.management.name
# Optional resource attributes
retention_in_days = 30
sku = "PerGB2018"
}
resource "azurerm_log_analytics_solution" "management" {
# Mandatory resource attributes
solution_name = "mgmyloganalytsolution"
location = azurerm_resource_group.management.location
resource_group_name = azurerm_resource_group.management.name
workspace_resource_id = azurerm_log_analytics_workspace.management.id
workspace_name = azurerm_log_analytics_workspace.management.name
plan {
publisher = "Microsoft"
product = "OMSGallery/ContainerInsights"
}
}
resource "azurerm_automation_account" "management" {
# Mandatory resource attributes
name = "mgmtautomationaccount"
location = azurerm_resource_group.management.location
resource_group_name = azurerm_resource_group.management.name
sku_name = "Basic"
}
resource "azurerm_log_analytics_linked_service" "management" {
# Mandatory resource attributes
resource_group_name = azurerm_resource_group.management.name
workspace_id = azurerm_log_analytics_workspace.management.id
read_access_id = azurerm_automation_account.management.id
}
Cost Mgmt / Azure Monitor / Network Watcher / Defender for Cloud ? Can
I build all these using Terraform code in this resource group or they
need to manually built from Azure portal. When we create any resource
on the left hand side options like Cost estimator / management are
already available. Does that mean they can be easily selected from
there on usage and no need to build from Terraform code ?
Yes , you can create Network Watcher , Azure Monitor resources & Cost Management using terraform resource blocks as azurerm_network_watcher , azurerm_network_watcher_flow_log ,azurerm_monitor_metric_alert ... , azurerm_resource_group_cost_management_export, azurerm_consumption_budget_resource_group etc. Defender for Cloud can't be built from terraform . Yes you are correct , cost management ,monitoring etc are also available on portal but there is a need for its resources to be created like budget alert etc. for simplification it has been added as a blade in portal.
How does we apply Role Entitlement / Policy Assignment from Terraform
code ?
You can use azurerm_role_assignment to assign built-in roles and use azurerm_role_definition to create a custom role and then assign it . For Policy assignment you can use this azurerm_resource_policy_assignment and remediate using azurerm_policy_insights_remediation.
For all the azure resource block you can refer the Official Registry Documentation of Terraform AzureRM Provider & Terraform AzureAD Provider.

Azure Cognitive Services - System assigned identity in Terraform

How can you create an Azure Cognitive Services Account with System assigned identity in Terraform?
I have tried the following but got an error: Blocks of type "identity" are not expected here.
resource "azurerm_cognitive_account" "cgsrv" {
# Conditionally based on feature flag
count = var.to_provision == true ? 1 : 0
name = lower(replace("${var.name_params.prefix}-cgnsrv-${var.name_params.use_case_name}", "-", ""))
location = var.location
resource_group_name = var.resourcegroup_name
kind = "CognitiveServices"
sku_name = "S0"
identity {
type = "SystemAssigned"
}
}
You are right, the terraform documentation does not mention this ability at the moment (see here)
The provider is open-sourced, you can find the source here and it looks like there is a pull request regarding this specific field: https://github.com/terraform-providers/terraform-provider-azurerm/pull/12469

How can I use Terraform to create a service principal and use that principal in a provider?

I have read the write-ups online but they dont seem to cover this topic completely and was hoping someone who has done it may have some direction for me.
We are setting up a complicated Terraform template to satisfy our IaC requirements relating to our SaaS offering. In doing so we want the template to use the user's credentials at launch to create a new service principal in Azure AD (This part I have no problem doing). Then in the next portion of the template we are using that service principal as the provider. Problem is that it throws errors in the plan/apply because the service principal doesnt exist (aka the id is non existent due to the service provider section not running yet).
So is there a way that I can do this? Create a service principal and then us it in a provider alias that uses that service principal without splitting this into multiple templates?
In the end I want this template to create the service provider using the local user's permissions or MSI, give it RBAC to a subscription, then use that service provider to create assets in that subscription.
main.ts (root)
provider "azurerm" {
alias = "ActiveDirectory"
subscription_id = "${var.subscriptionNucleus}"
}
provider "azurerm" {
alias = "Infrastructure"
subscription_id = "${var.subscriptionInfrastructure}"
}
module "activedirectory" {
providers = { azurerm = "azurerm.ActiveDirectory"
}
source = "./modules/activedirectory"
subscription_id_infrastructure = "${var.subscriptionInfrastructure}"
}
module "infrastructure" {
providers = { azurerm = "azurerm.Infrastructure"}
source = "./modules/infrastructure"
location = "${var.location}"
application_id =
"${module.activedirectory.service_principal_application_id}"
subscription_id = "${var.subscriptionInfrastructure}"
prefix = "${var.prefix}"
}
main.ts (./modules/infrastructure)
data "azurerm_azuread_service_principal" "serviceprincipal" {
application_id = "${var.application_id}"
}
provider "azurerm" {
alias = "InfrastructureSP"
subscription_id = "${var.subscription_id}"
client_id = "${var.application_id}"
client_secret = "secret"
tenant_id =
"${data.azurerm_client_config.clientconfig.tenant_id}"
}
For Azure Service Principal, there are two ways to use the service principal.
First: If you already have a service principal and want to use it in the Terraform. You can make use of the Terraform Data and the test like this:
data "azurerm_azuread_service_principal" "sp" {
application_id = "21f3e1de-54e2-4951-9743-c280ad7bd74a"
}
output "test" {
value = "${data.azurerm_azuread_service_principal.sp.id}"
}
The screenshot of the result is here:
Second: You don't have the service principal and you can just create a service principal in the Terraform like this:
resource "azurerm_azuread_service_principal" "test" {
application_id = "${azurerm_azuread_application.test.application_id}"
}
resource "azurerm_azuread_service_principal_password" "test" {
service_principal_id = "${azurerm_azuread_service_principal.test.id}"
value = "your pasword"
end_date = "2020-01-01T01:02:03Z"
}
Then, no matter which way you choose, there is an important step you should do for most resources. The step is that you need to create the role to give the permission and then assign it to the resource which needs. You can do that like this:
resource "azurerm_role_assignment" "test" {
scope = "yourScope" # the resource id
role_definition_name = "the Role In need" # such as "Contributor"
principal_id = "your service principal id"
}
Hope this will help you.
There is currently no working "depends_on" that works with modules that is not a hack (null_reference). This means that if you are breaking your template into modules(separating concerns) your order of operation is required to be correct to complete this successfully as one module will not know that the data source of service provider has to wait on the previous module to complete. I have had to break this into 2 separate templates where the first creates the service principal and the second has the modular separation that can then use a data source of azurerm_azuread_service_principal.
Once Hashicorp can implement the module depends_on, this will become easier.

Resources