forked from aws/aws-cdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(pipelines): can't use exports from very long stack names (aws#18039)
The variable namespace identifier in CodePipeline allows a maximum of 100 characters. If we ever come up with an identifier that would be too long, trim it down. While writing tests for this, discovered that actions exhibited the same length issues, and did the same there too. Fixes aws#17436. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
- Loading branch information
Showing
6 changed files
with
130 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import * as crypto from 'crypto'; | ||
import { StackDeployment } from '../blueprint/stack-deployment'; | ||
import { GraphNode } from '../helpers-internal/graph'; | ||
|
||
export function hash<A>(obj: A) { | ||
const d = crypto.createHash('sha256'); | ||
d.update(JSON.stringify(obj)); | ||
return d.digest('hex'); | ||
} | ||
|
||
export function actionName<A>(node: GraphNode<A>, parent: GraphNode<A>) { | ||
const names = node.ancestorPath(parent).map(n => n.id).map(sanitizeName); | ||
|
||
// Something slightly complicated here: | ||
// | ||
// Because we'll have structured action names like: | ||
// | ||
// 'VeryLongStackName.Prepare', 'VeryLongStackName.Deploy' | ||
// | ||
// it would be shitty if cut and hashed them differently: | ||
// | ||
// 'VeryLongSAAAAA.Prepare', 'VeryLonBBBBBme.Deploy' | ||
// | ||
// wouldn't sort and comprehend nicely. We will therefore trim each component individually. | ||
const totalMax = 100; // Max length of everything | ||
|
||
// No need to do anything | ||
if (names.join('.').length <= totalMax) { | ||
return names.join('.'); | ||
} | ||
|
||
const componentMin = 15; // Can't really go shorter than this, becomes unreadable | ||
const dots = names.length - 1; | ||
const maxLength = Math.max(componentMin, Math.floor((totalMax - dots) / names.length)); | ||
const trimmedNames = names.map(name => limitIdentifierLength(name, maxLength)); | ||
|
||
return limitIdentifierLength(trimmedNames.join('.'), totalMax); // Final trim in case we couldn't make it | ||
} | ||
|
||
export function stackVariableNamespace(stack: StackDeployment) { | ||
return limitIdentifierLength(stack.stackArtifactId, 100); | ||
} | ||
|
||
function sanitizeName(x: string): string { | ||
return x.replace(/[^A-Za-z0-9.@\-_]/g, '_'); | ||
} | ||
|
||
|
||
/** | ||
* Makes sure the given identifier length does not exceed N characters | ||
* | ||
* Replaces characters in the middle (to leave the start and end identifiable) and replaces | ||
* them with a hash to prevent collissions. | ||
*/ | ||
export function limitIdentifierLength(s: string, n: number): string { | ||
if (s.length <= n) { return s; } | ||
const h = hash(s).substr(0, 8); | ||
const mid = Math.floor((n - h.length) / 2); | ||
|
||
return s.substr(0, mid) + h + s.substr(s.length - mid); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
packages/@aws-cdk/pipelines/test/private/identifiers.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import { Graph } from '../../lib/helpers-internal'; | ||
import { actionName } from '../../lib/private/identifiers'; | ||
import { mkGraph } from '../blueprint/helpers-internal/util'; | ||
|
||
test('actionName trims subcomponents the same way', () => { | ||
const long1 = 'ExtremelyLong'.repeat(10); | ||
const long2 = 'AlsoLong'.repeat(10); | ||
|
||
const g = mkGraph('MyGraph', G => { | ||
G.graph(long1, [], G1 => { | ||
G1.graph(long2, [], G2 => { | ||
G2.node('Prepare'); | ||
G2.node('Deploy'); | ||
}); | ||
}); | ||
}); | ||
|
||
const G2 = ((g.tryGetChild(long1) as Graph<any>)?.tryGetChild(long2) as Graph<any>); | ||
expect(G2).toBeDefined(); | ||
|
||
const prep = G2.tryGetChild('Prepare'); | ||
const deploy = G2.tryGetChild('Deploy'); | ||
|
||
expect(prep).toBeDefined(); | ||
expect(deploy).toBeDefined(); | ||
|
||
// ActionNames have the same prefix | ||
const prepParts = actionName(prep!, g).split('.'); | ||
const deployParts = actionName(deploy!, g).split('.'); | ||
|
||
// Final parts are unchanged | ||
expect(prepParts.pop()).toEqual('Prepare'); | ||
expect(deployParts.pop()).toEqual('Deploy'); | ||
// Prefixes are the same | ||
expect(prepParts).toEqual(deployParts); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters