Deploying OpenAPI in Azure API Management with Terraform
An OpenAPI definition has to be deployed to API Management. The deployment is done with Terraform. Will an updated OpenAPI definition also update API Management API and operations?
The entire code can be found in GitHub https://github.com/PlanBGmbH/usecases.azure.apimanagement/tree/main/src/APIM-with-OpenAPI
Table of Contents
1 Terraform
2 Create API
3 Update API
1 Terraform
With Terraform, API Management and also the Example API is created. Thefore the local OpenAPI definition file has to be imported.
resource "azurerm_api_management" "example" {
name = var.apim_name
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
publisher_name = "PlanB. GmbH"
publisher_email = "markus1.meyer@plan-b-gmbh.om"
sku_name = "Developer_1"
}
resource "azurerm_api_management_api" "example" {
name = "example-api"
resource_group_name = azurerm_resource_group.rg.name
api_management_name = azurerm_api_management.example.name
revision = "1"
display_name = "Example API"
path = "example"
protocols = ["https"]
import {
content_format = "openapi"
content_value = file("${path.module}/example-api.yaml")
}
}
2 Create API
The initial version of the OpenAPI contains only the path '/users/{username}'
with a GET and PUT operation.
The OpenAPI definition is copied from https://github.com/Redocly/openapi-template/blob/gh-pages/openapi.yaml
# This is an **example** API to demonstrate features of OpenAPI specification.
# It doesn't cover all OpenAPI features. For more full example check out: https://github.com/APIs-guru/petstore_extended
openapi: 3.0.1
info:
version: '1.0.0' # Your API version
# It can be any string but it is better to use semantic versioning: http://semver.org/
# Warning: OpenAPI requires the version to be a string, but without quotation marks YAML can recognize it as a number.
title: Example.com # Replace with your API title
# Keep it simple. Don't add "API" or version at the end of the string.
termsOfService: 'https://example.com/terms/' # [Optional] Replace with an URL to your ToS
contact:
email: contact@example.com # [Optional] Replace with your contact email
url: 'http://example.com/contact' # [Optional] Replace with link to your contact form
license:
name: Apache 2.0
url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
x-logo:
url: 'https://redocly.github.io/openapi-template/logo.png'
# Describe your API here, you can use GFM (https://guides.github.com/features/mastering-markdown) here
description: |
This is an **example** API to demonstrate features of OpenAPI specification
# Introduction
This API definition is intended to to be a good starting point for describing your API in
[OpenAPI/Swagger format](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md).
It also demonstrates features of [create-openapi-repo](https://github.com/Redocly/create-openapi-repo) tool and
[Redoc](https://github.com/Redocly/Redoc) documentation engine. So beyond the standard OpenAPI syntax we use a few
[vendor extensions](https://github.com/Redocly/Redoc/blob/master/docs/redoc-vendor-extensions.md).
# OpenAPI Specification
The goal of The OpenAPI Specification is to define a standard, language-agnostic interface to REST APIs which
allows both humans and computers to discover and understand the capabilities of the service without access to source
code, documentation, or through network traffic inspection. When properly defined via OpenAPI, a consumer can
understand and interact with the remote service with a minimal amount of implementation logic. Similar to what
interfaces have done for lower-level programming, OpenAPI removes the guesswork in calling the service.
externalDocs:
description: Find out how to create a GitHub repo for your OpenAPI definition.
url: 'https://github.com/Rebilly/generator-openapi-repo'
# A list of tags used by the definition with additional metadata.
# The order of the tags can be used to reflect on their order by the parsing tools.
- name: User
description: Operations about user
servers:
- url: 'http://example.com/api/v1'
- url: 'https://example.com/api/v1'
# Holds the relative paths to the individual endpoints. The path is appended to the
# basePath in order to construct the full URL.
paths:
'/users/{username}': # path parameter in curly braces
# parameters list that are used with each operation for this path
parameters:
- name: pretty_print
in: query
description: Pretty print response
schema:
type: boolean
get: # documentation for GET operation for this path
tags:
- User
# summary is up to 120 symbold but we recommend to be shortest as possible
summary: Get user by user name
# you can use GFM in operation description too: https://guides.github.com/features/mastering-markdown
description: |
Some description of the operation.
You can use `markdown` here.
# operationId should be unique across the whole specification
operationId: getUserByName
# list of parameters for the operation
parameters:
- name: username
in: path
description: The name that needs to be fetched
required: true
schema:
type: string
- name: with_email
in: query
description: Filter users without email
schema:
type: boolean
# security schemas applied to this operation
security:
- main_auth:
- 'read:users' # for oauth2 provide list of scopes here
- api_key: []
responses: # list of responses
'200':
description: Success
content:
application/json: # operation response mime type
schema: # response schema can be specified for each response
$ref: '#/components/schemas/User'
example: # response example
username: user1
email: user@example.com
'403':
description: Forbidden
'404':
description: User not found
# documentation for PUT operation for this path
put:
tags:
- User
summary: Updated user
description: This can only be done by the logged in user.
operationId: updateUser
parameters:
- name: username
in: path
description: The name that needs to be updated
required: true
schema:
type: string
security:
- main_auth:
- 'write:users'
responses:
'200':
description: OK
'400':
description: Invalid user supplied
'404':
description: User not found
# request body documentation
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/User'
application/xml:
schema:
$ref: '#/components/schemas/User'
description: Updated user object
required: true
# An object to hold reusable parts that can be used across the definition
components:
schemas:
Email:
description: User email address
type: string
format: test
example: john.smith@example.com
User:
type: object
properties:
username:
description: User supplied username
type: string
minLength: 4
example: John78
firstName:
description: User first name
type: string
minLength: 1
example: John
lastName:
description: User last name
type: string
minLength: 1
example: Smith
email:
$ref: '#/components/schemas/Email'
headers:
ExpiresAfter:
description: date in UTC when token expires
schema:
type: string
format: date-time
# Security scheme definitions that can be used across the definition.
securitySchemes:
main_auth: # security definition name (you can name it as you want)
# the following options are specific to oauth2 type
type: oauth2 # authorization type, one of: oauth2, apiKey, http
flows:
implicit:
authorizationUrl: 'http://example.com/api/oauth/dialog'
scopes:
'read:users': read users info
'write:users': modify or remove users
api_key: # security definition name (you can name it as you want)
type: apiKey
# The following options are specific to apiKey type
in: header # Where API key will be passed: header or query
name: api_key # API key parameter name
basic_auth: # security definition name (you can name it as you want)
type: http
scheme: basic
Execute terraform:
terraform plan
terraform apply
The API Management with API 'Example API' is successfully created:
3 Update API
The OpenAPI definition is updated with path /echo
.
# This is an **example** API to demonstrate features of OpenAPI specification.
# It doesn't cover all OpenAPI features. For more full example check out: https://github.com/APIs-guru/petstore_extended
openapi: 3.0.1
info:
version: '1.0.0' # Your API version
# It can be any string but it is better to use semantic versioning: http://semver.org/
# Warning: OpenAPI requires the version to be a string, but without quotation marks YAML can recognize it as a number.
title: Example.com # Replace with your API title
# Keep it simple. Don't add "API" or version at the end of the string.
termsOfService: 'https://example.com/terms/' # [Optional] Replace with an URL to your ToS
contact:
email: contact@example.com # [Optional] Replace with your contact email
url: 'http://example.com/contact' # [Optional] Replace with link to your contact form
license:
name: Apache 2.0
url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
x-logo:
url: 'https://redocly.github.io/openapi-template/logo.png'
# Describe your API here, you can use GFM (https://guides.github.com/features/mastering-markdown) here
description: |
This is an **example** API to demonstrate features of OpenAPI specification
# Introduction
This API definition is intended to to be a good starting point for describing your API in
[OpenAPI/Swagger format](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md).
It also demonstrates features of [create-openapi-repo](https://github.com/Redocly/create-openapi-repo) tool and
[Redoc](https://github.com/Redocly/Redoc) documentation engine. So beyond the standard OpenAPI syntax we use a few
[vendor extensions](https://github.com/Redocly/Redoc/blob/master/docs/redoc-vendor-extensions.md).
# OpenAPI Specification
The goal of The OpenAPI Specification is to define a standard, language-agnostic interface to REST APIs which
allows both humans and computers to discover and understand the capabilities of the service without access to source
code, documentation, or through network traffic inspection. When properly defined via OpenAPI, a consumer can
understand and interact with the remote service with a minimal amount of implementation logic. Similar to what
interfaces have done for lower-level programming, OpenAPI removes the guesswork in calling the service.
externalDocs:
description: Find out how to create a GitHub repo for your OpenAPI definition.
url: 'https://github.com/Rebilly/generator-openapi-repo'
# A list of tags used by the definition with additional metadata.
# The order of the tags can be used to reflect on their order by the parsing tools.
tags:
- name: Echo
description: Example echo operations
- name: User
description: Operations about user
servers:
- url: 'http://example.com/api/v1'
- url: 'https://example.com/api/v1'
# Holds the relative paths to the individual endpoints. The path is appended to the
# basePath in order to construct the full URL.
paths:
'/users/{username}': # path parameter in curly braces
# parameters list that are used with each operation for this path
parameters:
- name: pretty_print
in: query
description: Pretty print response
schema:
type: boolean
get: # documentation for GET operation for this path
tags:
- User
# summary is up to 120 symbold but we recommend to be shortest as possible
summary: Get user by user name
# you can use GFM in operation description too: https://guides.github.com/features/mastering-markdown
description: |
Some description of the operation.
You can use `markdown` here.
# operationId should be unique across the whole specification
operationId: getUserByName
# list of parameters for the operation
parameters:
- name: username
in: path
description: The name that needs to be fetched
required: true
schema:
type: string
- name: with_email
in: query
description: Filter users without email
schema:
type: boolean
# security schemas applied to this operation
security:
- main_auth:
- 'read:users' # for oauth2 provide list of scopes here
- api_key: []
responses: # list of responses
'200':
description: Success
content:
application/json: # operation response mime type
schema: # response schema can be specified for each response
$ref: '#/components/schemas/User'
example: # response example
username: user1
email: user@example.com
'403':
description: Forbidden
'404':
description: User not found
# documentation for PUT operation for this path
put:
tags:
- User
summary: Updated user
description: This can only be done by the logged in user.
operationId: updateUser
parameters:
- name: username
in: path
description: The name that needs to be updated
required: true
schema:
type: string
security:
- main_auth:
- 'write:users'
responses:
'200':
description: OK
'400':
description: Invalid user supplied
'404':
description: User not found
# request body documentation
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/User'
application/xml:
schema:
$ref: '#/components/schemas/User'
description: Updated user object
required: true
/echo: # path parameter in curly braces
post: # documentation for POST operation for this path
tags:
- Echo
summary: Echo test
description: Receive the exact message you've sent
operationId: echo
security:
- api_key: []
- basic_auth: []
responses:
'200':
description: OK
# document headers for this response
headers:
X-Rate-Limit: # Header name
description: calls per hour allowed by the user
schema: # Header schema
type: integer
format: int32
X-Expires-After:
$ref: '#/components/headers/ExpiresAfter'
content:
application/json:
schema:
type: string
examples:
response:
value: Hello world!
application/xml:
schema:
type: string
text/csv:
schema:
type: string
requestBody:
content:
application/json:
schema:
type: string
example: Hello world!
application/xml:
schema:
type: string
example: Hello world!
description: Echo payload
required: true
# An object to hold reusable parts that can be used across the definition
components:
schemas:
Email:
description: User email address
type: string
format: test
example: john.smith@example.com
User:
type: object
properties:
username:
description: User supplied username
type: string
minLength: 4
example: John78
firstName:
description: User first name
type: string
minLength: 1
example: John
lastName:
description: User last name
type: string
minLength: 1
example: Smith
email:
$ref: '#/components/schemas/Email'
headers:
ExpiresAfter:
description: date in UTC when token expires
schema:
type: string
format: date-time
# Security scheme definitions that can be used across the definition.
securitySchemes:
main_auth: # security definition name (you can name it as you want)
# the following options are specific to oauth2 type
type: oauth2 # authorization type, one of: oauth2, apiKey, http
flows:
implicit:
authorizationUrl: 'http://example.com/api/oauth/dialog'
scopes:
'read:users': read users info
'write:users': modify or remove users
api_key: # security definition name (you can name it as you want)
type: apiKey
# The following options are specific to apiKey type
in: header # Where API key will be passed: header or query
name: api_key # API key parameter name
basic_auth: # security definition name (you can name it as you want)
type: http
scheme: basic
Execute Terraform:
terraform plan
Plan: 0 to add, 1 to change, 0 to destroy.
terraform apply
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
After deployment, the changes to the OpenAPI definition were deployed and the added operation Echo test exists:
Subscribe to my newsletter
Read articles from Markus Meyer directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Markus Meyer
Markus Meyer
I am a developer and architect from Bavaria. I'm focused on Azure technologies. And I love coding in C#.