Skip to content

Commit

Permalink
Add instanceProfileName input property to nodegroups (#1496)
Browse files Browse the repository at this point in the history
Add `instanceProfileName` as an `Input<string>` field to `NodeGroup` and
`NodeGroupV2`. This unblocks
pulumi/pulumi-self-hosted-installers#181 by
creating a path for users to create an `InstanceProfile` in a separate
stack from the `NodeGroup` and pass the instance profile's name by stack
reference. Currently pulumi/pulumi#17515,
prevents `NodeGroup` components from accepting `InstanceProfile` values
created in another stack and provided to the `NodeGroup`'s stack by
`aws.iam.InstanceProfile.get()`, and the `instanceProfile` parameter
cannot accept an `InstanceProfile` resource passed by stack reference
because the `instanceProfile` parameter is a plain `InstanceProfile`
type, not an `Input<InstanceProfile>`.

Closes #1497

Co-authored-by: Jonathan Davenport <[email protected]>
  • Loading branch information
jdavredbeard and jdavredbeard authored Nov 25, 2024
1 parent a1061b6 commit 230eba5
Show file tree
Hide file tree
Showing 32 changed files with 717 additions and 61 deletions.
4 changes: 2 additions & 2 deletions nodejs/eks/cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -819,7 +819,7 @@ export function createCore(
name,
parent,
args.instanceRole,
args.instanceProfileName,
args.instanceProfileName ?? nodeGroupOptions.instanceProfileName,
);
}
instanceRoles = pulumi.output([args.instanceRole]);
Expand Down Expand Up @@ -871,7 +871,7 @@ export function createCore(
name,
parent,
instanceRole,
args.instanceProfileName,
args.instanceProfileName ?? nodeGroupOptions.instanceProfileName,
);
} else {
instanceRoles = pulumi.output([]);
Expand Down
170 changes: 168 additions & 2 deletions nodejs/eks/nodegroup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

import { isGravitonInstance } from "./nodegroup";
import { isGravitonInstance, resolveInstanceProfileName } from "./nodegroup";
import { getArchitecture } from "./nodegroup";
import { CoreData } from "./cluster";
import { Cluster as ClusterComponent, CoreData } from "./cluster";

const gravitonInstances = [
"c6g.12xlarge",
Expand Down Expand Up @@ -446,6 +446,172 @@ describe("createManagedNodeGroup", function () {
});
});

