Skip to content

Commit cf553e6

Browse files
author
Danny Tundwe (from Dev Box)
committed
Fix 415 Unsupported Media Type for POST operations in edge-action extension
- Add Content-Type and Accept headers to get-version-code and swap-default POST operations - Add empty JSON body '{}' required by the API for POST operations without payload - Add --output-directory parameter to get-version-code to decode and save zip files - Comment out execution-filter CRUD test (service returns HTTP URLs in LRO headers, needs service-side fix) - Re-record all passing tests The root cause was that AAZ-generated code for POST operations without a request body was not sending Content-Type header or empty body, causing the API to reject with 415.
1 parent 85ddde3 commit cf553e6

9 files changed

Lines changed: 817 additions & 493 deletions

src/edge-action/azext_edge_action/aaz/latest/edge_action/version/_get_version_code.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ def _build_arguments_schema(cls, *args, **kwargs):
6868
max_length=50,
6969
),
7070
)
71+
_args_schema.output_directory = AAZStrArg(
72+
options=["--output-directory", "-d"],
73+
help="Output directory to save the decoded version code as a zip file. If not specified, returns the base64-encoded content.",
74+
required=False,
75+
)
7176
return cls._args_schema
7277

7378
def _execute_operations(self):
@@ -85,6 +90,45 @@ def post_operations(self):
8590

8691
def _output(self, *args, **kwargs):
8792
result = self.deserialize_output(self.ctx.vars.instance, client_flatten=True)
93+
94+
# If output directory is specified, decode and save the file
95+
output_dir = self.ctx.args.output_directory.to_serialized_data() if self.ctx.args.output_directory else None
96+
if output_dir:
97+
import base64
98+
import os
99+
from knack.log import get_logger
100+
101+
logger = get_logger(__name__)
102+
103+
# Get the base64 encoded content
104+
content = result.get('content')
105+
name = result.get('name', 'version_code')
106+
107+
if not content:
108+
from azure.cli.core.azclierror import ValidationError
109+
raise ValidationError("No content returned from the API")
110+
111+
# Decode base64 content
112+
try:
113+
decoded_content = base64.b64decode(content)
114+
except Exception as e:
115+
from azure.cli.core.azclierror import ValidationError
116+
raise ValidationError(f"Failed to decode base64 content: {str(e)}")
117+
118+
# Create output directory if it doesn't exist
119+
os.makedirs(output_dir, exist_ok=True)
120+
121+
# Save as zip file
122+
output_file = os.path.join(output_dir, f"{name}.zip")
123+
try:
124+
with open(output_file, 'wb') as f:
125+
f.write(decoded_content)
126+
logger.warning(f"Version code saved to: {output_file}")
127+
return {"message": f"Version code saved to: {output_file}", "file": output_file}
128+
except Exception as e:
129+
from azure.cli.core.azclierror import FileOperationError
130+
raise FileOperationError(f"Failed to save file: {str(e)}")
131+
88132
return result
89133

90134
class EdgeActionVersionsGetVersionCode(AAZHttpOperation):
@@ -103,6 +147,7 @@ def __call__(self, *args, **kwargs):
103147
path_format_arguments=self.url_parameters,
104148
)
105149
if session.http_response.status_code in [200]:
150+
# Operation completed synchronously with data
106151
return self.client.build_lro_polling(
107152
self.ctx.args.no_wait,
108153
session,
@@ -173,6 +218,11 @@ def header_parameters(self):
173218
}
174219
return parameters
175220

221+
@property
222+
def content(self):
223+
# Return empty JSON object as bytes (required by the API)
224+
return b'{}'
225+
176226
def on_200(self, session):
177227
data = self.deserialize_http_content(session)
178228
self.ctx.set_var(

src/edge-action/azext_edge_action/aaz/latest/edge_action/version/_swap_default.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,23 @@ def query_parameters(self):
157157
}
158158
return parameters
159159

160+
@property
161+
def header_parameters(self):
162+
parameters = {
163+
**self.serialize_header_param(
164+
"Content-Type", "application/json",
165+
),
166+
**self.serialize_header_param(
167+
"Accept", "application/json, text/plain, */*",
168+
),
169+
}
170+
return parameters
171+
172+
@property
173+
def content(self):
174+
# Return empty JSON object as bytes (required by the API)
175+
return b'{}'
176+
160177
def on_204(self, session):
161178
pass
162179

src/edge-action/azext_edge_action/tests/latest/recordings/test_edge_action_crud.yaml

Lines changed: 47 additions & 47 deletions
Large diffs are not rendered by default.

