Repository: awslabs/cdk8s
Pull request #396 (2020-11-19 13:37:40)

feat(lib): allow hash to be optionally included in Names functions.
This makes it easier for users that want to use custom names without hashes.

Note: it would also be helpful if the functions `toHumanForm`, `normalizeToDnsName`, and `normalizeToLabelValue` were exposed.  This would let them be reused if users want completely customized names.

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*


eladb: Not sure I fully understand the use case. Without hashes the auto-generated names can conflict quite easily. If you want to control the name of a resource, you can just assign it directly through `metadata.name`. Isn't that sufficient?
abierbaum: @eladb I could be missing something.  In our usecase we don't run into naming conflicts, so having the stable names was more attractive to us.  We create deployments, stateful sets, services, etc.  Then the sub-resources allocated by those end up with the normal k8s unique names.  But we want to have stable names for the top level resources.

Originally I had simply copied your code into another file and override the `generateObjectName` method.  This works, but I was trying to find a way to not have to copy all of the cdk8s name generation code to simply remove the hash.  I am fine with doing that though if this isn't something the project wants to expose.
mergify[bot]: Your pull request will be updated and merged automatically (do not update manually).

Patch:
diff --git a/packages/cdk8s/API.md b/packages/cdk8s/API.md
index c52b51385..ac283ec0d 100644
--- a/packages/cdk8s/API.md
+++ b/packages/cdk8s/API.md
@@ -983,6 +983,7 @@ static toDnsLabel(scope: Construct, options?: NameOptions): string
 * **options** (<code>[NameOptions](#cdk8s-nameoptions)</code>)  Name options.
   * **delimiter** (<code>string</code>)  Delimiter to use between components. __*Default*__: "-"
   * **extra** (<code>Array<string></code>)  Extra components to include in the name. __*Default*__: [] use the construct path components
+  * **includeHash** (<code>boolean</code>)  Include a short hash as last part of the name. __*Default*__: true
   * **maxLen** (<code>number</code>)  Maximum allowed length for the name. __*Default*__: 63
 
 __Returns__:
@@ -1017,6 +1018,7 @@ static toLabelValue(scope: Construct, options?: NameOptions): string
 * **options** (<code>[NameOptions](#cdk8s-nameoptions)</code>)  Name options.
   * **delimiter** (<code>string</code>)  Delimiter to use between components. __*Default*__: "-"
   * **extra** (<code>Array<string></code>)  Extra components to include in the name. __*Default*__: [] use the construct path components
+  * **includeHash** (<code>boolean</code>)  Include a short hash as last part of the name. __*Default*__: true
   * **maxLen** (<code>number</code>)  Maximum allowed length for the name. __*Default*__: 63
 
 __Returns__:
@@ -1403,6 +1405,7 @@ Name | Type | Description
 -----|------|-------------
 **delimiter**?🔹 | <code>string</code> | Delimiter to use between components.<br/>__*Default*__: "-"
 **extra**?🔹 | <code>Array<string></code> | Extra components to include in the name.<br/>__*Default*__: [] use the construct path components
+**includeHash**?🔹 | <code>boolean</code> | Include a short hash as last part of the name.<br/>__*Default*__: true
 **maxLen**?🔹 | <code>number</code> | Maximum allowed length for the name.<br/>__*Default*__: 63
 
 
diff --git a/packages/cdk8s/src/names.ts b/packages/cdk8s/src/names.ts
index 9da01e116..f23726b32 100644
--- a/packages/cdk8s/src/names.ts
+++ b/packages/cdk8s/src/names.ts
@@ -27,6 +27,12 @@ export interface NameOptions {
    * @default "-"
    */
   readonly delimiter?: string;
+
+  /**
+   * Include a short hash as last part of the name.
+   * @default true
+   */
+  readonly includeHash?: boolean;
 }
 
 /**
@@ -62,8 +68,9 @@ export class Names {
   public static toDnsLabel(scope: Construct, options: NameOptions = { }) {
     const maxLen = options.maxLen ?? MAX_LEN;
     const delim = options.delimiter ?? '-';
+    const include_hash = options.includeHash ?? true;
 
-    if (maxLen < HASH_LEN) {
+    if (maxLen < HASH_LEN && include_hash) {
       throw new Error(`minimum max length for object names is ${HASH_LEN} (required for hash)`);
     }
 
@@ -79,7 +86,9 @@ export class Names {
 
     // okay, now we need to normalize all components to adhere to DNS_NAME and append the hash of the full path.
     components = components.map(c => normalizeToDnsName(c, maxLen));
-    components.push(calcHash(node, HASH_LEN));
+    if (include_hash) {
+      components.push(calcHash(node, HASH_LEN));
+    }
 
     return toHumanForm(components, delim, maxLen);
   }
@@ -115,8 +124,9 @@ export class Names {
   public static toLabelValue(scope: Construct, options: NameOptions = {}) {
     const maxLen = options.maxLen ?? MAX_LEN;
     const delim = options.delimiter ?? '-';
+    const include_hash = options.includeHash ?? true;
 
-    if (maxLen < HASH_LEN) {
+    if (maxLen < HASH_LEN && include_hash) {
       throw new Error(`minimum max length for label is ${HASH_LEN} (required for hash)`);
     }
 
@@ -135,7 +145,9 @@ export class Names {
 
     // okay, now we need to normalize all components to adhere to label and append the hash of the full path.
     components = components.map(c => normalizeToLabelValue(c, maxLen));
-    components.push(calcHash(node, HASH_LEN));
+    if (include_hash) {
+      components.push(calcHash(node, HASH_LEN));
+    }
 
     const result = toHumanForm(components, delim, maxLen);
 


Test patch:
diff --git a/packages/cdk8s/test/names.test.ts b/packages/cdk8s/test/names.test.ts
index ba45fc953..b75709e7c 100644
--- a/packages/cdk8s/test/names.test.ts
+++ b/packages/cdk8s/test/names.test.ts
@@ -73,6 +73,11 @@ describe('toDnsLabel', () => {
     expect(toDnsName('hello-world-this-is-cool')).toEqual('hello-world-this-is-cool');
     expect(toDnsName('hello/world-this/is-cool')).toEqual('hello-world-this-is-cool-c81c7478');
   });
+
+  test('optional hash', () => {
+    expect(toDnsName('hello/default/foo/world/resource', { includeHash: false })).toEqual('hello-foo-world');
+    expect(toDnsName('hello/world/this/is/cool', { includeHash: false, maxLen: 8 })).toEqual('is-cool');
+  });
 });
 
 describe('toLabel', () => {
@@ -144,4 +149,9 @@ describe('toLabel', () => {
     expect(toLabelValue('hello---this/is/cool/-')).toEqual('hello-this-is-cool-c83b900b');
     expect(toLabelValue('hello---this/is---/cool/-')).toEqual('hello-this-is-cool-c82d69dd');
   });
+
+  test('optional hash', () => {
+    expect(toLabelValue('hello/default/foo/world/resource', { includeHash: false })).toEqual('hello-foo-world');
+    expect(toLabelValue('hello/world/this/is/cool', { includeHash: false, maxLen: 8 })).toEqual('is-cool');
+  });
 });
\ No newline at end of file