Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[API Management] az apim api export: Add command to export an API Management API #28279

Merged
merged 14 commits into from
Feb 19, 2024
9 changes: 9 additions & 0 deletions src/azure-cli/azure/cli/command_modules/apim/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,15 @@
az apim api import -g MyResourceGroup --service-name MyApim --path MyApi --specification-url https://MySpecificationURL --specification-format OpenApiJson
"""

helps['apim api export'] = """
type: command
short-summary: Export an API Management API.
examples:
- name: Export an API Management API to a file or returns a response containing a link of the export.
text: |-
az apim api export -g MyResourceGroup --service-name MyApim --api-id MyApi --export-format OpenApiJson --file-path path
"""

helps['apim product api list'] = """
type: command
short-summary: Lists a collection of the APIs associated with a product.
Expand Down
25 changes: 25 additions & 0 deletions src/azure-cli/azure/cli/command_modules/apim/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,19 @@ class ImportFormat(Enum):
GraphQL = "GraphQL"


class ExportFormat(Enum):
WadlFile = "WadlFile"
SwaggerFile = "SwaggerFile"
OpenApiFile = "OpenApiYamlFile"
OpenApiJsonFile = "OpenApiJsonFile"
WsdlFile = "WsdlFile"
WadlUrl = "WadlUrl"
SwaggerUrl = "SwaggerUrl"
OpenApiUrl = "OpenApiYamlUrl"
OpenApiJsonUrl = "OpenApiJsonUrl"
WsdlUrl = "WsdlUrl"


def load_arguments(self, _):

# REUSABLE ARGUMENT DEFINITIONS
Expand Down Expand Up @@ -289,6 +302,18 @@ def load_arguments(self, _):
c.argument('wsdl_service_name', help='Local name of WSDL Service to be imported.')
c.argument('wsdl_endpoint_name', help='Local name of WSDL Endpoint (port) to be imported.')

with self.argument_context('apim api export') as c:
c.argument('resource_group_name', arg_type=resource_group_name_type,
help="The name of the resource group. The name is case insensitive.")
c.argument('service_name', options_list=['--service-name', '-n'],
help="The name of the api management service instance", id_part=None)
c.argument('api_id', arg_type=api_id,
help='API identifier. Must be unique in the current API Management service instance. Non-current revision has ;rev=n as a suffix where n is the revision number. Regex pattern: ^[^*#&+:<>?]+$')
c.argument('export_format', arg_type=get_enum_type(ExportFormat), options_list=['--export-format', '--ef'],
help='Specify the format of the exporting API.')
c.argument('file_path', options_list=['--file-path', '-f'],
help='File path specified to export the API.')

with self.argument_context('apim product api list') as c:
c.argument('service_name', options_list=['--service-name', '-n'],
help="The name of the api management service instance", id_part=None)
Expand Down
1 change: 1 addition & 0 deletions src/azure-cli/azure/cli/command_modules/apim/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ def load_command_table(self, _):

with self.command_group('apim api', api_sdk) as g:
g.custom_command('import', 'apim_api_import', supports_no_wait=True)
g.custom_command('export', 'apim_api_export')
g.custom_command('create', 'apim_api_create', supports_no_wait=True)
g.custom_show_command('show', 'apim_api_get')
g.custom_command('list', 'apim_api_list')
Expand Down
115 changes: 114 additions & 1 deletion src/azure-cli/azure/cli/command_modules/apim/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

from azure.cli.command_modules.apim._params import ImportFormat
from azure.cli.core.util import sdk_no_wait
from azure.cli.core.util import get_logger
from azure.cli.core.azclierror import (RequiredArgumentMissingError, MutuallyExclusiveArgumentError,
InvalidArgumentValueError)
from azure.mgmt.apimanagement.models import (ApiManagementServiceResource, ApiManagementServiceIdentity,
Expand All @@ -39,6 +40,7 @@
OperationContract, ApiManagementServiceCheckNameAvailabilityParameters,
ApiReleaseContract, SchemaContract, ResolverContract, PolicyContract)

logger = get_logger(__name__)

# Helpers

