Skip to content
This repository was archived by the owner on Dec 9, 2024. It is now read-only.

Commit 374925e

Browse files
authored
fix: Default operation names no longer cause collisions (#332)
APIM uses the operation name as the key when upserting operations into an API. By default we were using the function name as the operation name. During default configuration inference with multiple HTTP methods this caused a collision resulting in the last operation in always one. This fix generates a unique operation name taking into account the function name + the HTTP method.
1 parent c505c2d commit 374925e

File tree

5 files changed

+32
-40
lines changed

5 files changed

+32
-40
lines changed

src/armTemplates/compositeArmTemplate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ArmResourceTemplate, ArmResourceTemplateGenerator, ArmParameters, ArmParamType } from "../models/armTemplates";
1+
import { ArmResourceTemplate, ArmResourceTemplateGenerator, ArmParameters } from "../models/armTemplates";
22
import { ServerlessAzureConfig } from "../models/serverless";
33
import { AzureNamingService } from "../services/namingService";
44
import { Guard } from "../shared/guard";

src/plugins/invoke/azureInvokePlugin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import fs from "fs";
2-
import { isAbsolute, join } from "path";
2+
import { isAbsolute } from "path";
33
import Serverless from "serverless";
44
import { InvokeService } from "../../services/invokeService";
55
import { AzureBasePlugin } from "../azureBasePlugin";

