async function getAadKubeconfig()

in src/commands/utils/clusters.ts [216:290]


async function getAadKubeconfig(
    client: azcs.ContainerServiceClient,
    resourceGroup: string,
    clusterName: string,
): Promise<Errorable<string>> {
    let clusterUserCredentials: azcs.CredentialResults;

    try {
        // For AAD clusters, force the credentials to be in 'exec' format.
        clusterUserCredentials = await client.managedClusters.listClusterUserCredentials(resourceGroup, clusterName, {
            format: azcs.KnownFormat.Exec,
        });
    } catch (e) {
        return {
            succeeded: false,
            error: `Failed to retrieve user credentials for AAD cluster ${clusterName}: ${e}`,
        };
    }

    // The initial credentials contain an 'exec' section that calls kubelogin with devicecode authentication.
    const unauthenticatedKubeconfig = getClusterUserKubeconfig(clusterUserCredentials, clusterName);
    if (failed(unauthenticatedKubeconfig)) {
        return unauthenticatedKubeconfig;
    }

    // Parse the kubeconfig YAML into an object so that we can read and update exec options.
    let kubeconfigYaml: KubeConfig;
    try {
        kubeconfigYaml = yaml.load(unauthenticatedKubeconfig.result) as KubeConfig;
    } catch (e) {
        return { succeeded: false, error: `Failed to parse kubeconfig YAML for ${clusterName}: ${e}` };
    }

    // We expect there will be just one user in the kubeconfig. Read the 'exec' arguments for it.
    const execBlock = kubeconfigYaml.users[0].user.exec;
    const execOptions = readExecOptions(execBlock.args);

    // Get a token whose audience is the AKS server ID.
    const scopes = [`${execOptions.serverId}/.default`, `VSCODE_TENANT:${execOptions.tenantId}`];
    let session: AuthenticationSession;
    try {
        session = await authentication.getSession("microsoft", scopes, { createIfNone: true });
    } catch (e) {
        return {
            succeeded: false,
            error: `Failed to retrieve Microsoft authentication session for scopes [${scopes.join(",")}]: ${getErrorMessage(e)}`,
        };
    }

    const tokenInfo = getTokenInfo(session);
    if (failed(tokenInfo)) {
        return tokenInfo;
    }

    // Now we have a token the user can use to access their cluster, but kubelogin doesn't support that being passed as an
    // argument directly. Instead, we can save it to a temp directory and instruct kubelogin to use that as its cache directory.
    const cacheDir = storeCachedAadToken(tokenInfo.result, execOptions);
    if (failed(cacheDir)) {
        return cacheDir;
    }

    // This extension controls the version of kubelogin used, so that:
    // 1. We don't need to rely on the user having previously downloaded it, and
    // 2. This workflow doesn't get broken by kubelogin behaviour changes between versions
    const kubeloginPath = await getKubeloginBinaryPath();
    if (failed(kubeloginPath)) {
        return kubeloginPath;
    }

    // Update the kubeconfig YAML with the new kubelogin options, and return the serialized output as a string.
    execBlock.command = kubeloginPath.result;
    execBlock.args = buildExecOptionsWithCache(execOptions, cacheDir.result);
    const authenticatedKubeConfig = yaml.dump(kubeconfigYaml);
    return { succeeded: true, result: authenticatedKubeConfig };
}