Skip to content

Commit

Permalink
Add region to kubeconfig (#1498)
Browse files Browse the repository at this point in the history
The generated kubeconfig is missing the region argument. This can lead
to issues in many scenarios, e.g. when a user has `AWS_DEFAULT_REGION`
set or when they have no default region set and use the provider
configuration for setting the pulumi region (i.e. `aws:region`). This
aligns the provider with how the AWS CLI generates the kubeconfig (`aws
eks update-kubeconfig`).
This change uses the cluster ARN to identify the cluster's region and
sets it in the kubeconfig.

The k8s provider's `kubeconfig` property will not trigger replacements.
Multiple upgrade tests validate this (e.g. `aws-profile`).

<!--Refer to related PRs or issues: #1234, or 'Fixes #1234' or 'Closes
#1234'.
Or link to full URLs to issues or pull requests in other GitHub
repositories. -->
  • Loading branch information
flostadler authored Nov 19, 2024
1 parent b170816 commit a1061b6
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 1 deletion.
23 changes: 22 additions & 1 deletion nodejs/eks/cluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { createStorageClass, EBSVolumeType, StorageClass } from "./storageclass"
import { InputTags, UserStorageClasses } from "./utils";
import { VpcCniAddon, VpcCniAddonOptions } from "./cni-addon";
import { stringifyAddonConfiguration } from "./addon";
import { getRegionFromArn } from "./utilities";

/**
* RoleMapping describes a mapping from an AWS IAM role to a Kubernetes user and groups.
Expand Down Expand Up @@ -202,11 +203,21 @@ interface ExecEnvVar {
export function generateKubeconfig(
clusterName: pulumi.Input<string>,
clusterEndpoint: pulumi.Input<string>,
region: pulumi.Input<string>,
includeProfile: boolean,
certData?: pulumi.Input<string>,
opts?: KubeconfigOptions,
) {
let args = ["eks", "get-token", "--cluster-name", clusterName, "--output", "json"];
let args = [
"--region",
region,
"eks",
"get-token",
"--cluster-name",
clusterName,
"--output",
"json",
];
const env: ExecEnvVar[] = [
{
name: "KUBERNETES_EXEC_INFO",
Expand Down Expand Up @@ -723,19 +734,22 @@ export function createCore(
// depend on the autoscaling group we'll create later so that nothing attempts to use the EKS cluster before
// its worker nodes have come up.
const genKubeconfig = (useProfileName: boolean) => {
const region = eksCluster.arn.apply(getRegionFromArn);
const kubeconfig = pulumi
.all([
eksCluster.name,
endpoint,
eksCluster.certificateAuthority,
args.providerCredentialOpts,
region,
])
.apply(
([
clusterName,
clusterEndpoint,
clusterCertificateAuthority,
providerCredentialOpts,
region,
]) => {
let config = {};

Expand All @@ -745,6 +759,7 @@ export function createCore(
return generateKubeconfig(
clusterName,
clusterEndpoint,
region,
useProfileName,
clusterCertificateAuthority?.data,
opts,
Expand All @@ -754,6 +769,7 @@ export function createCore(
config = generateKubeconfig(
clusterName,
clusterEndpoint,
region,
useProfileName,
clusterCertificateAuthority?.data,
providerCredentialOpts,
Expand All @@ -762,6 +778,7 @@ export function createCore(
config = generateKubeconfig(
clusterName,
clusterEndpoint,
region,
useProfileName,
clusterCertificateAuthority?.data,
);
Expand Down Expand Up @@ -2003,9 +2020,11 @@ export class Cluster extends pulumi.ComponentResource {
* - https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html
*/
getKubeconfig(args: KubeconfigOptions): pulumi.Output<string> {
const region = this.eksCluster.arn.apply(getRegionFromArn);
const kc = generateKubeconfig(
this.eksCluster.name,
this.eksCluster.endpoint,
region,
true,
this.eksCluster.certificateAuthority?.data,
args,
Expand Down Expand Up @@ -2275,9 +2294,11 @@ export class ClusterInternal extends pulumi.ComponentResource {
}

getKubeconfig(args: KubeconfigOptions): pulumi.Output<string> {
const region = this.eksCluster.arn.apply(getRegionFromArn);
const kc = generateKubeconfig(
this.eksCluster.name,
this.eksCluster.endpoint,
region,
true,
this.eksCluster.certificateAuthority?.data,
args,
Expand Down
47 changes: 47 additions & 0 deletions nodejs/eks/utilities.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2016-2024, Pulumi Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { getRegionFromArn } from "./utilities";

describe("getRegionFromArn", () => {
test.each([
["arn:aws:eks:us-east-1:123456789012:cluster/my-awesome-cluster", "us-east-1"],
["arn:aws:eks:us-west-2:123456789012:cluster/my-awesome-cluster", "us-west-2"],
["arn:aws-cn:eks:cn-north-1:123456789012:cluster/my-awesome-cluster", "cn-north-1"],
[
"arn:aws-us-gov:eks:us-gov-west-1:123456789012:cluster/my-awesome-cluster",
"us-gov-west-1",
],
])("should extract the region from the ARN", (arn, expected) => {
const region = getRegionFromArn(arn);
expect(region).toEqual(expected);
});

it("should throw an error for an ARN with less than 4 parts", () => {
const arn = "arn:aws:service";
expect(() => getRegionFromArn(arn)).toThrow("Invalid ARN: 'arn:aws:service'");
});

it("should throw an error for an ARN not starting with 'arn'", () => {
const arn = "invalid:aws:service:region:account-id:resource";
expect(() => getRegionFromArn(arn)).toThrow(
"Invalid ARN: 'invalid:aws:service:region:account-id:resource'",
);
});

it("should throw an error for an empty ARN", () => {
const arn = "";
expect(() => getRegionFromArn(arn)).toThrow("Invalid ARN: ''");
});
});
15 changes: 15 additions & 0 deletions nodejs/eks/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,18 @@ export function getVersion(): string {
export function isObject(obj: any): obj is Record<string, any> {
return obj !== null && typeof obj === "object" && !Array.isArray(obj);
}

/**
* Extracts the AWS region from an Amazon Resource Name (ARN).
*
* @param arn - The ARN from which to extract the region.
* @returns The region extracted from the ARN.
* @throws Will throw an error if the ARN is invalid.
*/
export function getRegionFromArn(arn: string): string {
const arnParts = arn.split(":");
if (arnParts.length < 4 || arnParts[0] !== "arn") {
throw new Error(`Invalid ARN: '${arn}'`);
}
return arnParts[3];
}

0 comments on commit a1061b6

Please sign in to comment.