How to publish an API

CONCEPTS

In order to follow the instruction for creating a new API registration in APIM it would help to understand a few terms.

  • APIM Application: Programming Interface Management is used document and list multiple apis to make it easier for interfacing between systems or retrieve data.

  • swagger.json: A definition of a given api that include all public endpoints and their related request object structure.

  • Terraform: Terraform is a tool for building, changing, and versioning infrastructure safely and efficiently. Terraform can manage existing and popular service providers as well as custom in-house solutions. For more information head to Terraform  azure provider documentation site.

  • Azure DevOps: Software as a service (SaaS) platform from Microsoft that provides an end-to-end DevOps toolchain for developing and deploying software. It also integrates with most leading tools on the market and is a great option for orchestrating a DevOps toolchain.

  • Pull Requests: Are a mechanism for a developer to notify team members that they have completed a feature.

Prerequisites

In order to successfully use your API in the Azure APIM instance set up by DECO you will need the following items:

  1. API deployed in a way that is accessible via APIM

  2. The ability to generate an OpenAPI Specification or 'swagger.json' file for your API.

Assumptions

The instruction below assume a certain level of understanding for multiple concepts. If you have questions about any of the below assumptions feel free to contact the DECO team at the DL 'DECO-DevOps'.

  • Git syntax and actions

  • Feature Flow

  • Terraform syntax

  • RESTful APIs

  • Minimal understanding of Azure DevOps

  • Semantic versioning

  • Minimal understanding of Powershell syntax

  • Access to the Midmark Share Enterprise Azure DevOps project. (Please use the DL above to gain access)

Publish API Monitoring Agreement

In order to ensure the highest quality APIs, it is important to have monitoring included for each service. If monitoring is not included it can be extremely difficult or impossible to determine if there are issues, if there is too much demand, or who is using a particular service. APIs being exposed via APIM should have logs and real-time monitoring. For more information on implementing a monitoring solution head out to the DECO documentation page.

Logs

Any API that will be published to APIM must, at a minimum, have logs that can be searched. Those logs should provide the following information:

  • ​Events and Activities of a service, such as errors or successes

  • Request attempted against a service and associated response HTTPS status codes

  • Request information that can show the originator of a particular request to the service

  • Health checks

​Real-Time Monitoring

Another important aspect of monitoring is real-time monitoring. These are external applications or processes that monitor and log whether an API is up and available. Your real-time monitoring should provide the following:

  • ​Uptime

  • Down/Up status

  • Average response time

If you are publishing an API that is public, it is required that you expose a Health Check endpoint and add it to the Pingdom public status page. If you are publishing a private API we still highly recommend the same process. Please see the documentation here on how to create health check endpoints.

IMPLEMENTATION

Terraform

Getting Started

  1. Gain access to the Midmark Share Enterprise Azure DevOps project if you do not have access. Please use the DL 'DECO-DevOps' asking for access. This email should include who needs access granted and why you need access to the Azure DevOps project.

  2. Clone Midmark.Enterprise.Infrastructure.Apim.Apis repository

  3. Create a new feature branch with the name format of 'feature/name_of_your_feature' and perform a git push. This new branch will be used when performing a pull request into the 'master' branch.

  4. Using your IDE open the newly downloaded repository code. For the below direction Visual Code  will be used as an example.

Setting up a new Domain

Below will describe the process that will need to be perform for the first time a team needs to add their API to APIM. If your team already has a location in the terraform code, you can skip to the section ADDING NEW API REGISTRATIONS.

  1. With the Midmark.Enterprise.Infrastructure.Apim.Apis repository open navigate to the folder '<local_repo_location>/Midmark.Enterprise.Infrastructure.Apim.Apis/modules/api_domains'.

  2. Add a new folder that hasn't already been used that is either named after your team or business area.

  3. Add a file with the name variables.tf with the below contents into the newly created folder.

    variable "environment" {} 
    variable "apim_name" {}
    variable "apim_resource_group_name" {}
    variable "publisher" {}
  4. Add a file with the name main.tf with the below contents into the newly created folder.

    locals {}
  5. Add an empty file with the name outputs.tf into the newly created folder. This file is not currently used but will be in the future.

  6. With the Midmark.Enterprise.Infrastructure.Apim.Apis repository open navigate to the file '<local_repo_location>/Midmark.Enterprise.Infrastructure.Apim.Apis/modules/init/main.tf'.

  7. Add the following to the bottom of the main.tf file. <DOMAIN_NAME> should be equal to the name of the folder to created above. Make sure to replace all instance of <DOMAIN_NAME>.

    module "<DOMAIN_NAME>" { 
    source = "../api_domains/<DOMAIN_NAME>"
    environment = var.environment
    apim_name = data.azurerm_api_management.apim.name
    apim_resource_group_name = data.azurerm_api_management.apim.resource_group_name
    }