describe("resolveInstanceProfileName", function () {
beforeAll(() => {
pulumi.runtime.setMocks(
{
newResource: function (args: pulumi.runtime.MockResourceArgs): {
id: string;
state: any;
} {
if (args.type === "aws:iam/instanceProfile:InstanceProfile") {
return {
id: args.inputs.name + "_id",
state: {
name: args.id,
},
};
}
return {
id: args.inputs.name + "_id",
state: args.inputs,
};
},
call: function (args: pulumi.runtime.MockCallArgs) {
return args.inputs;
},
},
"project",
"stack",
false, // Sets the flag `dryRun`, which indicates if pulumi is running in preview mode.
);
});

let ng: typeof import("./nodegroup");
beforeEach(async function () {
ng = await import("./nodegroup");
});

test("no args, no c.nodeGroupOptions throws", async () => {
expect(() =>
resolveInstanceProfileName(
"nodegroup-name",
{},
{
nodeGroupOptions: {},
} as pulumi.UnwrappedObject<CoreData>,
undefined as any,
),
).toThrowError("an instanceProfile or instanceProfileName is required");
});

test("both args.instanceProfile and args.instanceProfileName throws", async () => {
const instanceProfile = new aws.iam.InstanceProfile("instanceProfile", {});
const name = "nodegroup-name";
expect(() =>
resolveInstanceProfileName(
name,
{
instanceProfile: instanceProfile,
instanceProfileName: "instanceProfileName",
},
{
nodeGroupOptions: {},
} as pulumi.UnwrappedObject<CoreData>,
undefined as any,
),
).toThrowError(
`invalid args for node group ${name}, instanceProfile and instanceProfileName are mutually exclusive`,
);
});

test("both c.nodeGroupOptions.instanceProfileName and c.nodeGroupOptions.instanceProfile throws", async () => {
const instanceProfile = new aws.iam.InstanceProfile("instanceProfile", {});
const name = "nodegroup-name";
expect(() =>
resolveInstanceProfileName(
name,
{},
{
nodeGroupOptions: {
instanceProfile: instanceProfile,
instanceProfileName: "instanceProfileName",
},
} as pulumi.UnwrappedObject<CoreData>,
undefined as any,
),
).toThrowError(
`invalid args for node group ${name}, instanceProfile and instanceProfileName are mutually exclusive`,
);
});

test("args.instanceProfile returns passed instanceProfile", async () => {
const instanceProfile = new aws.iam.InstanceProfile("instanceProfile", {
name: "passedInstanceProfile",
});
const nodeGroupName = "nodegroup-name";
const resolvedInstanceProfileName = resolveInstanceProfileName(
nodeGroupName,
{
instanceProfile: instanceProfile,
},
{
nodeGroupOptions: {},
} as pulumi.UnwrappedObject<CoreData>,
undefined as any,
);
const expected = await promisify(instanceProfile.name);
const recieved = await promisify(resolvedInstanceProfileName);
expect(recieved).toEqual(expected);
});

test("nodeGroupOptions.instanceProfile returns passed instanceProfile", async () => {
const instanceProfile = new aws.iam.InstanceProfile("instanceProfile", {
name: "passedInstanceProfile",
});
const nodeGroupName = "nodegroup-name";
const resolvedInstanceProfileName = resolveInstanceProfileName(
nodeGroupName,
{},
{
nodeGroupOptions: {
instanceProfile: instanceProfile,
},
} as pulumi.UnwrappedObject<CoreData>,
undefined as any,
);
const expected = await promisify(instanceProfile.name);
const recieved = await promisify(resolvedInstanceProfileName);
expect(recieved).toEqual(expected);
});

test("args.instanceProfileName returns passed InstanceProfile", async () => {
const nodeGroupName = "nodegroup-name";
const existingInstanceProfileName = "existingInstanceProfileName";
const resolvedInstanceProfileName = resolveInstanceProfileName(
nodeGroupName,
{
instanceProfileName: existingInstanceProfileName,
},
{
nodeGroupOptions: {},
} as pulumi.UnwrappedObject<CoreData>,
undefined as any,
);
const expected = existingInstanceProfileName;
const received = await promisify(resolvedInstanceProfileName);
expect(received).toEqual(expected);
});

test("nodeGroupOptions.instanceProfileName returns existing InstanceProfile", async () => {
const nodeGroupName = "nodegroup-name";
const existingInstanceProfileName = "existingInstanceProfileName";
const resolvedInstanceProfileName = resolveInstanceProfileName(
nodeGroupName,
{},
{
nodeGroupOptions: {
instanceProfileName: existingInstanceProfileName,
},
} as pulumi.UnwrappedObject<CoreData>,
undefined as any,
);
const expected = existingInstanceProfileName;
const received = await promisify(resolvedInstanceProfileName);
expect(received).toEqual(expected);
});
});

function promisify<T>(output: pulumi.Output<T> | undefined): Promise<T> {
expect(output).toBeDefined();
return new Promise((resolve) => output!.apply(resolve));
Expand Down
56 changes: 42 additions & 14 deletions nodejs/eks/nodegroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
SelfManagedV1NodeUserDataArgs,
SelfManagedV2NodeUserDataArgs,
} from "./userdata";
import { InstanceProfile } from "@pulumi/aws/iam";

export type TaintEffect = "NoSchedule" | "NoExecute" | "PreferNoSchedule";