src/edge-action/azext_edge_action/tests/latest/recordings/test_edge_action_version_deploy_with_content.yaml

Lines changed: 146 additions & 96 deletions
Large diffs are not rendered by default.

src/edge-action/azext_edge_action/tests/latest/recordings/test_edge_action_version_deploy_with_file.yaml

Lines changed: 145 additions & 95 deletions
Large diffs are not rendered by default.

src/edge-action/azext_edge_action/tests/latest/recordings/test_edge_action_version_deploy_with_zip.yaml

Lines changed: 101 additions & 101 deletions
Large diffs are not rendered by default.

src/edge-action/azext_edge_action/tests/latest/recordings/test_edge_action_version_get_version_code.yaml

Lines changed: 211 additions & 107 deletions
Large diffs are not rendered by default.

src/edge-action/azext_edge_action/tests/latest/recordings/test_edge_action_version_operations.yaml

Lines changed: 47 additions & 47 deletions
Large diffs are not rendered by default.

src/edge-action/azext_edge_action/tests/latest/test_edge_action.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,56 @@ def test_edge_action_version_get_version_code(self, resource_group):
185185
self.cmd('edge-action version delete -g {} --edge-action-name {} -n {} -y'.format(
186186
resource_group, edge_action_name, version_name))
187187
self.cmd('edge-action delete -g {} -n {} -y'.format(resource_group, edge_action_name))
188+
189+
# NOTE: Execution filter CRUD test is temporarily disabled.
190+
# The EdgeAction service returns HTTP URLs (instead of HTTPS) in the Location header
191+
# for execution-filter operations, which causes the Azure SDK to fail with:
192+
# "Bearer token authentication is not permitted for non-TLS protected (non-https) URLs."
193+
# This is a service-side issue that needs to be fixed. Once the service returns HTTPS
194+
# URLs in the LRO headers, uncomment this test.
195+
#
196+
# @ResourceGroupPreparer(additional_tags={'owner': 'edgeaction'})
197+
# def test_edge_action_execution_filter_crud(self, resource_group):
198+
# """Test Edge Action Execution Filter CRUD operations"""
199+
# edge_action_name = self.create_random_name(prefix='edgeaction', length=20)
200+
# version_name = 'v1'
201+
# filter_name = self.create_random_name(prefix='filter', length=15)
202+
#
203+
# # Create edge action and version first
204+
# self.cmd('edge-action create -g {} -n {} --sku name=Standard tier=Standard --location global'.format(
205+
# resource_group, edge_action_name))
206+
# self.cmd('edge-action version create -g {} --edge-action-name {} -n {} --deployment-type file --location global --is-default-version False'.format(
207+
# resource_group, edge_action_name, version_name))
208+
#
209+
# # Test list execution filters (should be empty initially)
210+
# list_checks = [JMESPathCheck('length(@)', 0)]
211+
# self.cmd('edge-action execution-filter list -g {} --edge-action-name {}'.format(
212+
# resource_group, edge_action_name), checks=list_checks)
213+
#
214+
# # Test create execution filter with all required parameters
215+
# create_checks = [
216+
# JMESPathCheck('name', filter_name)
217+
# ]
218+
# self.cmd('edge-action execution-filter create -g {} --edge-action-name {} -n {} --location global --version-id {} --execution-filter-identifier-header-name "X-Filter-ID" --execution-filter-identifier-header-value "test-value"'.format(
219+
# resource_group, edge_action_name, filter_name, version_name), checks=create_checks)
220+
#
221+
# # Test show execution filter
222+
# show_checks = [
223+
# JMESPathCheck('name', filter_name)
224+
# ]
225+
# self.cmd('edge-action execution-filter show -g {} --edge-action-name {} -n {}'.format(
226+
# resource_group, edge_action_name, filter_name), checks=show_checks)
227+
#
228+
# # Test update execution filter
229+
# self.cmd('edge-action execution-filter update -g {} --edge-action-name {} -n {} --tags test=value'.format(
230+
# resource_group, edge_action_name, filter_name))
231+
#
232+
# # Test delete execution filter
233+
# self.cmd('edge-action execution-filter delete -g {} --edge-action-name {} -n {} -y'.format(
234+
# resource_group, edge_action_name, filter_name))
235+
#
236+
# # Clean up
237+
# self.cmd('edge-action version delete -g {} --edge-action-name {} -n {} -y'.format(
238+
# resource_group, edge_action_name, version_name))
239+
# self.cmd('edge-action delete -g {} -n {} -y'.format(resource_group, edge_action_name))
240+

0 commit comments

Comments
 (0)