Adding new Api Registrations

  1. With the Midmark.Enterprise.Infrastructure.Apim.Apis repository open navigate to the file '<local_repo_location>/Midmark.Enterprise.Infrastructure.Apim.Apis/modules/api_domains/<DOMAIN_NAME>/main.tf'.

    • <DOMAIN_NAME>: Should be equal to the name of the folder that was created in the SETTING UP A NEW DOMAIN section steps.

  2. For each API you will need to add the following to the bottom of the main.tf file.

    • <team_name>: Any alpha-numeric character. excludes all other characters. Name of the team that is creating this entry.

    • <api_nameany>: Lowercase alpha-numeric character. excludes allow other characters. Short lowercase name of the api. Can use "-" to separate words.

    • <api_name_readable>: Any alpha-numeric character or special character not used for terraform syntax. A more human readable version of the api name.

    • <is_subscription_required>: true or false. Make your api require the use of a subscription key.

    • <is_approval_required>: true or false. Make your api require approval before generating subscription key for a user.

    • <is_published>: true or false. Enable or disable your api in APIM.

    • <apim_route>: Any lowercase alpha-numeric character. excludes allow other characters. Url route appended to the end of the APIM url. This route used by callers of your service to navigate to your particular api in APIM.

    • <version>: Any integer value. The particular version of your API.

    module "<team_name>_<api_name>_api_product" { 
    source = "../../provider/azure/api_management_product"
    product_id = "<team_name>-api-<api_name>"
    api_management_name = var.apim_name
    resource_group_name = var.apim_resource_group_name
    display_name = "<api_name_readable>"
    is_subscription_required = <is_subscription_required>
    is_approval_required = <is_approval_required>
    is_published = <is_published>
    api_management_resource_group_name = var.apim_resource_group_name
    publisher = var.publisher
    apis = [{
    name = "<team_name>-api-<api_name>"
    display_name = "<api_name_readable>"
    versions = [
    {
    revision = "1"
    path = "<apim_route>"
    version = <version>
    deprecated = false enterprise = true
    }
    /* // If you need more than one version copy the below text for every
    // instance of a major version and replace <version> with that
    // major version value. You should never have two block
    // of the text below with the same <version> value.
    ,{
    revision = "1"
    path = "<apim_route>"
    version = <version>
    deprecated = false enterprise = true
    } */
    ]
    }]
    }

Deploying Your Change

After having successfully merged your change into master via a pull request using your feature branch you will need to run the APIM API deployment process.

  1. Navigate to the Azure DevOps pipeline Midmark.Enterprise.Infrastructure.Apim.Apis - CI and find the build that was created from your pull request. Note the build number next to your most recent commit message.

  2. Navigate to the Azure DevOps release Midmark.Enterprise.Infrastructure.Apim.Apis in the Enterprise Services - API Mgmt and Authentication project.

  3. Click the blue button Create a release

  4. A menu will pop up on the right of the screen. In the artifact section make sure the number underneath the Version is the same number you noted in step 1. If it isn't then, using the drop down, change it to the correct number.

  5. Click the blue Create button at the bottom of the pop up.

  6. The release will now begin running with the planning step for the DEV environment. The process will not be able to continue unless the 'DEV - Plan' step runs successfully. It is heavily suggested click on the DEV - Plan step and find the task called 'Terraform plan dev'. Verify in the logs here to make sure that the changes in terraform are what you are expecting.

  7. After validating the 'Terraform plan dev' task, navigate back to your release and click the blue Approve button. This will now deploy your changes to Azure. This step must be successful in order to continue deploying to the other environments.

  8. Repeat steps 6 & 7 for the TEST and PROD environments.

  9. Once the step 'PROD - Apply' has been completed successfully you are done with deployment.

Definition Upload

After having successfully changed terraform and pushed your changes to Azure you will next need to upload your swagger.json to APIM.

DECO API Deployment Process Example

Below is the steps the DECO team is using for deploying their swagger.json and may not necessarily be the same as your teams process. The Users API will be used as an example.

  1. After having made a change to your API and having your feature branch merged into the master branch via a pull request you will need to build your API. Navigate to the Midmark.DECO.Users.API - Release pipeline. Note the build id to be used in later steps.

  2. Navigate to the release Midmark.DECO.Users.API.

  3. Click the blue button at the top of the screen labeled Create release. In the artifact section make sure the number underneath the Version is the same number you noted in step 1. If it isn't then, using the drop down, change it to the correct number.

  4. The api will begin deploying to DEV as soon as the release is created use the step 'dev app deploy'. During this process the executable code is deployed to Azure and then we use the below powershell script to upload our swagger.json to APIM.

  5. After the code has successfully deployed to DEV, navigate back to your release and click the blue Approve button.

  6. Repeat step 5 for the 'test app deploy' and 'prod app deploy'.

  7. Once the step 'prod app deploy' has been completed successfully you are done with deployment.

How to push swagger.json to APIM via Powershell

The powershell script below grabs all versioned swagger.json that were created using a MSBuild build task. This is how the DECO team is uploading their swagger.json to APIM. We included this section to help other teams that will need to do a similar task.

  • $groupName: resource group of APIM. The value for DEV should be 'mm-rg-apim-dev-001'.

  • $instanceName: Name of the APIM instance in Azure. The value for DEV should be 'mm-apim-dev-001'.

  • $apimPath: The url apim path for a given api. The value for the users api should be 'users'. This must be the same value as the 'apim_route' in the terraform code changes on step 2.

  • $apimApiName: The name for a given api in APIM. The value for the users api should be 'b2c-api-users'. This must be the same value as the 'api_name' in the terraform code changes on step 2.

  • $serviceUrl: The url path to make requests for your service. The value for the users api in DEV should be 'https://decop-app-usersapi-dev-001.azurewebsites.net '.

  • $swaggerPath: The file system path for where your swagger.json file is located.

param (
[Parameter(Mandatory)][string]$groupName,
[Parameter(Mandatory)][string]$instanceName,
[Parameter(Mandatory)][string]$apimPath,
[Parameter(Mandatory)][string]$apimApiName,
[Parameter(Mandatory)][string]$serviceUrl,
[Parameter(Mandatory)][string]$swaggerPath
)

$ApiMgmtContext = New-AzApiManagementContext -ResourceGroupName $groupName -ServiceName $instanceName

Write-Output "Successfully found ApiManagementContext from Azure."

$swaggerFiles = Get-ChildItem -Path $swaggerPath -Filter *.swagger.json -Recurse

Write-Output "Finding Swagger Files."

if($swaggerFiles.Length -eq 0){
Write-Error "No swagger files found. Stopping..." -ErrorAction Stop
}
else {
Write-Output "Found Swagger Files."
Write-Output $swaggerFiles
}

foreach($swaggerFile in $swaggerFiles) {
Write-Output "Importing file to APIM: $swaggerFile"
$swagerFileContent = Get-Content -Raw -Path $swaggerFile | ConvertFrom-Json
$version = $swagerFileContent.info.version
$apiMgmtApiName = "$apimApiName-$version"
$import = Import-AzApiManagementApi -Context $ApiMgmtContext -ApiId $apiMgmtApiName -SpecificationFormat 'OpenApi' -SpecificationPath $swaggerFile -Path $apimPath

if(!$import){
Write-Error "Import failed. Stopping..." -ErrorAction Stop
}
Write-Output "Successfully imported swagger.json to [$apimApiName] $apimPath"
}
$policy = "<policies><inbound><base /><set-backend-service base-url=""$($serviceUrl)"" /></inbound><backend><forward-request /></backend></policies>"
Write-Output "Setting policy"
Write-Output $policy
Set-AzApiManagementPolicy -Context $ApiMgmtContext -ApiId $apiMgmtApiName -Policy $policy
Write-Output "Successfully set new policy for backend service."