Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

fix(pipelines): "Node with duplicate id" on duplicate stack names #31328

Merged
merged 2 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/aws-cdk-lib/pipelines/lib/helpers-internal/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ export class Graph<A> extends GraphNode<A> {
return this.children.get(name);
}

public containsId(id: string) {
return this.tryGetChild(id) !== undefined;
}

public contains(node: GraphNode<A>) {
return this.nodes.has(node);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,12 @@ export class PipelineGraph {
const stackGraphs = new Map<StackDeployment, AGraph>();

for (const stack of stage.stacks) {
const stackGraph: AGraph = Graph.of(this.simpleStackName(stack.stackName, stage.stageName), { type: 'stack-group', stack });
const stackGraphName = findUniqueName(retGraph, [
this.simpleStackName(stack.stackName, stage.stageName),
...stack.account ? [stack.account] : [],
...stack.region ? [stack.region] : [],
]);
GavinZZ marked this conversation as resolved.
Show resolved Hide resolved
const stackGraph: AGraph = Graph.of(stackGraphName, { type: 'stack-group', stack });
const prepareNode: AGraphNode | undefined = this.prepareStep ? aGraphNode('Prepare', { type: 'prepare', stack }) : undefined;
const deployNode: AGraphNode = aGraphNode('Deploy', {
type: 'execute',
Expand Down Expand Up @@ -412,4 +417,14 @@ function aGraphNode(id: string, x: GraphAnnotation): AGraphNode {

function stripPrefix(s: string, prefix: string) {
return s.startsWith(prefix) ? s.slice(prefix.length) : s;
}

function findUniqueName(parent: Graph<any>, parts: string[]): string {
for (let i = 1; i <= parts.length; i++) {
const candidate = parts.slice(0, i).join('.');
if (!parent.containsId(candidate)) {
return candidate;
}
}
return parts.join('.');
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,33 @@ test('overridden stack names are respected', () => {
});
});

test('two stacks can have the same name', () => {
const pipeline = new ModernTestGitHubNpmPipeline(pipelineStack, 'Cdk', { useChangeSets: false });
pipeline.addStage(new TwoStacksApp(app, 'App'));

Template.fromStack(pipelineStack).hasResourceProperties('AWS::CodePipeline::Pipeline', {
Stages: Match.arrayWith([
{
Name: 'App',
Actions: Match.arrayWith([
Match.objectLike({
Name: stringLike('MyFancyStack.Deploy'),
Configuration: Match.objectLike({
StackName: 'MyFancyStack',
}),
}),
Match.objectLike({
Name: stringLike('MyFancyStack.eu-west-2.Deploy'),
Configuration: Match.objectLike({
StackName: 'MyFancyStack',
}),
}),
]),
},
]),
});
});

test('changing CLI version leads to a different pipeline structure (restarting it)', () => {

// GIVEN
Expand Down Expand Up @@ -154,3 +181,17 @@ class OneStackAppWithCustomName extends Stage {
});
}
}

class TwoStacksApp extends Stage {
constructor(scope: Construct, id: string, props?: StageProps) {
super(scope, id, props);
new BucketStack(this, 'Stack1', {
env: { region: 'eu-west-1' },
stackName: 'MyFancyStack',
});
new BucketStack(this, 'Stack2', {
env: { region: 'eu-west-2' },
stackName: 'MyFancyStack',
});
}
}