Skip to content

Commit

Permalink
feat: enable openapi spec from url in api register (#74)
Browse files Browse the repository at this point in the history
* feat: enable openapi spec from url in api register

* refactor: set spec definition as link format when link provided

* fix: fix style

* test: add error handling case for testing invalid spec url

* fix: fix test case

* fix: use 404 response url

* test: update case

* test: update test case

* refactor: update error logic
  • Loading branch information
frankqianms authored Oct 16, 2024
1 parent ac58b53 commit ae37879
Show file tree
Hide file tree
Showing 7 changed files with 1,856 additions and 21 deletions.
4 changes: 4 additions & 0 deletions src/apic-extension/azext_apic_extension/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,8 @@
text: |
az apic api register -g api-center-test -n contosoeuap --api-location "examples/cli-examples/spec-examples/openai.json" --environment-id public
az apic api register -g api-center-test -n contosoeuap --api-location "examples/cli-examples/spec-examples/openai.yml" --environment-id public
- name: Register api by providing spec url.
text: |
az apic api register -g api-center-test -n contosoeuap --api-location "https://petstore.swagger.io/v2/swagger.json" --environment-id public
az apic api register -g api-center-test -n contosoeuap --api-location "https://petstore.swagger.io/v2/swagger.yaml" --environment-id public
"""
69 changes: 49 additions & 20 deletions src/apic-extension/azext_apic_extension/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
from .command_patches import ExportAPIDefinitionExtension
from .command_patches import ExportMetadataExtension

from azure.cli.core.azclierror import InvalidArgumentValueError

logger = get_logger(__name__)


Expand Down Expand Up @@ -162,26 +164,53 @@ def register_apic(cmd, api_location, resource_group, service_name, environment_i
# Load the JSON file
if api_location:

# TODO Future Confirm its a file and not link
with open(str(api_location), 'rb') as f:
rawdata = f.read()
result = chardet.detect(rawdata)
encoding = result['encoding']

# TODO - read other file types later
value = None
if str(api_location).endswith('.yaml') or str(api_location).endswith('.yml'):
with open(str(api_location), 'r', encoding=encoding) as f:
content = f.read()
data = yaml.safe_load(content)
if data:
value = content
if (str(api_location).endswith('.json')):
with open(str(api_location), 'r', encoding=encoding) as f:
content = f.read()
data = json.loads(content)
if data:
value = content
custom_format = 'inline'
# Read the spec content from URL
if str(api_location).startswith('https://') or str(api_location).startswith('http://'):
try:
# Fetch the content from the URL
response = requests.get(api_location)
# Raise an error for bad status codes
response.raise_for_status()
# Try to parse the content as JSON
try:
data = json.loads(response.content)
except json.JSONDecodeError:
try:
# If JSON parsing fails, try to parse as YAML
data = yaml.safe_load(response.content)
except yaml.YAMLError as e:
data = None
value = None
raise InvalidArgumentValueError(error_msg=f"Error parsing data from {api_location}: {e}")
# sys.exit(-1)
# If we could parse the content(json or yaml), set format to link
value = str(api_location) if data else None
custom_format = 'link' if data else 'inline'
except requests.exceptions.RequestException as e:
data = None
value = None
raise InvalidArgumentValueError(error_msg=f"Error fetching data from invalid url {api_location}: {e}")
# sys.exit(-1)
else:
# Confirm its a file and not link
with open(str(api_location), 'rb') as f:
rawdata = f.read()
result = chardet.detect(rawdata)
encoding = result['encoding']

# TODO - read other file types later
if str(api_location).endswith('.yaml') or str(api_location).endswith('.yml'):
with open(str(api_location), 'r', encoding=encoding) as f:
content = f.read()
data = yaml.safe_load(content)
value = content if data else None
if (str(api_location).endswith('.json')):
with open(str(api_location), 'r', encoding=encoding) as f:
content = f.read()
data = json.loads(content)
value = content if data else None

# If we could not read the file, return error
if value is None:
Expand Down Expand Up @@ -315,7 +344,7 @@ def register_apic(cmd, api_location, resource_group, service_name, environment_i
'api_id': extracted_api_name,
'version_id': extracted_api_version,
'definition_id': extracted_definition_name,
'format': 'inline',
'format': custom_format,
'specification': specification_details, # TODO write the correct spec object
'value': value
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
User-Agent:
- python-requests/2.32.3
method: GET
uri: https://github.com/
response:
body:
string: '!!! The response body has been omitted from the recording because it
is larger than 128 KB. It will be replaced with blank content of 254684 bytes
while replay. <CTRL-REPLACE>254684'
headers:
accept-ranges:
- bytes
cache-control:
- max-age=0, private, must-revalidate
content-language:
- en-US
content-length:
- '181'
content-security-policy:
- 'default-src ''none''; base-uri ''self''; child-src github.com/assets-cdn/worker/
github.com/webpack/ github.com/assets/ gist.github.com/assets-cdn/worker/;
connect-src ''self'' uploads.github.com www.githubstatus.com collector.github.com
raw.githubusercontent.com api.github.com github-cloud.s3.amazonaws.com github-production-repository-file-5c1aeb.s3.amazonaws.com
github-production-upload-manifest-file-7fdce7.s3.amazonaws.com github-production-user-asset-6210df.s3.amazonaws.com
*.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com
objects-origin.githubusercontent.com copilot-proxy.githubusercontent.com proxy.individual.githubcopilot.com
proxy.business.githubcopilot.com proxy.enterprise.githubcopilot.com *.actions.githubusercontent.com
wss://*.actions.githubusercontent.com productionresultssa0.blob.core.windows.net/
productionresultssa1.blob.core.windows.net/ productionresultssa2.blob.core.windows.net/
productionresultssa3.blob.core.windows.net/ productionresultssa4.blob.core.windows.net/
productionresultssa5.blob.core.windows.net/ productionresultssa6.blob.core.windows.net/
productionresultssa7.blob.core.windows.net/ productionresultssa8.blob.core.windows.net/
productionresultssa9.blob.core.windows.net/ productionresultssa10.blob.core.windows.net/
productionresultssa11.blob.core.windows.net/ productionresultssa12.blob.core.windows.net/
productionresultssa13.blob.core.windows.net/ productionresultssa14.blob.core.windows.net/
productionresultssa15.blob.core.windows.net/ productionresultssa16.blob.core.windows.net/
productionresultssa17.blob.core.windows.net/ productionresultssa18.blob.core.windows.net/
productionresultssa19.blob.core.windows.net/ github-production-repository-image-32fea6.s3.amazonaws.com
github-production-release-asset-2e65be.s3.amazonaws.com insights.github.com
wss://alive.github.com api.githubcopilot.com api.individual.githubcopilot.com
api.business.githubcopilot.com api.enterprise.githubcopilot.com github.githubassets.com
edge.fullstory.com rs.fullstory.com; font-src github.githubassets.com; form-action
''self'' github.com gist.github.com copilot-workspace.githubnext.com objects-origin.githubusercontent.com;
frame-ancestors ''none''; frame-src viewscreen.githubusercontent.com notebooks.githubusercontent.com
www.youtube-nocookie.com; img-src ''self'' data: blob: github.githubassets.com
media.githubusercontent.com camo.githubusercontent.com identicons.github.com
avatars.githubusercontent.com private-avatars.githubusercontent.com github-cloud.s3.amazonaws.com
objects.githubusercontent.com secured-user-images.githubusercontent.com/ user-images.githubusercontent.com/
private-user-images.githubusercontent.com opengraph.githubassets.com github-production-user-asset-6210df.s3.amazonaws.com
customer-stories-feed.github.com spotlights-feed.github.com objects-origin.githubusercontent.com
*.githubusercontent.com; manifest-src ''self''; media-src github.com user-images.githubusercontent.com/
secured-user-images.githubusercontent.com/ private-user-images.githubusercontent.com
github-production-user-asset-6210df.s3.amazonaws.com gist.github.com github.githubassets.com;
script-src github.githubassets.com; style-src ''unsafe-inline'' github.githubassets.com;
upgrade-insecure-requests; worker-src github.com/assets-cdn/worker/ github.com/webpack/
github.com/assets/ gist.github.com/assets-cdn/worker/'
content-type:
- text/html; charset=utf-8
date:
- Tue, 15 Oct 2024 04:01:12 GMT
etag:
- W/"2a20e14cbb76e542f3c5fed75a897deb"
referrer-policy:
- origin-when-cross-origin, strict-origin-when-cross-origin
server:
- GitHub.com
set-cookie:
- _gh_sess=nDe4UkSl89r%2BcpkpALiHWruZQSCE0K7Fv4fZdUJ16N4E1RiBG7xxX7sfwTTFgEBKMq3qRcEnxXvS3tZOl5yHbg1o2Uda3WqjBs4TNl7cYJI7F6QT5eyX%2FtSiHQYzoxuAyhq5e%2F%2Bouf17OSFst%2FoFYupJtDycyzraeIzFF7CY6%2FLMz8AvqcXpf%2FQBM2C0pU%2FZQDG%2F9pdSzk6xumiDwggGAJ1WhMgHSE%2FPWlQIQoPfh%2FF%2FOwIOs7NWfq6d5zkw0tcAbzNO%2Fo5x1oL1C7EY88z2%2Bg%3D%3D--DX1tQfpx6VemsBsP--fkxfh8oqGwFOgJFUsVzA5Q%3D%3D;
Path=/; HttpOnly; Secure; SameSite=Lax
- _octo=GH1.1.1313912529.1728964875; Path=/; Domain=github.com; Expires=Wed,
15 Oct 2025 04:01:15 GMT; Secure; SameSite=Lax
- logged_in=no; Path=/; Domain=github.com; Expires=Wed, 15 Oct 2025 04:01:15
GMT; HttpOnly; Secure; SameSite=Lax
strict-transport-security:
- max-age=31536000; includeSubdomains; preload
transfer-encoding:
- chunked
vary:
- X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame, Accept-Language, Accept-Encoding,
Accept, X-Requested-With
x-content-type-options:
- nosniff
x-frame-options:
- deny
x-github-request-id:
- 0806:21A5DE:29131BD:2D8EF81:670DE90B
x-xss-protection:
- '0'
status:
code: 200
message: OK
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
CommandName:
- apic api show
Connection:
- keep-alive
ParameterSetName:
- -g -n --api-id
User-Agent:
- AZURECLI/2.63.0 azsdk-python-core/1.28.0 Python/3.11.9 (Windows-10-10.0.22631-SP0)
method: GET
uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clirg000001/providers/Microsoft.ApiCenter/services/clitest000002/workspaces/default/apis/swaggerpetstore?api-version=2024-03-01
response:
body:
string: '{"code":"404"}'
headers:
api-supported-versions:
- 2023-07-01-preview, 2024-03-01, 2024-03-15-preview
cache-control:
- no-cache
content-length:
- '14'
content-type:
- application/json; charset=utf-8
date:
- Tue, 15 Oct 2024 04:01:16 GMT
expires:
- '-1'
pragma:
- no-cache
strict-transport-security:
- max-age=31536000; includeSubDomains
vary:
- Accept-Encoding
x-cache:
- CONFIG_NOCACHE
x-content-type-options:
- nosniff
x-ms-ratelimit-remaining-subscription-global-reads:
- '16499'
x-msedge-ref:
- 'Ref A: F157DB4EBD6E442D94239BD1D1EBFE1A Ref B: MAA201060513023 Ref C: 2024-10-15T04:01:15Z'
x-powered-by:
- ASP.NET
status:
code: 404
message: Not Found
version: 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
interactions:
- request:
body: null
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
User-Agent:
- python-requests/2.32.3
method: GET
uri: https://github.com/invalidrepo
response:
body:
string: Not Found
headers:
cache-control:
- no-cache
content-length:
- '9'
content-security-policy:
- default-src 'none'; base-uri 'self'; connect-src 'self'; form-action 'self';
img-src 'self' data:; script-src 'self'; style-src 'unsafe-inline'
content-type:
- text/plain; charset=utf-8
date:
- Tue, 15 Oct 2024 04:01:14 GMT
referrer-policy:
- origin-when-cross-origin, strict-origin-when-cross-origin
server:
- GitHub.com
set-cookie:
- _gh_sess=05a6i77a0k6QZ7mJnH%2BnWIliR5guipRh0Z%2Fs73RhXbNuLqVAnKu19dDhM1kWOhpwMFe8ZSuBFfzUJOop9ta9yqM6ZL3qs0chytbmyaEh%2F6YELXvhhcXHO1za3tMCbJBynfJ2HGeIRPC1GyepCLgKnyg13K1gRKMbFbxNTUgqERu59BrD%2FNGDDp1bdz4EYNrl5THCBqSX8Ozp%2Foi1ZKPOaJK0LujquBpWJqd7CQ09E9EUxeIqUSeKPPrUv4S41%2B8B9pLJDrHign95vSXBE48xMQ%3D%3D--N%2F4dD85JrQ2c5oY5--X7gxxV%2Bs35GOWsptYdJVPw%3D%3D;
Path=/; HttpOnly; Secure; SameSite=Lax
- _octo=GH1.1.98402674.1728964874; Path=/; Domain=github.com; Expires=Wed, 15
Oct 2025 04:01:14 GMT; Secure; SameSite=Lax
- logged_in=no; Path=/; Domain=github.com; Expires=Wed, 15 Oct 2025 04:01:14
GMT; HttpOnly; Secure; SameSite=Lax
strict-transport-security:
- max-age=31536000; includeSubdomains; preload
vary:
- X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame, Accept-Encoding, Accept,
X-Requested-With
x-content-type-options:
- nosniff
x-frame-options:
- deny
x-github-request-id:
- 6596:211F6C:2906DAC:2D82A98:670DE90A
x-xss-protection:
- '0'
status:
code: 404
message: Not Found
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- gzip, deflate
CommandName:
- apic api show
Connection:
- keep-alive
ParameterSetName:
- -g -n --api-id
User-Agent:
- AZURECLI/2.63.0 azsdk-python-core/1.28.0 Python/3.11.9 (Windows-10-10.0.22631-SP0)
method: GET
uri: https://management.azure.com/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/clirg000001/providers/Microsoft.ApiCenter/services/clitest000002/workspaces/default/apis/swaggerpetstore?api-version=2024-03-01
response:
body:
string: '{"code":"404"}'
headers:
api-supported-versions:
- 2023-07-01-preview, 2024-03-01, 2024-03-15-preview
cache-control:
- no-cache
content-length:
- '14'
content-type:
- application/json; charset=utf-8
date:
- Tue, 15 Oct 2024 04:01:15 GMT
expires:
- '-1'
pragma:
- no-cache
strict-transport-security:
- max-age=31536000; includeSubDomains
vary:
- Accept-Encoding
x-cache:
- CONFIG_NOCACHE
x-content-type-options:
- nosniff
x-ms-ratelimit-remaining-subscription-global-reads:
- '16499'
x-msedge-ref:
- 'Ref A: F3CFA1D4358D4160A2BCF0E69AEE0789 Ref B: MAA201060515025 Ref C: 2024-10-15T04:01:14Z'
x-powered-by:
- ASP.NET
status:
code: 404
message: Not Found
version: 1
Loading

0 comments on commit ae37879

Please sign in to comment.