Expand Down Expand Up @@ -277,6 +278,7 @@ export interface NodeGroupBaseOptions {
* must be supplied in the ClusterOptions as either: 'instanceRole', or as a role of 'instanceRoles'.
*/
instanceProfile?: aws.iam.InstanceProfile;
instanceProfileName?: pulumi.Input<string>;

/**
* The tags to apply to the NodeGroup's AutoScalingGroup in the
Expand Down Expand Up @@ -652,19 +654,48 @@ export function createNodeGroup(
return createNodeGroupInternal(name, args, pulumi.output(core), parent, provider);
}

export function resolveInstanceProfileName(
name: string,
args: Omit<NodeGroupOptions, "cluster">,
c: pulumi.UnwrappedObject<CoreData>,
parent: pulumi.ComponentResource,
): pulumi.Output<string> {
if (
(args.instanceProfile || c.nodeGroupOptions.instanceProfile) &&
(args.instanceProfileName || c.nodeGroupOptions.instanceProfileName)
) {
throw new pulumi.ResourceError(
`invalid args for node group ${name}, instanceProfile and instanceProfileName are mutually exclusive`,
parent,
);
}

if (args.instanceProfile) {
return args.instanceProfile.name;
} else if (args.instanceProfileName) {
return pulumi.output(args.instanceProfileName);
} else if (c.nodeGroupOptions.instanceProfile) {
return c.nodeGroupOptions.instanceProfile.name;
} else if (c.nodeGroupOptions.instanceProfileName) {
return pulumi.output(c.nodeGroupOptions.instanceProfileName!);
} else {
throw new pulumi.ResourceError(
`an instanceProfile or instanceProfileName is required`,
parent,
);
}
}

function createNodeGroupInternal(
name: string,
args: Omit<NodeGroupOptions, "cluster">,
core: pulumi.Output<pulumi.Unwrap<CoreData>>,
parent: pulumi.ComponentResource,
provider?: pulumi.ProviderResource,
): NodeGroupData {
const instanceProfile = core.apply((c) => {
if (!args.instanceProfile && !c.nodeGroupOptions.instanceProfile) {
throw new pulumi.ResourceError(`an instanceProfile is required`, parent);
}
return args.instanceProfile ?? c.nodeGroupOptions.instanceProfile!;
});
const instanceProfileName = core.apply((c) =>
resolveInstanceProfileName(name, args, c, parent),
);

if (args.clusterIngressRule && args.clusterIngressRuleId) {
throw new pulumi.ResourceError(
Expand Down Expand Up @@ -975,7 +1006,7 @@ function createNodeGroupInternal(
associatePublicIpAddress: nodeAssociatePublicIpAddress,
imageId: amiId,
instanceType: args.instanceType || "t3.medium",
iamInstanceProfile: instanceProfile,
iamInstanceProfile: instanceProfileName,
keyName: keyName,
// This apply is necessary in s.t. the launchConfiguration picks up a
// dependency on the eksClusterIngressRule. The nodes may fail to
Expand Down Expand Up @@ -1130,12 +1161,9 @@ function createNodeGroupV2Internal(
parent: pulumi.ComponentResource,
provider?: pulumi.ProviderResource,
): NodeGroupV2Data {
const instanceProfileArn = core.apply((c) => {
if (!args.instanceProfile && !c.nodeGroupOptions.instanceProfile) {
throw new pulumi.ResourceError(`an instanceProfile is required`, parent);
}
return args.instanceProfile?.arn ?? c.nodeGroupOptions.instanceProfile!.arn;
});
const instanceProfileName = core.apply((c) =>
resolveInstanceProfileName(name, args, c, parent),
);

if (args.clusterIngressRule && args.clusterIngressRuleId) {
throw new pulumi.ResourceError(
Expand Down Expand Up @@ -1458,7 +1486,7 @@ function createNodeGroupV2Internal(
{
imageId: amiId,
instanceType: args.instanceType || "t3.medium",
iamInstanceProfile: { arn: instanceProfileArn },
iamInstanceProfile: { name: instanceProfileName },
keyName: keyName,
instanceMarketOptions: marketOptions,
blockDeviceMappings,
Expand Down
2 changes: 1 addition & 1 deletion nodejs/eks/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5331,4 +5331,4 @@ yocto-queue@^0.1.0:
yocto-queue@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.1.1.tgz#fef65ce3ac9f8a32ceac5a634f74e17e5b232110"
integrity sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==
integrity sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==
6 changes: 5 additions & 1 deletion provider/cmd/pulumi-gen-eks/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2308,7 +2308,11 @@ func nodeGroupProperties(cluster, v2 bool) map[string]schema.PropertySpec {
Ref: awsRef("#/resources/aws:iam%2FinstanceProfile:InstanceProfile"),
Plain: true,
},
Description: "The ingress rule that gives node group access.",
Description: "The IAM InstanceProfile to use on the NodeGroup. Properties instanceProfile and instanceProfileName are mutually exclusive.",
},
"instanceProfileName": {
TypeSpec: schema.TypeSpec{Type: "string"},
Description: "The name of the IAM InstanceProfile to use on the NodeGroup. Properties instanceProfile and instanceProfileName are mutually exclusive.",
},
"autoScalingGroupTags": {
TypeSpec: schema.TypeSpec{
Expand Down
18 changes: 15 additions & 3 deletions provider/cmd/pulumi-resource-eks/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,11 @@
"instanceProfile": {
"$ref": "/aws/v6.18.2/schema.json#/resources/aws:iam%2FinstanceProfile:InstanceProfile",
"plain": true,
"description": "The ingress rule that gives node group access."
"description": "The IAM InstanceProfile to use on the NodeGroup. Properties instanceProfile and instanceProfileName are mutually exclusive."
},
"instanceProfileName": {
"type": "string",
"description": "The name of the IAM InstanceProfile to use on the NodeGroup. Properties instanceProfile and instanceProfileName are mutually exclusive."
},
"instanceType": {
"type": "string",
Expand Down Expand Up @@ -1778,7 +1782,11 @@
"instanceProfile": {
"$ref": "/aws/v6.18.2/schema.json#/resources/aws:iam%2FinstanceProfile:InstanceProfile",
"plain": true,
"description": "The ingress rule that gives node group access."
"description": "The IAM InstanceProfile to use on the NodeGroup. Properties instanceProfile and instanceProfileName are mutually exclusive."
},
"instanceProfileName": {
"type": "string",
"description": "The name of the IAM InstanceProfile to use on the NodeGroup. Properties instanceProfile and instanceProfileName are mutually exclusive."
},
"instanceType": {
"type": "string",
Expand Down Expand Up @@ -2051,7 +2059,11 @@
"instanceProfile": {
"$ref": "/aws/v6.18.2/schema.json#/resources/aws:iam%2FinstanceProfile:InstanceProfile",
"plain": true,
"description": "The ingress rule that gives node group access."
"description": "The IAM InstanceProfile to use on the NodeGroup. Properties instanceProfile and instanceProfileName are mutually exclusive."
},
"instanceProfileName": {
"type": "string",
"description": "The name of the IAM InstanceProfile to use on the NodeGroup. Properties instanceProfile and instanceProfileName are mutually exclusive."
},
"instanceType": {
"type": "string",
Expand Down
8 changes: 7 additions & 1 deletion sdk/dotnet/Inputs/ClusterNodeGroupOptionsArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,17 @@ public InputList<Pulumi.Aws.Ec2.SecurityGroup> ExtraNodeSecurityGroups
public bool? IgnoreScalingChanges { get; set; }

/// <summary>
/// The ingress rule that gives node group access.
/// The IAM InstanceProfile to use on the NodeGroup. Properties instanceProfile and instanceProfileName are mutually exclusive.
/// </summary>
[Input("instanceProfile")]
public Pulumi.Aws.Iam.InstanceProfile? InstanceProfile { get; set; }

/// <summary>
/// The name of the IAM InstanceProfile to use on the NodeGroup. Properties instanceProfile and instanceProfileName are mutually exclusive.
/// </summary>
[Input("instanceProfileName")]
public Input<string>? InstanceProfileName { get; set; }

/// <summary>
/// The instance type to use for the cluster's nodes. Defaults to "t3.medium".
/// </summary>
Expand Down
Loading

0 comments on commit 230eba5

Please sign in to comment.