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

feat: enable openapi spec from url in api register #74

Merged
merged 9 commits into from
Oct 16, 2024
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
frankqianms marked this conversation as resolved.
Show resolved Hide resolved
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:
frankqianms marked this conversation as resolved.
Show resolved Hide resolved
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
Loading