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

Commit 44185c5

Browse files
authored
fix: Append 6-char resource group hash to storage account name (#256)
Because storage account names are global, the need to be unique across resource groups. This adds the first 6 characters of the `md5` resource group hash to the end of the storage account name. Resolves AB#846
1 parent 660f8b7 commit 44185c5

File tree

8 files changed

+88
-18
lines changed

8 files changed

+88
-18
lines changed

package-lock.json

Lines changed: 41 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"js-yaml": "^3.13.1",
5252
"jsonpath": "^1.0.1",
5353
"lodash": "^4.16.6",
54+
"md5": "^2.2.1",
5455
"open": "^6.3.0",
5556
"request": "^2.81.0",
5657
"rimraf": "^2.6.3",

src/armTemplates/resources/storageAccount.test.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import { ServerlessAzureConfig } from "../../models/serverless";
22
import { AzureNamingService } from "../../services/namingService";
33
import { StorageAccountResource } from "./storageAccount";
4+
import md5 from "md5";
45

56
describe("Storage Account Resource", () => {
7+
const resourceGroup = "myResourceGroup";
8+
const resourceGroupHash = md5(resourceGroup).substr(0, 6);
9+
610
const config: ServerlessAzureConfig = {
711
functions: [],
812
plugins: [],
@@ -11,19 +15,24 @@ describe("Storage Account Resource", () => {
1115
name: "azure",
1216
region: "westus",
1317
stage: "dev",
18+
resourceGroup
1419
},
1520
service: "test-api"
1621
}
1722

1823
it("Generates safe storage account name with short parts", () => {
1924
const testConfig: ServerlessAzureConfig = {
2025
...config,
26+
provider: {
27+
...config.provider,
28+
resourceGroup
29+
},
2130
service: "test-api",
2231
};
2332

2433
const result = StorageAccountResource.getResourceName(testConfig);
2534
assertValidStorageAccountName(testConfig, result);
26-
expect(result.startsWith("slswusdev")).toBe(true);
35+
expect(result).toEqual(`slswusdev${resourceGroupHash}`);
2736
});
2837

2938
it("Generates safe storage account names with long parts", () => {
@@ -33,13 +42,14 @@ describe("Storage Account Resource", () => {
3342
...config.provider,
3443
prefix: "my-long-test-prefix-name",
3544
region: "Australia Southeast",
36-
stage: "development"
45+
stage: "development",
46+
resourceGroup
3747
}
3848
};
3949

4050
const result = StorageAccountResource.getResourceName(testConfig);
4151
assertValidStorageAccountName(testConfig, result);
42-
expect(result.startsWith("mylaussedev")).toBe(true);
52+
expect(result).toEqual(`mylaussedev${resourceGroupHash}`)
4353
});
4454

4555
it("Generating a storage account name is idempotent", () => {

src/armTemplates/resources/storageAccount.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ import configConstants from "../../config";
55

66
export class StorageAccountResource implements ArmResourceTemplateGenerator {
77
public static getResourceName(config: ServerlessAzureConfig) {
8-
return AzureNamingService.getSafeResourceName(config, configConstants.naming.maxLength.storageAccount, config.provider.storageAccount);
8+
return AzureNamingService.getSafeResourceName(
9+
config,
10+
configConstants.naming.maxLength.storageAccount,
11+
config.provider.storageAccount,
12+
"",
13+
true
14+
);
915
}
1016

1117
public getTemplate(): ArmResourceTemplate {

src/config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ export const configConstants = {
3232
scmCommandApiPath: "/api/command",
3333
scmDomain: ".scm.azurewebsites.net",
3434
scmVfsPath: "/api/vfs/site/wwwroot/",
35-
scmZipDeployApiPath: "/api/zipdeploy"
35+
scmZipDeployApiPath: "/api/zipdeploy",
36+
resourceGroupHashLength: 6,
3637
};
3738

3839
export default configConstants;

src/services/baseService.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export abstract class BaseService {
4242
this.credentials = serverless.variables["azureCredentials"];
4343
this.subscriptionId = serverless.variables["subscriptionId"];
4444
this.resourceGroup = this.getResourceGroupName();
45+
this.config.provider.resourceGroup = this.resourceGroup;
4546
this.deploymentConfig = this.getDeploymentConfig();
4647
this.deploymentName = this.getDeploymentName();
4748
this.artifactName = this.getArtifactName(this.deploymentName);

src/services/namingService.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ import { AzureNamingService } from "./namingService"
22
import { ServerlessAzureConfig } from "../models/serverless";
33

44
describe("Naming Service", () => {
5+
6+
const resourceGroup = "myResourceGroup";
7+
58
it("Creates a short name for an azure region", () => {
69
const expected = "ausse";
710
const actual = AzureNamingService.createShortAzureRegionName("australiasoutheast");
@@ -96,6 +99,7 @@ describe("Naming Service", () => {
9699
name: "azure",
97100
region: "westus",
98101
stage: "dev",
102+
resourceGroup,
99103
},
100104
service: "test-api"
101105
};
@@ -116,6 +120,7 @@ describe("Naming Service", () => {
116120
name: "azure",
117121
region: "westus",
118122
stage: "multicloud",
123+
resourceGroup,
119124
},
120125
service: "extra-long-service-name"
121126
};

src/services/namingService.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ServerlessAzureConfig, ResourceConfig } from "../models/serverless"
22
import { Guard } from "../shared/guard"
33
import configConstants from "../config";
4+
import md5 from "md5";
45

56
export class AzureNamingService {
67

@@ -45,25 +46,28 @@ export class AzureNamingService {
4546
* @param forbidden Regex for characters to remove from name. Defaults to non-alpha-numerics
4647
* @param replaceWith String to replace forbidden characters. Defaults to empty string
4748
*/
48-
public static getSafeResourceName(config: ServerlessAzureConfig, maxLength: number, resourceConfig?: ResourceConfig, suffix: string = "", forbidden: RegExp = /\W+/g, replaceWith: string = "") {
49+
public static getSafeResourceName(config: ServerlessAzureConfig, maxLength: number, resourceConfig?: ResourceConfig, suffix: string = "", includeHash = false) {
50+
const nonAlphaNumeric = /\W+/g;
51+
4952
if (resourceConfig && resourceConfig.name) {
5053
const { name } = resourceConfig;
5154

5255
if (name.length > maxLength) {
5356
throw new Error(`Name '${name}' invalid. Should be shorter than ${maxLength} characters`);
5457
}
5558

56-
return name.replace(forbidden, replaceWith);
59+
return name.replace(nonAlphaNumeric, "");
5760
}
5861

5962
const { prefix, region, stage } = config.provider;
6063

61-
let safePrefix = prefix.replace(forbidden, replaceWith);
64+
let safePrefix = prefix.replace(nonAlphaNumeric, "");
6265
const safeRegion = this.createShortAzureRegionName(region);
6366
let safeStage = this.createShortStageName(stage);
64-
let safeSuffix = suffix.replace(forbidden, replaceWith);
67+
let safeSuffix = suffix.replace(nonAlphaNumeric, "");
6568

66-
const remaining = maxLength - (safePrefix.length + safeRegion.length + safeStage.length + safeSuffix.length);
69+
const hashLength = (includeHash) ? configConstants.resourceGroupHashLength : 0;
70+
const remaining = maxLength - (safePrefix.length + safeRegion.length + safeStage.length + safeSuffix.length + hashLength);
6771

6872
// Dynamically adjust the substring based on space needed
6973
if (remaining < 0) {
@@ -77,9 +81,17 @@ export class AzureNamingService {
7781
safeSuffix = safeSuffix.substr(0, partLength);
7882
}
7983

80-
return [safePrefix, safeRegion, safeStage, safeSuffix]
84+
const safeHash = md5(config.provider.resourceGroup).substr(0, hashLength);
85+
86+
const name = [safePrefix, safeRegion, safeStage, safeHash, safeSuffix]
8187
.join("")
8288
.toLowerCase();
89+
90+
if (name.length > maxLength) {
91+
throw new Error(`Name ${name} is too long. Should be shorter than ${maxLength} characters`)
92+
}
93+
94+
return name;
8395
}
8496

8597
/**

0 commit comments

Comments
 (0)