From a1061b676439e2436e511ccb27f62b3cfa0ad458 Mon Sep 17 00:00:00 2001 From: Florian Stadler Date: Tue, 19 Nov 2024 15:50:33 +0100 Subject: [PATCH] Add region to kubeconfig (#1498) 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`). --- nodejs/eks/cluster.ts | 23 +++++++++++++++++- nodejs/eks/utilities.test.ts | 47 ++++++++++++++++++++++++++++++++++++ nodejs/eks/utilities.ts | 15 ++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 nodejs/eks/utilities.test.ts diff --git a/nodejs/eks/cluster.ts b/nodejs/eks/cluster.ts index 1ca137636..2e02d5167 100644 --- a/nodejs/eks/cluster.ts +++ b/nodejs/eks/cluster.ts @@ -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. @@ -202,11 +203,21 @@ interface ExecEnvVar { export function generateKubeconfig( clusterName: pulumi.Input, clusterEndpoint: pulumi.Input, + region: pulumi.Input, includeProfile: boolean, certData?: pulumi.Input, 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", @@ -723,12 +734,14 @@ 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( ([ @@ -736,6 +749,7 @@ export function createCore( clusterEndpoint, clusterCertificateAuthority, providerCredentialOpts, + region, ]) => { let config = {}; @@ -745,6 +759,7 @@ export function createCore( return generateKubeconfig( clusterName, clusterEndpoint, + region, useProfileName, clusterCertificateAuthority?.data, opts, @@ -754,6 +769,7 @@ export function createCore( config = generateKubeconfig( clusterName, clusterEndpoint, + region, useProfileName, clusterCertificateAuthority?.data, providerCredentialOpts, @@ -762,6 +778,7 @@ export function createCore( config = generateKubeconfig( clusterName, clusterEndpoint, + region, useProfileName, clusterCertificateAuthority?.data, ); @@ -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 { + const region = this.eksCluster.arn.apply(getRegionFromArn); const kc = generateKubeconfig( this.eksCluster.name, this.eksCluster.endpoint, + region, true, this.eksCluster.certificateAuthority?.data, args, @@ -2275,9 +2294,11 @@ export class ClusterInternal extends pulumi.ComponentResource { } getKubeconfig(args: KubeconfigOptions): pulumi.Output { + const region = this.eksCluster.arn.apply(getRegionFromArn); const kc = generateKubeconfig( this.eksCluster.name, this.eksCluster.endpoint, + region, true, this.eksCluster.certificateAuthority?.data, args, diff --git a/nodejs/eks/utilities.test.ts b/nodejs/eks/utilities.test.ts new file mode 100644 index 000000000..53af31b66 --- /dev/null +++ b/nodejs/eks/utilities.test.ts @@ -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: ''"); + }); +}); diff --git a/nodejs/eks/utilities.ts b/nodejs/eks/utilities.ts index c1a313c60..e886bb63e 100644 --- a/nodejs/eks/utilities.ts +++ b/nodejs/eks/utilities.ts @@ -26,3 +26,18 @@ export function getVersion(): string { export function isObject(obj: any): obj is Record { 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]; +}