Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat(lib): add move workflows for specifying ids directly #3231

Merged
merged 11 commits into from
Nov 27, 2023
47 changes: 47 additions & 0 deletions examples/typescript/aws-move/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,9 +176,56 @@ export class ComplexIteratorMoveStack extends TerraformStack {
}
// MOVE INTO RESOURCE USING COMPLEX ITERATOR

export class MoveToIDStack extends TerraformStack {
constructor(scope: Construct, id: string) {
super(scope, id);

new AwsProvider(this, "aws", {
region: "us-west-2",
});
if (process.env.STEP_2) {
new S3Bucket(this, "test-bucket-move-by", {
bucket: "test-move-bucket-move-to-id",
}).moveToId("aws_s3_bucket.test-bucket-1");

new S3Bucket(this, "test-bucket-1", {
bucket: "test-move-bucket-move-to-id",
});
}
if (process.env.STEP_1) {
new S3Bucket(this, "test-bucket-move-by", {
bucket: "test-move-bucket-move-to-id",
});
}
}
}

export class MoveFromIDStack extends TerraformStack {
constructor(scope: Construct, id: string) {
super(scope, id);

new AwsProvider(this, "aws", {
region: "us-west-2",
});
if (process.env.STEP_2) {
new S3Bucket(this, "test-bucket-1", {
bucket: "test-move-bucket-move-from-id",
}).moveFromId("aws_s3_bucket.test-bucket-move-by");
}
if (process.env.STEP_1) {
new S3Bucket(this, "test-bucket-move-by", {
bucket: "test-move-bucket-move-from-id",
});
}
}
}

const app = new App();
new MoveToIDStack(app, "move-to-id-stack");
new MoveFromIDStack(app, "move-from-id-stack");
new UnNestingMoveStack(app, "un-nesting-move-stack");
new NestingMoveStack(app, "nesting-move-stack");
new ListIteratorMoveStack(app, "list-iterator-move-stack");
new ComplexIteratorMoveStack(app, "complex-iterator-move-stack");

app.synth();
2 changes: 1 addition & 1 deletion packages/cdktf/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,6 @@ export * from "./terraform-conditions";
export * from "./terraform-count";
export * from "./importable-resource";
export * from "./terraform-resource-targets";

export * from "./upgrade-id-aspect";
// required for JSII because Fn extends from it
export * from "./functions/terraform-functions.generated";
10 changes: 10 additions & 0 deletions packages/cdktf/lib/synthesize/synthesizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { AnnotationMetadataEntryType, Annotations } from "../annotations";
import { ConstructOrder, IConstruct, MetadataEntry } from "constructs";
import { Aspects, IAspect } from "../aspect";
import { StackAnnotation } from "../manifest";
import { ValidateTerraformVersion } from "../validations/validate-terraform-version";

