Refactoring Pulumi Code

Ami Mahloof
4 min readOct 4, 2023

Pulumi code can become hard to read and maintain, making refactoring essential for improving reusability and readability.

Scenario:

Imagine the following scenario:

  • You have developed or given an EKS cluster module
  • A request arises to add Karpenter to the EKS cluster.
  • The code size jumps from less than 200 lines to about 500 lines.
  • Months later, there’s a need to move Karpenter to Fargate, requiring more code changes.

Refactoring Steps:

To make Pulumi reusable, we can use the pulumi.ComponentResource and move everything that relates to Karpenter to it.

  1. Use pulumi.ComponentResource to encapsulate everything related to Karpenter
  2. Create a new file named karpenter.ts to house the refactored code.
  3. Inside this new file, define a class that extends pulumi.ComponentResource. Prefix the class with the keywordexport to enable importing it from the original file.
  4. Pass all variables used in the original file as arguments by defining an interface of Args (e.g., KarpenterArgs) and set it as arguments to the constructor of this new class.

5. Within the constructor, invoke super to allow Pulumi to recognize the class as a component resource.

6. With the base class in place we can start moving the methods and resources from the original file into this new file.

7. Start moving the methods and resources from the original file into the new file.

  • For each resource added or moved, ensure that the parent is set to the new class by using { parent: this } in the opts argument.

use the {parent: this} in the opts arguments for the resource.

Handling Resource Changes:

When moving resources to a new file, you may encounter issues where Pulumi cannot find them in the resource list due to a change in the parent. To address this:

  • If you have Pulumi Cloud, navigate to the graph view under resources to identify the new parent, typically the root of the stack.
  • Use Aliases in the opts argument to inform Pulumi of the previous parent (e.g., pulumi.rootStackResource). This ensures that Pulumi can locate and manage the resource even during updates.
  • If you don’t use Pulumi Cloud, you can still find the parent for a resource by using the command pulumi stack export | jq . and looking for the resource URN.

Pulumi URN Format:

The Pulumi URN format comprises several components, such as stack name, project name, parent type, resource type, and resource name. This format allows for unique resource identification:

urn:pulumi:stackname::projectname::aws:resourceType:resourceName
urn:pulumi:production::acmecorp-website::custom:resources:Resource$aws:s3/bucket:Bucket::my-bucket
^^^^^^^^^^ ^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^
<stack-name> <project-name> <parent-type> <resource-type> <resource-name>

Example of Kubernetes Namespace resource:

namespace resource before using aliases

in this case, the root of the stack is the parent for the namespace.

We can use Aliases in the opts arg to tell pulumi who was the previous parent (pulumi.rootStackResource).

namespace resource after using aliases

Now pulumi will find and use that resource even for updates.\

sometimes it’s not enough to pass the previous parent in the alias, and you need to also pass the name

Full Alias Reference:

Instead of specifying URN you can use any of these attributes:

export interface Alias {
/**
* The previous name of the resource. If not provided, the current name of the resource is
* used.
*/
name?: Input<string>;
/**
* The previous type of the resource. If not provided, the current type of the resource is used.
*/
type?: Input<string>;
/**
* The previous parent of the resource. If not provided (i.e. `{ name: "foo" }`), the current
* parent of the resource is used (`opts.parent` if provided, else the implicit stack resource
* parent).
*
* To specify no original parent, use `{ parent: pulumi.rootStackResource }`.
*/
parent?: Resource | Input<URN>;
/**
* The previous stack of the resource. If not provided, defaults to `pulumi.getStack()`.
*/
stack?: Input<string>;
/**
* The previous project of the resource. If not provided, defaults to `pulumi.getProject()`.
*/
project?: Input<string>;
}

Imprortant!!!

Stack and Projects are scoped to the current project!

just like you would run pulumi stack ls and get the stacks for the current project you are on, pulumi can’t tell other stacks or projects, so it would not work if you try to specify the full URN of another stack and project, since the current project cannot see those stacks.

--

--