Skip to content

Commit

Permalink
Centralize request logic, turn on retries, and add debug logging (#323)
Browse files Browse the repository at this point in the history
  • Loading branch information
sethvargo authored Dec 16, 2024
1 parent 9025e8f commit b7a282c
Show file tree
Hide file tree
Showing 7 changed files with 294 additions and 282 deletions.
4 changes: 2 additions & 2 deletions dist/index.js

Large diffs are not rendered by default.

293 changes: 137 additions & 156 deletions package-lock.json

Large diffs are not rendered by default.

16 changes: 8 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,18 @@
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
"@eslint/js": "^9.15.0",
"@kubernetes/client-node": "^0.22.2",
"@types/node": "^22.9.3",
"@typescript-eslint/eslint-plugin": "^8.15.0",
"@typescript-eslint/parser": "^8.15.0",
"@eslint/js": "^9.17.0",
"@kubernetes/client-node": "^0.22.3",
"@types/node": "^22.10.2",
"@typescript-eslint/eslint-plugin": "^8.18.0",
"@typescript-eslint/parser": "^8.18.0",
"@vercel/ncc": "^0.38.3",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint": "^9.15.0",
"prettier": "^3.3.3",
"eslint": "^9.17.0",
"prettier": "^3.4.2",
"ts-node": "^10.9.2",
"typescript-eslint": "^8.15.0",
"typescript-eslint": "^8.18.0",
"typescript": "^5.7.2"
}
}
144 changes: 89 additions & 55 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import { presence } from '@google-github-actions/actions-utils';
import { GoogleAuth } from 'google-auth-library';
import { Headers } from 'gaxios';
import { Headers, GaxiosOptions } from 'gaxios';
import YAML from 'yaml';

// Do not listen to the linter - this can NOT be rewritten as an ES6 import statement.
Expand Down Expand Up @@ -45,6 +45,16 @@ type ClientOptions = {
projectID?: string;
quotaProjectID?: string;
location?: string;
logger?: Logger;
};

/**
* Logger is the passed in logger on the client.
*/
type Logger = {
debug: (message: string) => void; // eslint-disable-line no-unused-vars
info: (message: string) => void; // eslint-disable-line no-unused-vars
warn: (message: string) => void; // eslint-disable-line no-unused-vars
};

/**
Expand Down Expand Up @@ -130,9 +140,11 @@ export class ClusterClient {
* name is given (e.g. `c`), these values will be used to construct the full
* resource name.
*/
readonly #logger?: Logger;
readonly #projectID?: string;
readonly #quotaProjectID?: string;
readonly #location?: string;
readonly #headers?: Headers;