// eslint-disable-next-line jsdoc/require-jsdoc
export class StackSynthesizer implements IStackSynthesizer {
Expand All @@ -26,6 +27,15 @@ export class StackSynthesizer implements IStackSynthesizer {
synthesize(session: ISynthesisSession) {
invokeAspects(this.stack);

if (this.stack.hasResourceMove()) {
this.stack.node.addValidation(
new ValidateTerraformVersion(
">=1.5",
`Resource move functionality is only supported for Terraform >=1.5. Please upgrade your Terraform version.`
)
);
}

if (!session.skipValidation) {
this.stack.runAllValidations();
}
Expand Down
131 changes: 109 additions & 22 deletions packages/cdktf/lib/terraform-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,16 @@ export interface TerraformResourceConfig extends TerraformMetaArguments {
readonly terraformGeneratorMetadata?: TerraformProviderGeneratorMetadata;
}

export interface TerraformResourceMove {
export interface TerraformResourceMoveByTarget {
readonly moveTarget: string;
readonly index?: string | number;
}

export interface TerraformResourceMoveById {
readonly to: string;
readonly from: string;
}

export interface TerraformResourceImport {
readonly id: string;
readonly provider?: TerraformProvider;
Expand All @@ -103,7 +108,9 @@ export class TerraformResource
FileProvisioner | LocalExecProvisioner | RemoteExecProvisioner
>;
private _imported?: TerraformResourceImport;
private _moved?: TerraformResourceMove;
private _movedByTarget?: TerraformResourceMoveByTarget;
Maed223 marked this conversation as resolved.
Show resolved Hide resolved
private _movedById?: TerraformResourceMoveById;
private _hasMoved = false;

constructor(scope: Construct, id: string, config: TerraformResourceConfig) {
super(scope, id, config.terraformResourceType);
Expand All @@ -130,6 +137,10 @@ export class TerraformResource
);
}

public hasResourceMove() {
return this._movedById || this._movedByTarget;
}

public getStringAttribute(terraformAttribute: string) {
return Token.asString(this.interpolationForAttribute(terraformAttribute));
}
Expand Down Expand Up @@ -215,9 +226,10 @@ export class TerraformResource
...(attributes["//"] ?? {}),
...this.constructNodeMetadata,
};

const movedBlock = this._buildMovedBlock();
return {
resource: movedBlock
resource: this._hasMoved
? undefined
: {
[this.terraformResourceType]: {
Expand Down Expand Up @@ -256,11 +268,12 @@ export class TerraformResource
[this.terraformResourceType]: [this.friendlyUniqueId],
}
: undefined,
moved: this._moved
? {
[this.terraformResourceType]: [this.friendlyUniqueId],
}
: undefined,
moved:
this._movedByTarget || this._movedById
? {
[this.terraformResourceType]: [this.friendlyUniqueId],
}
: undefined,
};
}

Expand Down Expand Up @@ -294,11 +307,10 @@ export class TerraformResource
);
}

private _buildMovedBlock() {
if (!this._moved) {
return undefined;
}
const { moveTarget, index } = this._moved;
private _buildMovedBlockByTarget(
movedTarget: TerraformResourceMoveByTarget
): { to: string; from: string } {
const { moveTarget, index } = movedTarget;
const resourceToMoveTo = this._getResourceTarget(moveTarget);
if (this.terraformResourceType !== resourceToMoveTo.terraformResourceType) {
throw new Error(
Expand All @@ -317,27 +329,47 @@ export class TerraformResource
return { to, from };
}

private _buildMovedBlock(): { to: string; from: string } | undefined {
if (this._movedByTarget && this._movedById) {
throw new Error(`
${this.node.id} has been given two separate moved operations.

Move target: "${this._movedByTarget.moveTarget}"
Move by id: {
from: ${this._movedById.from}
to: ${this._movedById.to}
}

Only one move operation can occur per plan/apply. Remove one of the operations.
`);
} else if (this._movedByTarget) {
const movedBlockByTarget = this._buildMovedBlockByTarget(
this._movedByTarget
);
return { to: movedBlockByTarget.to, from: movedBlockByTarget.from };
} else if (this._movedById) {
return { to: this._movedById.to, from: this._movedById.from };
} else {
return undefined;
}
}

/**
* Moves this resource to the target resource given by moveTarget.
* @param moveTarget The previously set user defined string set by .addMoveTarget() corresponding to the resource to move to.
* @param index Optional The index corresponding to the key the resource is to appear in the foreach of a resource to move to
*/
public moveTo(moveTarget: string, index?: string | number) {
if (this._moved) {
if (this._movedByTarget) {
throw new Error(
`The resource ${this.friendlyUniqueId} has been given two moveTargets: "${this._moved.moveTarget}" and "${moveTarget}"
`The resource ${this.friendlyUniqueId} has been given two moveTargets: "${this._movedByTarget.moveTarget}" and "${moveTarget}"

A resource can only be moved once per plan/apply
`
);
}
this._moved = { moveTarget, index };
this.node.addValidation(
new ValidateTerraformVersion(
">=1.5",
`Resource move functionality is only supported for Terraform >=1.5. Please upgrade your Terraform version.`
)
);
this._movedByTarget = { moveTarget, index };
this._hasMoved = true;
}

/**
Expand All @@ -347,4 +379,59 @@ export class TerraformResource
public addMoveTarget(moveTarget: string) {
this._addResourceTarget(moveTarget);
}

/**
* Moves this resource to the resource corresponding to "id"
* @param id Full id of resource to move to, e.g. "aws_s3_bucket.example"
*/
public moveToId(id: string) {
if (this._movedById) {
throw new Error(`
${this.node.id} has been given two separate moved operations.

{
from: ${this._movedById.from}
to: ${this._movedById.to}
}
{
from: ${id}
to: ${this.terraformResourceType}.${this.friendlyUniqueId} (Resource calling the move to operation)
}

Only one move operation can occur per plan/apply. Remove one of the operations.
`);
}
this._movedById = {
to: id,
from: `${this.terraformResourceType}.${this.friendlyUniqueId}`,
};
this._hasMoved = true;
}

/**
* Move the resource corresponding to "id" to this resource. Note that the resource being moved from must be marked as moved using it's instance function.
* @param id Full id of resource being moved from, e.g. "aws_s3_bucket.example"
*/
public moveFromId(id: string) {
if (this._movedById) {
throw new Error(`
${this.node.id} has been given two separate moved operations.

{
from: ${this._movedById.from}
to: ${this._movedById.to}
}
{
from: ${this.terraformResourceType}.${this.friendlyUniqueId} (Resource calling the move from operation)
to: ${id}
}

Only one move operation can occur plan/apply. Remove one of the operations.
`);
}
this._movedById = {
to: `${this.terraformResourceType}.${this.friendlyUniqueId}`,
from: id,
};
}
}
10 changes: 10 additions & 0 deletions packages/cdktf/lib/terraform-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { ValidateProviderPresence } from "./validations";
import { App } from "./app";
import { TerraformBackend } from "./terraform-backend";
import { TerraformResourceTargets } from "./terraform-resource-targets";
import { TerraformResource } from "./terraform-resource";

// eslint-disable-next-line @typescript-eslint/ban-types
type StackIdentifier = string;
Expand Down Expand Up @@ -328,6 +329,15 @@ export class TerraformStack extends Construct {
);
}
}

public hasResourceMove(): boolean {
return terraformElements(this).some((e) => {
if (TerraformResource.isTerraformResource(e) && e.hasResourceMove()) {
return true;
}
return false;
});
}
}

// eslint-disable-next-line jsdoc/require-jsdoc
Expand Down
Loading
Loading