src/services/apimService.test.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,11 @@ describe("APIM Service", () => {
387387

388388
it("infers APIM operation configuration from HTTP binding", async () => {
389389
const functions = MockFactory.createTestSlsFunctionConfig();
390+
391+
// Remove APIM operation configuration
392+
delete functions.hello.apim;
393+
delete functions.goodbye.apim;
394+
390395
Object.assign(serverless.service, { functions });
391396

392397
let apimResource: ApiManagementServiceResource = {
@@ -425,11 +430,12 @@ describe("APIM Service", () => {
425430
resourceGroupName,
426431
serviceName,
427432
apiName,
428-
"hello",
433+
"GET-hello",
429434
{
430-
displayName: "hello",
435+
name: "GET-hello",
436+
displayName: "hello (GET)",
431437
description: "",
432-
method: "get",
438+
method: "GET",
433439
urlTemplate: "hello",
434440
templateParameters: [],
435441
responses: [],
@@ -439,11 +445,12 @@ describe("APIM Service", () => {
439445
resourceGroupName,
440446
serviceName,
441447
apiName,
442-
"goodbye",
448+
"GET-goodbye",
443449
{
444-
displayName: "goodbye",
450+
name: "GET-goodbye",
451+
displayName: "goodbye (GET)",
445452
description: "",
446-
method: "get",
453+
method: "GET",
447454
urlTemplate: "goodbye",
448455
templateParameters: [],
449456
responses: [],

src/services/apimService.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export class ApimService extends BaseService {
116116
return null;
117117
}
118118

119-
this.log("-> Deploying API Operations");
119+
this.log(`-> Deploying API Operations: ${this.apimConfig.name}`);
120120

121121
const deployApiTasks = this.serverless.service
122122
.getAllFunctions()
@@ -144,12 +144,17 @@ export class ApimService extends BaseService {
144144

145145
const httpConfig = httpEvent["x-azure-settings"];
146146

147+
// Set to GET method by default
148+
if (!httpConfig.methods) {
149+
httpConfig.methods = ["GET"];
150+
}
151+
147152
// Infer APIM operation configuration from HTTP event if not already set
148153
if (!functionConfig.apim) {
149154
const operations = httpConfig.methods.map((method) => {
150155
return {
151-
name: functionConfig.name,
152-
displayName: functionConfig.name,
156+
name: `${method}-${functionConfig.name}`,
157+
displayName: `${functionConfig.name} (${method})`,
153158
urlTemplate: httpConfig.route || functionConfig.name,
154159
method: method,
155160
templateParameters: this.getTemplateParameters(httpConfig.route)
@@ -214,7 +219,7 @@ export class ApimService extends BaseService {
214219
});
215220

216221
if (this.apimConfig.cors) {
217-
this.log("-> Deploying CORS policy");
222+
this.log(`-> Deploying CORS policy: ${apiContract.name}`);
218223

219224
await this.apimClient.apiPolicy.createOrUpdate(this.resourceGroup, this.apimConfig.name, apiContract.name, {
220225
format: "rawxml",
@@ -273,6 +278,7 @@ export class ApimService extends BaseService {
273278
const client = new ApiManagementClient(this.credentials, this.subscriptionId);
274279

275280
const operationConfig: OperationContract = {
281+
name: operation.name || functionName,
276282
displayName: operation.displayName || functionName,
277283
description: operation.description || "",
278284
method: operation.method,
@@ -284,17 +290,17 @@ export class ApimService extends BaseService {
284290
// Ensure a single path seperator in the operation path
285291
const operationPath = `/${api.path}/${operationConfig.urlTemplate}`.replace(/\/+/g, "/");
286292
const operationUrl = `${resource.gatewayUrl}${operationPath}`;
287-
this.log(`--> ${functionName}: [${operationConfig.method.toUpperCase()}] ${operationUrl}`);
293+
this.log(`--> ${operationConfig.name}: [${operationConfig.method.toUpperCase()}] ${operationUrl}`);
288294

289295
const result = await client.apiOperation.createOrUpdate(
290296
this.resourceGroup,
291297
this.apimConfig.name,
292298
api.name,
293-
functionName,
299+
operationConfig.name,
294300
operationConfig,
295301
);
296302

297-
await client.apiOperationPolicy.createOrUpdate(this.resourceGroup, this.apimConfig.name, api.name, functionName, {
303+
await client.apiOperationPolicy.createOrUpdate(this.resourceGroup, this.apimConfig.name, api.name, operationConfig.name, {
298304
format: "rawxml",
299305
value: this.createApiOperationXmlPolicy(backend.name),
300306
});

src/services/armService.test.ts

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ describe("Arm Service", () => {
363363
await expect(service.deployTemplate(deployment))
364364
.rejects
365365
.toThrowError(
366-
new RegExp(`.*${errorPattern}.*`,"s")
366+
new RegExp(`.*${errorPattern}.*`, "s")
367367
);
368368
});
369369

@@ -401,39 +401,18 @@ describe("Arm Service", () => {
401401
expect(call[1]).toMatch(expectedDeploymentNameRegex);
402402
expect(call[2]).toEqual(expectedDeployment);
403403
});
404-
404+
405405
it("Throws original error when there has not been a previous deployment", async () => {
406406
const originalError = new Error("original error message");
407407
Deployments.prototype.createOrUpdate = jest.fn(() => Promise.reject(originalError));
408-
const previousDeploymentError: DeploymentExtendedError = {
409-
code: "DeploymentFailed",
410-
message: "At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/arm-debug for usage details.",
411-
details: [
412-
{
413-
code: "ServiceAlreadyExists",
414-
message: "Api service already exists: abc-123-apim"
415-
},
416-
{
417-
code: "StorageAccountAlreadyTaken",
418-
message: "The storage account named ABC123 is already taken."
419-
}
420-
]
421-
}
422408
ResourceService.prototype.getPreviousDeployment = jest.fn(() => Promise.resolve(undefined)) as any;
409+
423410
const deployment: ArmDeployment = {
424411
parameters: MockFactory.createTestParameters(),
425412
template: MockFactory.createTestArmTemplate()
426413
};
427414
deployment.parameters.param1.value = "3"
428-
const { code, message, details } = previousDeploymentError;
429-
let errorPattern = [
430-
code,
431-
message,
432-
details[0].code,
433-
details[0].message,
434-
details[1].code,
435-
details[1].message
436-
].join(".*")
415+
437416
await expect(service.deployTemplate(deployment))
438417
.rejects
439418
.toThrowError(originalError);

0 commit comments

Comments
 (0)