Expand Down Expand Up @@ -514,8 +516,119 @@ def apim_api_import(
parameters=parameters)


# Product API Operations
def apim_api_export(client, resource_group_name, service_name, api_id, export_format, file_path=None):
"""Gets the details of the API specified by its identifier in the format specified """

import json
import yaml
import xml.etree.ElementTree as ET
import os
import requests

# Define the mapping from old format values to new ones
format_mapping = {
"WadlFile": "wadl-link",
"SwaggerFile": "swagger-link",
"OpenApiYamlFile": "openapi-link",
"OpenApiJsonFile": "openapi+json-link",
"WsdlFile": "wsdl-link",
"WadlUrl": "wadl-link",
"SwaggerUrl": "swagger-link",
"OpenApiYamlUrl": "openapi-link",
"OpenApiJsonUrl": "openapi+json-link",
"WsdlUrl": "wsdl-link"
}
mappedFormat = format_mapping.get(export_format)

# Export the API from APIManagement
response = client.api_export.get(resource_group_name, service_name, api_id, mappedFormat, True)

# If url is requested
if export_format in ['WadlUrl', 'SwaggerUrl', 'OpenApiYamlUrl', 'OpenApiJsonUrl', 'WsdlUrl']:
return response

# If file is requested
if file_path is None:
raise RequiredArgumentMissingError(
"Please specify file path using '--file-path' argument.")

# Obtain link from the response
response_dict = api_export_result_to_dict(response)
try:
# Extract the link from the response where results are stored
link = response_dict['additional_properties']['properties']['value']['link']
except KeyError:
logger.warning("Error exporting api from APIManagement. The expected link is not present in the response.")

# Determine the file extension based on the mappedFormat
if mappedFormat in ['swagger-link', 'openapi+json-link']:
file_extension = '.json'
elif mappedFormat in ['wsdl-link', 'wadl-link']:
file_extension = '.xml'
elif mappedFormat in ['openapi-link']:
file_extension = '.yaml'
else:
file_extension = '.txt'

# Remove '-link' from the mappedFormat and create the file name with full path
exportType = mappedFormat.replace('-link', '')
file_name = f"{api_id}_{exportType}{file_extension}"
full_path = os.path.join(file_path, file_name)

# Get the results from the link where the API Export Results are stored
try:
exportedResults = requests.get(link, timeout=30)
if not exportedResults.ok:
logger.warning("Got bad status from APIManagement during API Export:%s, {exportedResults.status_code}")
except requests.exceptions.ReadTimeout:
logger.warning("Timed out while exporting api from APIManagement.")

try:
# Try to parse as JSON
exportedResultContent = json.loads(exportedResults.text)
except json.JSONDecodeError:
try:
# Try to parse as YAML
exportedResultContent = yaml.safe_load(exportedResults.text)
except yaml.YAMLError:
try:
# Try to parse as XML
exportedResultContent = ET.fromstring(exportedResults.text)
except ET.ParseError:
logger.warning("Content is not in JSON, YAML, or XML format.")

# Write results to a file
logger.warning("Writing results to file: %s", full_path)
try:
with open(full_path, 'w') as f:
if file_extension == '.json':
json.dump(exportedResultContent, f, indent=4)
elif file_extension == '.yaml':
yaml.dump(exportedResultContent, f)
elif file_extension == '.xml':
ET.register_namespace('', 'http://wadl.dev.java.net/2009/02')
xml_string = ET.tostring(exportedResultContent, encoding='unicode')
f.write(xml_string)
else:
f.write(str(exportedResultContent))
except IOError as e:
logger.warning("Error writing exported API to file.: %s", e)

# Write the response to a file
return logger.warning("APIMExport results written to file: %s", full_path)


def api_export_result_to_dict(api_export_result):
# This function returns a dictionary representation of the ApiExportResult object
return {
'additional_properties': api_export_result.additional_properties,
'id': api_export_result.id,
'export_result_format': api_export_result.export_result_format,
'value': api_export_result.value
}


# Product API Operations
def apim_product_api_list(client, resource_group_name, service_name, product_id):

return client.product_api.list_by_product(resource_group_name, service_name, product_id)
Expand Down
Loading