This is an addition to the Automated APIM Swagger definition download post where we showed how you can download your Swagger definition from PowerShell. In this post we do it the other way around, uploading your Swagger definition to your APIM instance. With this approach you can keep your APIM API up-to-date in for example a CD pipeline. But in this example we'll update the API manually with a local swagger.json file.
So we assume that you have an APIM API instance running, if not we like to refer to the Microsoft documentation on to set this up:
A developer instance of APIM is enough to be able to test. Now let's get started and look into the script that can be used to update your APIM api:
param(
[string] $subscriptionName,
[string] $resourceGroup,
[string] $apimInstance,
[string] $apiName,
[string] $displayName,
[string] $apiPath,
[string] $swaggerFilePath
)
az apim api import -g $resourceGroup -n $apimInstance --subscription-required true --api-id $apiName --display-name $displayName --path $apiPath --specification-format "OpenAPIJson" --specification-path $swaggerFilePath --service-url $serviceUrl --debug
Deep dive into the parameters
After running the script you'll have an updated APIM API, now for our project we wanted to go a bit further and also wanted to filter out apis that we don't to publish in APIM. There are different solutions to this problem but we introduced a custom Swagger property which is used in the update process to filter out endpoints that we don't want to be published in APIM. For this we needed to create:
- A custom .net attribute to decorate the endpoints with
- An IOperationFilter
for SwashBuckle reference
First we create the custom attribute:
[AttributeUsage(AttributeTargets.Method)]
public class SwaggerApimAttribute : Attribute
{
public SwaggerApimAttribute(string[] tags)
{
Tags = tags;
}
public string[] Tags { get; }
}
With this we now have a SwaggerApim
attribute which we decorate endpoints with that we would like to publish:
[SwaggerApim(new[] {ApimTags.PublishMe})]
public async Task<IActionResult> NotRelevant()
{
return Ok();
}
The ApimTags
is just an enum to make it easy to find all reference, a string would also work here. Now we have to add the IOperationFilter
which will be responsible to create the custom attribute in the Swagger definition. This implementation will be called for each endpoint and allows us to extract custom attributes on the controller method, when the custom attribute can be found then we add an operation extension by calling operation.AddExtensions("<ATTRIBUTENAME>"). We prefix this operation with
x-` to indicate that this is a custom property!
public class AddApimTagsOperationFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var swaggerApimAttribute = context.MethodInfo.GetCustomAttribute<SwaggerApimAttribute>();
if (swaggerApimAttribute != null)
{
foreach (var apimAttribute in swaggerApimAttribute.Tags)
{
operation.AddExtension($"x-apim-{apimAttribute}", new OpenApiBoolean(true));
}
}
}
}
Next, we need to register the IOperationFilter
in the Startup.cs
where you registered Swagger:
services.AddSwaggerGen(options => { options.OperationFilter<AddApimTagsOperationFilter>(); }
If everything went ok and you run the code then you should see x-apim-publishme: true
attributes in the definition. Now we need to change the script to filter out endpoints that are decorated with these attributes. For this we use the openapi-filter
NPM package which you can check out here. For this you would need to run have NPM installed and need to run npm install -g openapi-filter
to be able to run it. Adjusted script:
param(
[Parameter(Mandatory = $true)]
[string] $resourceGroup,
[Parameter(Mandatory = $true)]
[string] $apimInstance,
[Parameter(Mandatory = $true)]
[string] $apiName,
[Parameter(Mandatory = $true)]
[string] $displayName,
[Parameter(Mandatory = $true)]
[string] $apiPath,
[Parameter(Mandatory = $true)]
[string] $swaggerFilePath
)
$openFilter = "openapi-filter -f $swaggerTag --info --valid -i -- $swaggerfile $filteredSwagger"
Invoke-Expression $openFilter
$utf8 = New-Object System.Text.UTF8Encoding $false
$MyFile = Get-Content $filteredSwagger -Raw
Set-Content -Value $utf8.GetBytes($MyFile) -AsByteStream -Path $filteredSwagger
az apim api import -g $resourceGroup -n $apimInstance --subscription-required true --api-id $apiName --display-name $displayName --path $apiPath --specification-format "OpenAPIJson" --specification-path $filteredSwagger --service-url $serviceUrl --debug
Now after running the script again only the decorated api endpoints will be in the Swagger definition and are published to APIM. Obviously running this from your machine doesn't make much sense, but it works quite well if you integrate this in your CD pipeline to keep your APIM instance up-to-date.
Hope you enjoyed and learned something from this post! Feel free to contact us if you have any questions or remarks!