readonly defaultEndpoint = 'https://container.googleapis.com/v1';
readonly hubEndpoint = 'https://gkehub.googleapis.com/v1';
Expand All @@ -141,6 +153,8 @@ export class ClusterClient {
readonly auth: GoogleAuth;

constructor(opts?: ClientOptions) {
this.#logger = opts?.logger;

this.auth = new GoogleAuth({
scopes: [
'https://www.googleapis.com/auth/cloud-platform',
Expand All @@ -152,6 +166,13 @@ export class ClusterClient {
this.#projectID = opts?.projectID;
this.#quotaProjectID = opts?.quotaProjectID;
this.#location = opts?.location;

this.#headers = {
'User-Agent': userAgent,
};
if (this.#quotaProjectID) {
this.#headers['X-Goog-User-Project'] = this.#quotaProjectID;
}
}

/**
Expand All @@ -160,13 +181,38 @@ export class ClusterClient {
* @returns string
*/
async getToken(): Promise<string> {
this.#logger?.debug(`Getting token`);

const token = await this.auth.getAccessToken();
if (!token) {
throw new Error('Failed to generate token.');
}
return token;
}

/**
* request is a wrapper around an authenticated request.
*
* @returns T
*/
async request<T>(opts: GaxiosOptions): Promise<T> {
this.#logger?.debug(`Initiating request with options: ${JSON.stringify(opts)}`);

const mergedOpts: GaxiosOptions = {
...{
retry: true,
headers: this.#headers,
errorRedactor: false,
},
...opts,
};

this.#logger?.debug(` Request options: ${JSON.stringify(mergedOpts)}`);

const resp = await this.auth.request<T>(mergedOpts);
return resp.data;
}

/**
* Generates full resource name.
*
Expand Down Expand Up @@ -209,14 +255,15 @@ export class ClusterClient {
* @returns project number.
*/
async projectIDtoNum(projectID: string): Promise<string> {
this.#logger?.debug(`Converting project ID '${projectID}' to a project number`);

const url = `${this.cloudResourceManagerEndpoint}/projects/${projectID}`;
const resp = (await this.auth.request({
const resp = await this.request<{ name: string }>({
url: url,
headers: this.#defaultHeaders(),
})) as { data: { name: string } };
});

// projectRef of form projects/<project-num>"
const projectRef = resp.data?.name;
const projectRef = resp.name;
const projectNum = projectRef.replace('projects/', '');
if (!projectRef.includes('projects/') || !projectNum) {
throw new Error(
Expand All @@ -234,15 +281,15 @@ export class ClusterClient {
* @returns endpoint.
*/
async getConnectGWEndpoint(name: string): Promise<string> {
this.#logger?.debug(`Getting connect gateway endpoint for '${name}'`);

const membershipURL = `${this.hubEndpoint}/${name}`;
const resp = (await this.auth.request({
const membership = await this.request<HubMembershipResponse>({
url: membershipURL,
headers: this.#defaultHeaders(),
})) as HubMembershipResponse;
});

const membership = resp.data;
if (!membership) {
throw new Error(`Failed to lookup membership: ${resp}`);
throw new Error(`Failed to lookup membership: ${name}`);
}

// For GKE clusters, the configuration path is gkeMemberships, not
Expand All @@ -269,6 +316,8 @@ export class ClusterClient {
* @returns Fleet membership name.
*/
async discoverClusterMembership(clusterName: string): Promise<string> {
this.#logger?.debug(`Discovering cluster membership for '${clusterName}'`);

const clusterResourceLink = `//container.googleapis.com/${this.getResource(clusterName)}`;
const projectID = this.#projectID;
if (!projectID) {
Expand All @@ -278,12 +327,11 @@ export class ClusterClient {
}

const url = `${this.hubEndpoint}/projects/${projectID}/locations/global/memberships?filter=endpoint.gkeCluster.resourceLink="${clusterResourceLink}"`;
const resp = (await this.auth.request({
const resp = await this.request<HubMembershipsResponse>({
url: url,
headers: this.#defaultHeaders(),
})) as HubMembershipsResponse;
});

const memberships = resp.data.resources;
const memberships = resp.resources;
if (!memberships || memberships.length < 1) {
throw new Error(
`Expected one membership for ${clusterName} in ${projectID}. ` +
Expand All @@ -309,11 +357,12 @@ export class ClusterClient {
* @returns a Cluster object.
*/
async getCluster(clusterName: string): Promise<ClusterResponse> {
this.#logger?.debug(`Getting information about cluster '${clusterName}'`);

const url = `${this.defaultEndpoint}/${this.getResource(clusterName)}`;
const resp = (await this.auth.request({
const resp = await this.request<ClusterResponse>({
url: url,
headers: this.#defaultHeaders(),
})) as ClusterResponse;
});
return resp;
}

Expand All @@ -323,17 +372,19 @@ export class ClusterClient {
* @param opts Input options. See CreateKubeConfigOptions.
*/
async createKubeConfig(opts: CreateKubeConfigOptions): Promise<string> {
this.#logger?.debug(`Creating kubeconfig with options: ${JSON.stringify(opts)}`);

const cluster = opts.clusterData;
let endpoint = cluster.data.endpoint;
let endpoint = cluster.endpoint;
const connectGatewayEndpoint = presence(opts.connectGWEndpoint);
if (connectGatewayEndpoint) {
endpoint = connectGatewayEndpoint;
}
if (opts.useInternalIP) {
endpoint = cluster.data.privateClusterConfig.privateEndpoint;
endpoint = cluster.privateClusterConfig.privateEndpoint;
}
if (opts.useDNSBasedEndpoint) {
endpoint = cluster.data.controlPlaneEndpointsConfig.dnsEndpointConfig.endpoint;
endpoint = cluster.controlPlaneEndpointsConfig.dnsEndpointConfig.endpoint;
}

// By default, use the CA cert. Even if user doesn't specify
Expand All @@ -356,41 +407,30 @@ export class ClusterClient {
{
cluster: {
...(useCACert && {
'certificate-authority-data': cluster.data.masterAuth?.clusterCaCertificate,
'certificate-authority-data': cluster.masterAuth?.clusterCaCertificate,
}),
server: `https://${endpoint}`,
},
name: cluster.data.name,
name: cluster.name,
},
],
'contexts': [
{
context: {
cluster: cluster.data.name,
user: cluster.data.name,
cluster: cluster.name,
user: cluster.name,
namespace: opts.namespace,
},
name: contextName,
},
],
'kind': 'Config',
'current-context': contextName,
'users': [{ ...{ name: cluster.data.name }, ...auth }],
'users': [{ ...{ name: cluster.name }, ...auth }],
};

return YAML.stringify(kubeConfig);
}

#defaultHeaders(): Headers {
const h: Headers = {
'User-Agent': userAgent,
};

if (this.#quotaProjectID) {
h['X-Goog-User-Project'] = this.#quotaProjectID;
}
return h;
}
}

type cluster = {
Expand Down Expand Up @@ -454,19 +494,17 @@ export type KubeConfig = {
};

export type ClusterResponse = {
data: {
name: string;
endpoint: string;
masterAuth: {
clusterCaCertificate: string;
};
privateClusterConfig: {
privateEndpoint: string;
};
controlPlaneEndpointsConfig: {
dnsEndpointConfig: {
endpoint: string;
};
name: string;
endpoint: string;
masterAuth: {
clusterCaCertificate: string;
};
privateClusterConfig: {
privateEndpoint: string;
};
controlPlaneEndpointsConfig: {
dnsEndpointConfig: {
endpoint: string;
};
};
};
Expand All @@ -475,17 +513,13 @@ export type ClusterResponse = {
* HubMembershipsResponse is the response from listing GKE Hub memberships.
*/
type HubMembershipsResponse = {
data: {
resources: HubMembership[];
};
resources: HubMembership[];
};

/**
* HubMembershipResponse is the response from getting a GKE Hub membership.
*/
type HubMembershipResponse = {
data: HubMembership;
};
type HubMembershipResponse = HubMembership;

/**
* HubMembership is a single HubMembership.
Expand Down
6 changes: 6 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
getInput,
debug as logDebug,
info as logInfo,
warning as logWarning,
setFailed,
setOutput,
} from '@actions/core';
Expand Down Expand Up @@ -105,6 +106,11 @@ export async function run(): Promise<void> {
projectID: projectID,
quotaProjectID: quotaProjectID,
location: location,
logger: {
debug: logDebug,
info: logInfo,
warn: logWarning,
},
});

// Get Cluster object
Expand Down
Loading

0 comments on commit b7a282c

Please sign in to comment.