From 40c85818d4d1df6e0a1ac1799799bd3271c42f5e Mon Sep 17 00:00:00 2001 From: Rick Lane Date: Tue, 2 Apr 2024 11:52:59 -0400 Subject: [PATCH] Auto-formatting Javascript files --- .github/actions/provision-cluster/create.js | 54 ++-- .github/actions/provision-cluster/delete.js | 42 +-- .../provision-cluster/lib/common_test.js | 67 +++-- .github/actions/provision-cluster/lib/gke.js | 284 ++++++++++-------- .../actions/provision-cluster/lib/gke.test.js | 12 +- .../provision-cluster/lib/kubeception.js | 96 +++--- .../provision-cluster/lib/kubeception.test.js | 115 ++++--- .github/actions/provision-cluster/lib/mock.js | 58 ++-- .../actions/provision-cluster/lib/slack.js | 30 +- .../actions/provision-cluster/lib/utils.js | 96 +++--- .../provision-cluster/lib/utils.test.js | 212 +++++++------ 11 files changed, 590 insertions(+), 476 deletions(-) diff --git a/.github/actions/provision-cluster/create.js b/.github/actions/provision-cluster/create.js index 77f778f3..281fadb0 100644 --- a/.github/actions/provision-cluster/create.js +++ b/.github/actions/provision-cluster/create.js @@ -1,43 +1,45 @@ -'use strict'; +"use strict"; -const core = require('@actions/core') +const core = require("@actions/core"); -const registry = require('./lib/registry.js') -const utils = require('./lib/utils.js') +const registry = require("./lib/registry.js"); +const utils = require("./lib/utils.js"); async function create() { // inputs are defined in action metadata file - const distribution = core.getInput('distribution') - const action = core.getInput('action') - const version = core.getInput('version') - const lifespan = core.getInput('lifespan') - const kubeconfigPath = core.getInput('kubeconfig') + const distribution = core.getInput("distribution"); + const action = core.getInput("action"); + const version = core.getInput("version"); + const lifespan = core.getInput("lifespan"); + const kubeconfigPath = core.getInput("kubeconfig"); - let provider = registry.getProvider(distribution) + let provider = registry.getProvider(distribution); - if (action === 'expire') { - return + if (action === "expire") { + return; } - core.notice(`Creating ${distribution} ${version} and writing kubeconfig to file: ${kubeconfigPath}!`) - let cluster = await provider.allocateCluster(version, lifespan) - core.saveState(registry.CLUSTER_NAME, cluster.name) + core.notice( + `Creating ${distribution} ${version} and writing kubeconfig to file: ${kubeconfigPath}!` + ); + let cluster = await provider.allocateCluster(version, lifespan); + core.saveState(registry.CLUSTER_NAME, cluster.name); core.setOutput("clusterName", cluster?.name); core.setOutput("projectId", cluster?.project); core.setOutput("location", cluster?.zone); - core.notice(`Creating ${distribution} cluster ${cluster.name} ...`) - let kubeconfig = await provider.makeKubeconfig(cluster) - core.notice(`Cluster created: ${cluster.name}!`) - let contents = JSON.stringify(kubeconfig, undefined, 2) + "\n" - utils.writeFile(kubeconfigPath, contents) + core.notice(`Creating ${distribution} cluster ${cluster.name} ...`); + let kubeconfig = await provider.makeKubeconfig(cluster); + core.notice(`Cluster created: ${cluster.name}!`); + let contents = JSON.stringify(kubeconfig, undefined, 2) + "\n"; + utils.writeFile(kubeconfigPath, contents); - core.notice(`Kubeconfig written to ${kubeconfigPath}.`) - core.notice(`Exporting KUBECONFIG as ${kubeconfigPath}`) - core.exportVariable("KUBECONFIG", kubeconfigPath) + core.notice(`Kubeconfig written to ${kubeconfigPath}.`); + core.notice(`Exporting KUBECONFIG as ${kubeconfigPath}`); + core.exportVariable("KUBECONFIG", kubeconfigPath); } -create().catch((error)=>{ - core.setFailed(error.message) -}) +create().catch((error) => { + core.setFailed(error.message); +}); diff --git a/.github/actions/provision-cluster/delete.js b/.github/actions/provision-cluster/delete.js index ede2c77e..167656d4 100644 --- a/.github/actions/provision-cluster/delete.js +++ b/.github/actions/provision-cluster/delete.js @@ -1,44 +1,44 @@ -'use strict'; +"use strict"; -const core = require('@actions/core') +const core = require("@actions/core"); -const registry = require('./lib/registry.js') -const slack = require('./lib/slack.js') +const registry = require("./lib/registry.js"); +const slack = require("./lib/slack.js"); async function do_delete() { // inputs are defined in action metadata file - const distribution = core.getInput('distribution') - const clusterName = core.getState(registry.CLUSTER_NAME) + const distribution = core.getInput("distribution"); + const clusterName = core.getState(registry.CLUSTER_NAME); - let provider = registry.getProvider(distribution) + let provider = registry.getProvider(distribution); - let promises = [] - promises.push(expire(provider, distribution)) + let promises = []; + promises.push(expire(provider, distribution)); if (typeof clusterName !== typeof undefined && clusterName !== "") { - core.notice(`Deleting ${distribution} cluster ${clusterName}!`) - promises.push(delete_allocated(provider, clusterName)) + core.notice(`Deleting ${distribution} cluster ${clusterName}!`); + promises.push(delete_allocated(provider, clusterName)); } - return Promise.all(promises) + return Promise.all(promises); } async function expire(provider) { - let orphaned = await provider.expireClusters() + let orphaned = await provider.expireClusters(); if (orphaned.length == 0) { - return + return; } - core.notice(`Orphaned Clusters: ${orphaned.join(', ')}`) - slack.notify(`Orphaned clusters:\n\n - ${orphaned.join("\n - ")}`) + core.notice(`Orphaned Clusters: ${orphaned.join(", ")}`); + slack.notify(`Orphaned clusters:\n\n - ${orphaned.join("\n - ")}`); } async function delete_allocated(provider, name) { - let cluster = await provider.getCluster(name) - return provider.deleteCluster(cluster) + let cluster = await provider.getCluster(name); + return provider.deleteCluster(cluster); } -do_delete().catch((error)=>{ - core.setFailed(error.message) -}) +do_delete().catch((error) => { + core.setFailed(error.message); +}); diff --git a/.github/actions/provision-cluster/lib/common_test.js b/.github/actions/provision-cluster/lib/common_test.js index fca18453..42e8c19b 100644 --- a/.github/actions/provision-cluster/lib/common_test.js +++ b/.github/actions/provision-cluster/lib/common_test.js @@ -1,36 +1,47 @@ -'use string'; +"use string"; -const mock = require('./mock.js') -let MOCK = mock.MOCK -let cluster = mock.cluster +const mock = require("./mock.js"); +let MOCK = mock.MOCK; +let cluster = mock.cluster; async function lifecycle(client) { - let allocated = await client.allocateCluster('1.22', 300) - let kubeconfig = await client.makeKubeconfig(allocated) - expect(kubeconfig).toEqual( - { - "apiVersion": "v1", - "kind":"Config", - "clusters": [{ - "cluster": { + let allocated = await client.allocateCluster("1.22", 300); + let kubeconfig = await client.makeKubeconfig(allocated); + expect(kubeconfig).toEqual({ + apiVersion: "v1", + kind: "Config", + clusters: [ + { + cluster: { "certificate-authority-data": cluster.masterAuth.clusterCaCertificate, - "server":"https://34.172.65.239" + server: "https://34.172.65.239", }, - "name":"gke-cluster" - }], - "users": [{ - "name":"gke-user", - "user":{ - "token":MOCK.ACCESS_TOKEN - }}], - "contexts": [{ - "context":{"cluster":"gke-cluster","namespace":"default","user":"gke-user"}, - "name":"gke-context"}], - "current-context":"gke-context" - }) + name: "gke-cluster", + }, + ], + users: [ + { + name: "gke-user", + user: { + token: MOCK.ACCESS_TOKEN, + }, + }, + ], + contexts: [ + { + context: { + cluster: "gke-cluster", + namespace: "default", + user: "gke-user", + }, + name: "gke-context", + }, + ], + "current-context": "gke-context", + }); - let op = await client.deleteCluster(cluster) - expect(op.done).toBeTruthy() + let op = await client.deleteCluster(cluster); + expect(op.done).toBeTruthy(); } -module.exports = { lifecycle } +module.exports = { lifecycle }; diff --git a/.github/actions/provision-cluster/lib/gke.js b/.github/actions/provision-cluster/lib/gke.js index ba59e686..674e1df9 100644 --- a/.github/actions/provision-cluster/lib/gke.js +++ b/.github/actions/provision-cluster/lib/gke.js @@ -1,188 +1,208 @@ -'use strict'; +"use strict"; -const core = require('@actions/core') -const container = require('@google-cloud/container') -const utils = require('./utils.js') +const core = require("@actions/core"); +const container = require("@google-cloud/container"); +const utils = require("./utils.js"); -const STATUS_ENUM = container.protos.google.container.v1.Operation.Status +const STATUS_ENUM = container.protos.google.container.v1.Operation.Status; // Every cluster created by this action is labeled with provisioned-category=ephemeral -const CATEGORY_PROPERTY = 'provisioned-category' +const CATEGORY_PROPERTY = "provisioned-category"; // Every cluster created by this action will have a provisioned-lifespan that defines when it is // ok to delete the cluster. -const LIFESPAN_PROPERTY = 'provisioned-lifespan' -const EPHEMERAL = 'ephemeral' +const LIFESPAN_PROPERTY = "provisioned-lifespan"; +const EPHEMERAL = "ephemeral"; // Default lifespan of 60 minutes. -const DEFAULT_LIFESPAN = 3600 // 60 minutes +const DEFAULT_LIFESPAN = 3600; // 60 minutes const gkeDefaults = { - network: 'default', + network: "default", initialNodeCount: 1, nodeConfig: { - machineType: 'e2-standard-2', - } -} + machineType: "e2-standard-2", + }, +}; -const oneHourMillis = 60*60*1000 +const oneHourMillis = 60 * 60 * 1000; // The Client class is a convenience wrapper around the google API that allows for sharing of some // of the boilerplate between different operations. class Client { - constructor(zone, gkeClient) { if (typeof gkeClient == typeof undefined) { - const rawCredentials = core.getInput('gkeCredentials') + const rawCredentials = core.getInput("gkeCredentials"); if (!rawCredentials) { - throw new Error(`gkeCredentials are missing. Make sure that input parameter gkeCredentials was provided`) + throw new Error( + `gkeCredentials are missing. Make sure that input parameter gkeCredentials was provided` + ); } - const credentials = JSON.parse(rawCredentials) + const credentials = JSON.parse(rawCredentials); const options = { - projectId: core.getInput('gkeProject') || credentials.project_id, - credentials: credentials - } + projectId: core.getInput("gkeProject") || credentials.project_id, + credentials: credentials, + }; if (!options.projectId) { - throw new Error(`Unable to determine the name of the gke project, please specify the gkeProject input parameter or make sure it is included in the gkeCredentials input.`) + throw new Error( + `Unable to determine the name of the gke project, please specify the gkeProject input parameter or make sure it is included in the gkeCredentials input.` + ); } - gkeClient = new container.v1.ClusterManagerClient(options) + gkeClient = new container.v1.ClusterManagerClient(options); } - this.client = gkeClient - this.project = null - this.zone = zone + this.client = gkeClient; + this.project = null; + this.zone = zone; } async getProjectId() { if (this.project === null) { - this.project = await this.client.getProjectId() + this.project = await this.client.getProjectId(); } - return this.project + return this.project; } // Compute the location used in multiple methods. async getLocation() { - return `projects/${await this.getProjectId()}/locations/${this.zone}` + return `projects/${await this.getProjectId()}/locations/${this.zone}`; } // Create a new cluster with a unique name, wait for it to be fully provisioned, and then fetch // and return the resulting cluster object. async allocateCluster(version, lifespan) { - const rawConfig = core.getInput('gkeConfig') || '{}' - const config = JSON.parse(rawConfig) - let cluster = {...gkeDefaults, ...config} + const rawConfig = core.getInput("gkeConfig") || "{}"; + const config = JSON.parse(rawConfig); + let cluster = { ...gkeDefaults, ...config }; - const name = `test-${utils.uid()}` - cluster.name = name + const name = `test-${utils.uid()}`; + cluster.name = name; if (!cluster.initialClusterVersion) { - cluster.initialClusterVersion = version + cluster.initialClusterVersion = version; } if (lifespan) { if (typeof cluster.resourceLabels === typeof undefined) { - cluster.resourceLabels = {} + cluster.resourceLabels = {}; } - if (typeof cluster.resourceLabels[LIFESPAN_PROPERTY] === typeof undefined) { - cluster.resourceLabels[LIFESPAN_PROPERTY] = lifespan + if ( + typeof cluster.resourceLabels[LIFESPAN_PROPERTY] === typeof undefined + ) { + cluster.resourceLabels[LIFESPAN_PROPERTY] = lifespan; } } - await this.createCluster(cluster) - return this.getCluster(name) + await this.createCluster(cluster); + return this.getCluster(name); } // Get a cluster by name. async getCluster(name) { - const [cluster] = await this.client.getCluster({name: `${await this.getLocation()}/clusters/${name}`}) - return cluster + const [cluster] = await this.client.getCluster({ + name: `${await this.getLocation()}/clusters/${name}`, + }); + return cluster; } // Make a functioning kubeconfig from a cluster object. async makeKubeconfig(cluster) { - let token = await this.client.auth.getAccessToken() + let token = await this.client.auth.getAccessToken(); let kubeconfig = { apiVersion: "v1", kind: "Config", - clusters: [{ - cluster: { - "certificate-authority-data": cluster.masterAuth.clusterCaCertificate, - server: `https://${cluster.endpoint}` + clusters: [ + { + cluster: { + "certificate-authority-data": + cluster.masterAuth.clusterCaCertificate, + server: `https://${cluster.endpoint}`, + }, + name: "gke-cluster", }, - name: "gke-cluster" - }], - users: [{name: "gke-user", user: {token: token}}], - contexts: [{ - context: { - cluster: "gke-cluster", - namespace: "default", - user: "gke-user" + ], + users: [{ name: "gke-user", user: { token: token } }], + contexts: [ + { + context: { + cluster: "gke-cluster", + namespace: "default", + user: "gke-user", + }, + name: "gke-context", }, - name: "gke-context" - }], - "current-context": "gke-context" - } + ], + "current-context": "gke-context", + }; - return kubeconfig + return kubeconfig; } // Iterate over all the clusters in the zone and delete any expired clusters. async expireClusters(lifespanOverride) { - let promises = [] - let orphaned = [] + let promises = []; + let orphaned = []; for (let c of await this.listClusters()) { - promises.push(this.maybeExpireCluster(c, lifespanOverride)) - const ageMillis = clusterAgeMillis(c) - const lifespanMillis = clusterLifespanMillis(c) + promises.push(this.maybeExpireCluster(c, lifespanOverride)); + const ageMillis = clusterAgeMillis(c); + const lifespanMillis = clusterLifespanMillis(c); // We may not run often enough to expire short lifespan clusters, so use the max of the // lifespan and one hour. - const threshold = Math.max(lifespanMillis, oneHourMillis) - if (lifespanMillis > 0 && ageMillis >= 2*threshold) { + const threshold = Math.max(lifespanMillis, oneHourMillis); + if (lifespanMillis > 0 && ageMillis >= 2 * threshold) { // The self link is a very descriptive way to reference the cluster. - orphaned.push(c.selfLink) + orphaned.push(c.selfLink); } } - await Promise.allSettled(promises) - return orphaned + await Promise.allSettled(promises); + return orphaned; } async listClusters() { const [response] = await this.client.listClusters({ projectId: await this.getProjectId(), zone: this.zone, - }) - return response.clusters + }); + return response.clusters; } // Create the supplied cluster. This method will automatically add labels to mark the cluster as // having been created by this action and it will provide a default lifespan label of 30 minutes. async createCluster(cluster) { if (typeof cluster.resourceLabels === typeof undefined) { - cluster.resourceLabels = {} + cluster.resourceLabels = {}; } if (typeof cluster.resourceLabels[CATEGORY_PROPERTY] === typeof undefined) { - cluster.resourceLabels[CATEGORY_PROPERTY] = EPHEMERAL + cluster.resourceLabels[CATEGORY_PROPERTY] = EPHEMERAL; } if (typeof cluster.resourceLabels[LIFESPAN_PROPERTY] === typeof undefined) { - cluster.resourceLabels[LIFESPAN_PROPERTY] = DEFAULT_LIFESPAN + cluster.resourceLabels[LIFESPAN_PROPERTY] = DEFAULT_LIFESPAN; } - const [operation] = await this.client.createCluster({parent: await this.getLocation(), cluster: cluster}) - return this.awaitOperation(operation) + const [operation] = await this.client.createCluster({ + parent: await this.getLocation(), + cluster: cluster, + }); + return this.awaitOperation(operation); } // Delete the given cluster. This method will throw an exception if the supplied cluster does not // have the appropriate labels that indicate the cluster was created by this github action. Pass // in force=true to override this check. - async deleteCluster(cluster, force=false) { - let name = cluster.name + async deleteCluster(cluster, force = false) { + let name = cluster.name; if (!force && cluster.resourceLabels[CATEGORY_PROPERTY] !== EPHEMERAL) { - return new Operation(false, `Cannot delete cluster ${name}, it is not ephemeral.`) + return new Operation( + false, + `Cannot delete cluster ${name}, it is not ephemeral.` + ); } try { - const [op] = await this.client.deleteCluster({name: `${await this.getLocation()}/clusters/${name}`}) - return Operation.wrap(op) + const [op] = await this.client.deleteCluster({ + name: `${await this.getLocation()}/clusters/${name}`, + }); + return Operation.wrap(op); } catch (error) { - return new Operation(false, `Error deleting cluster: ${error}`) + return new Operation(false, `Error deleting cluster: ${error}`); } } @@ -193,107 +213,117 @@ class Client { // cluster if they are not present and set to the correct value. We are expecting that only // clusters provisioned by this action will have those labels. async maybeExpireCluster(cluster, lifespanOverride) { - let labels = cluster.resourceLabels + let labels = cluster.resourceLabels; - let category = labels[CATEGORY_PROPERTY] + let category = labels[CATEGORY_PROPERTY]; if (category !== EPHEMERAL) { - console.log(`Ignoring cluster ${cluster.name} because it has not ephemeral.`) - return + console.log( + `Ignoring cluster ${cluster.name} because it has not ephemeral.` + ); + return; } - let lifespanMillis = clusterLifespanMillis(cluster) + let lifespanMillis = clusterLifespanMillis(cluster); if (typeof lifespanOverride !== typeof undefined) { - lifespanMillis = lifespanMillis(lifespanOverride) + lifespanMillis = lifespanMillis(lifespanOverride); } if (lifespanMillis <= 0) { - console.log(`Keeping ${cluster.name} because the lifespan is <= 0.`) - return + console.log(`Keeping ${cluster.name} because the lifespan is <= 0.`); + return; } - const ageMillis = clusterAgeMillis(cluster) + const ageMillis = clusterAgeMillis(cluster); if (ageMillis < lifespanMillis) { - console.log(`Keeping ${cluster.name} because ${ageMillis/1000}s < ${lifespanMillis/1000}s.`) - return + console.log( + `Keeping ${cluster.name} because ${ageMillis / 1000}s < ${ + lifespanMillis / 1000 + }s.` + ); + return; } - console.log(`Deleting ${cluster.name} because ${ageMillis/1000}s >= ${lifespanMillis/1000}s.`) - let op = await this.deleteCluster(cluster) - console.log(op.status) + console.log( + `Deleting ${cluster.name} because ${ageMillis / 1000}s >= ${ + lifespanMillis / 1000 + }s.` + ); + let op = await this.deleteCluster(cluster); + console.log(op.status); } // Wait for the supplied operation to finish by polling up to limit times. async awaitOperation(operation) { - return utils.fibonacciRetry(async ()=>{ - const op = await this.getOperation(operation) + return utils.fibonacciRetry(async () => { + const op = await this.getOperation(operation); if (op.done) { - return op + return op; } else { - throw new utils.Transient(op.status) + throw new utils.Transient(op.status); } - }) + }); } // Get the current status of the supplied operation. async getOperation(operation) { - let location = await this.getLocation() - let opId = `${location}/operations/${operation.name}` - const [op] = await this.client.getOperation({name: opId}) - return Operation.wrap(op) + let location = await this.getLocation(); + let opId = `${location}/operations/${operation.name}`; + const [op] = await this.client.getOperation({ name: opId }); + return Operation.wrap(op); } - } function clusterAgeMillis(cluster) { - return Date.now() - Date.parse(cluster.createTime) + return Date.now() - Date.parse(cluster.createTime); } function clusterLifespanMillis(cluster) { - let labels = cluster.resourceLabels - return lifespanMillis(labels[LIFESPAN_PROPERTY]) + let labels = cluster.resourceLabels; + return lifespanMillis(labels[LIFESPAN_PROPERTY]); } function lifespanMillis(lifespan) { if (typeof lifespan === typeof undefined) { - lifespan = 0 + lifespan = 0; } else if (typeof lifespan === typeof "") { if (lifespan === "") { - lifespan = 0 + lifespan = 0; } else { - lifespan = Number(lifespan) + lifespan = Number(lifespan); } } - return lifespan * 1000 + return lifespan * 1000; } // The Operation object is used to report the status of a long running procedures. class Operation { - // Wrap the google supplied operation class and convert it into something simpler. static wrap(op) { - const done = op.status == STATUS_ENUM[STATUS_ENUM.DONE] + const done = op.status == STATUS_ENUM[STATUS_ENUM.DONE]; - let url = op.targetLink - let idx = url.lastIndexOf('/') - let name = url + let url = op.targetLink; + let idx = url.lastIndexOf("/"); + let name = url; if (idx >= 0) { - name = url.substring(idx+1) + name = url.substring(idx + 1); } if (op.detail) { - return new Operation(done, `${op.operationType} ${name} ${op.status}: ${op.detail}`) + return new Operation( + done, + `${op.operationType} ${name} ${op.status}: ${op.detail}` + ); } else { - return new Operation(done, `${op.operationType} ${name} ${op.status}`) + return new Operation(done, `${op.operationType} ${name} ${op.status}`); } } constructor(done, status) { - this.done = done - this.status = status + this.done = done; + this.status = status; } - } module.exports = { - Client -} + Client, +}; diff --git a/.github/actions/provision-cluster/lib/gke.test.js b/.github/actions/provision-cluster/lib/gke.test.js index 806472e2..0003c1d9 100644 --- a/.github/actions/provision-cluster/lib/gke.test.js +++ b/.github/actions/provision-cluster/lib/gke.test.js @@ -1,8 +1,8 @@ -'use strict'; +"use strict"; -const common = require('./common_test.js') -const mock = require('./mock.js') +const common = require("./common_test.js"); +const mock = require("./mock.js"); -test('gke mock e2e', async ()=> { - await common.lifecycle(mock.Client()) -}) +test("gke mock e2e", async () => { + await common.lifecycle(mock.Client()); +}); diff --git a/.github/actions/provision-cluster/lib/kubeception.js b/.github/actions/provision-cluster/lib/kubeception.js index 771fa7fb..2250b9d4 100644 --- a/.github/actions/provision-cluster/lib/kubeception.js +++ b/.github/actions/provision-cluster/lib/kubeception.js @@ -1,67 +1,75 @@ -'use strict'; +"use strict"; -const core = require('@actions/core') -const httpClient = require('@actions/http-client') -const httpClientLib = require('@actions/http-client/lib/auth.js') -const utils = require('./utils.js') -const yaml = require('yaml') +const core = require("@actions/core"); +const httpClient = require("@actions/http-client"); +const httpClientLib = require("@actions/http-client/lib/auth.js"); +const utils = require("./utils.js"); +const yaml = require("yaml"); -const MAX_KLUSTER_NAME_LEN = 63 -const defaultLifespan = 60*60 // One hour worth of seconds +const MAX_KLUSTER_NAME_LEN = 63; +const defaultLifespan = 60 * 60; // One hour worth of seconds class Client { - constructor(client) { - this.client = client + this.client = client; } async allocateCluster(version, lifespan) { - const clusterName = utils.getUniqueClusterName(MAX_KLUSTER_NAME_LEN) - const kubeConfig = await this.createKluster(clusterName, version, lifespan) + const clusterName = utils.getUniqueClusterName(MAX_KLUSTER_NAME_LEN); + const kubeConfig = await this.createKluster(clusterName, version, lifespan); return { - "name": clusterName, - "config": kubeConfig - } + name: clusterName, + config: kubeConfig, + }; } async makeKubeconfig(cluster) { - return yaml.parse(cluster.config) + return yaml.parse(cluster.config); } async getCluster(clusterName) { - return clusterName + return clusterName; } async deleteCluster(clusterName) { - return this.deleteKluster(clusterName) + return this.deleteKluster(clusterName); } async expireClusters() { // Kubeception automatically expires klusters, no client side expiration is required. - return [] + return []; } async createKluster(name, version, lifespan) { if (!name) { - throw new Error('Function createKluster() needs a Kluster name') + throw new Error("Function createKluster() needs a Kluster name"); } if (!version) { - throw Error('Function createKluster() needs a Kluster version') + throw Error("Function createKluster() needs a Kluster version"); } - if (typeof lifespan === typeof undefined || lifespan === "" || lifespan === 0) { - lifespan = defaultLifespan + if ( + typeof lifespan === typeof undefined || + lifespan === "" || + lifespan === 0 + ) { + lifespan = defaultLifespan; } - const kubeceptionToken = core.getInput('kubeceptionToken') + const kubeceptionToken = core.getInput("kubeceptionToken"); if (!kubeceptionToken) { - throw Error(`kubeceptionToken is missing. Make sure that input parameter kubeceptionToken was provided`) + throw Error( + `kubeceptionToken is missing. Make sure that input parameter kubeceptionToken was provided` + ); } - let kubeceptionProfile = core.getInput('kubeceptionProfile') - if (typeof kubeceptionProfile !== typeof "" || kubeceptionProfile.trim() === "") { - kubeceptionProfile = "default" + let kubeceptionProfile = core.getInput("kubeceptionProfile"); + if ( + typeof kubeceptionProfile !== typeof "" || + kubeceptionProfile.trim() === "" + ) { + kubeceptionProfile = "default"; } return utils.fibonacciRetry(async () => { @@ -96,35 +104,43 @@ class Client { async deleteKluster(name) { if (!name) { - throw Error('Function deleteKluster() needs a Kluster name') + throw Error("Function deleteKluster() needs a Kluster name"); } - const response = await this.client.del(`https://sw.bakerstreet.io/kubeception/api/klusters/${name}`) + const response = await this.client.del( + `https://sw.bakerstreet.io/kubeception/api/klusters/${name}` + ); if (!response || !response.message) { - throw Error("Unknown error getting response") + throw Error("Unknown error getting response"); } if (response.message.statusCode == 200) { return { done: true, - status: "deleted" - } + status: "deleted", + }; } else { - throw Error(`Expected status code 200 but got ${response.message.statusCode}`) + throw Error( + `Expected status code 200 but got ${response.message.statusCode}` + ); } } } function getHttpClient() { - const userAgent = 'datawire/provision-cluster' + const userAgent = "datawire/provision-cluster"; - const kubeceptionToken = core.getInput('kubeceptionToken') + const kubeceptionToken = core.getInput("kubeceptionToken"); if (!kubeceptionToken) { - throw Error(`kubeceptionToken is missing. Make sure that input parameter kubeceptionToken was provided`) + throw Error( + `kubeceptionToken is missing. Make sure that input parameter kubeceptionToken was provided` + ); } - const credentialHandler = new httpClientLib.BearerCredentialHandler(kubeceptionToken) - return new httpClient.HttpClient(userAgent, [credentialHandler]) + const credentialHandler = new httpClientLib.BearerCredentialHandler( + kubeceptionToken + ); + return new httpClient.HttpClient(userAgent, [credentialHandler]); } -module.exports = { Client, getHttpClient } +module.exports = { Client, getHttpClient }; diff --git a/.github/actions/provision-cluster/lib/kubeception.test.js b/.github/actions/provision-cluster/lib/kubeception.test.js index c582adb2..3310132e 100644 --- a/.github/actions/provision-cluster/lib/kubeception.test.js +++ b/.github/actions/provision-cluster/lib/kubeception.test.js @@ -1,75 +1,90 @@ -'use strict'; +"use strict"; -const core = require('@actions/core') -const kubeception = require('./kubeception.js') -const common = require('./common_test.js') -const mock = require('./mock.js') -const MOCK = mock.MOCK -const cluster = mock.cluster -const URL = require('url').URL +const core = require("@actions/core"); +const kubeception = require("./kubeception.js"); +const common = require("./common_test.js"); +const mock = require("./mock.js"); +const MOCK = mock.MOCK; +const cluster = mock.cluster; +const URL = require("url").URL; -test('kubeception profile', async ()=>{ +test("kubeception profile", async () => { let inputs = { - kubeceptionToken: 'mock-kube-token', - kubeceptionProfile: 'mock-profile' - } + kubeceptionToken: "mock-kube-token", + kubeceptionProfile: "mock-profile", + }; let count = 0; class MockHttpClient { async put(url) { - let parsed = new URL(url) - expect(parsed.searchParams.get('profile')).toBe(inputs.kubeceptionProfile) + let parsed = new URL(url); + expect(parsed.searchParams.get("profile")).toBe( + inputs.kubeceptionProfile + ); - let status = 200 + let status = 200; if (count < 2) { - status = 425 - count = count + 1 + status = 425; + count = count + 1; } else { - status = 200 + status = 200; } return { message: { statusCode: status, }, - readBody: ()=>{ + readBody: () => { return JSON.stringify({ - "apiVersion": "v1", - "kind":"Config", - "clusters": [{ - "cluster": { - "certificate-authority-data": cluster.masterAuth.clusterCaCertificate, - "server":"https://34.172.65.239" + apiVersion: "v1", + kind: "Config", + clusters: [ + { + cluster: { + "certificate-authority-data": + cluster.masterAuth.clusterCaCertificate, + server: "https://34.172.65.239", + }, + name: "gke-cluster", }, - "name":"gke-cluster" - }], - "users": [{ - "name":"gke-user", - "user":{ - "token":MOCK.ACCESS_TOKEN - }}], - "contexts": [{ - "context":{"cluster":"gke-cluster","namespace":"default","user":"gke-user"}, - "name":"gke-context"}], - "current-context":"gke-context" - }) - } - } + ], + users: [ + { + name: "gke-user", + user: { + token: MOCK.ACCESS_TOKEN, + }, + }, + ], + contexts: [ + { + context: { + cluster: "gke-cluster", + namespace: "default", + user: "gke-user", + }, + name: "gke-context", + }, + ], + "current-context": "gke-context", + }); + }, + }; } async del() { return { message: { - statusCode: 200 - } - } + statusCode: 200, + }, + }; } } - process.env.GITHUB_REPOSITORY = 'test-project-repo' - process.env.GITHUB_HEAD_REF = 'refs/pull/1234' - process.env.GITHUB_SHA = 'abc1234' - core.getInput = (name)=>{ - return inputs[name] - } - await common.lifecycle(new kubeception.Client(new MockHttpClient())) -}) + process.env.GITHUB_REPOSITORY = "test-project-repo"; + process.env.GITHUB_HEAD_REF = "refs/pull/1234"; + process.env.GITHUB_SHA = "abc1234"; + core.getInput = (name) => { + return inputs[name]; + }; + await common.lifecycle(new kubeception.Client(new MockHttpClient())); +}); diff --git a/.github/actions/provision-cluster/lib/mock.js b/.github/actions/provision-cluster/lib/mock.js index 28b0f61f..461bfc1f 100644 --- a/.github/actions/provision-cluster/lib/mock.js +++ b/.github/actions/provision-cluster/lib/mock.js @@ -1,73 +1,73 @@ -'use strict'; +"use strict"; -const gke = require('./gke.js') -const fs = require('fs') -const path = require('path') +const gke = require("./gke.js"); +const fs = require("fs"); +const path = require("path"); -const container = require('@google-cloud/container') -const STATUS_ENUM = container.protos.google.container.v1.Operation.Status +const container = require("@google-cloud/container"); +const STATUS_ENUM = container.protos.google.container.v1.Operation.Status; const MOCK = { - PROJECT_ID: 'mock-project-id', - ACCESS_TOKEN: 'mock-access-token', - ZONE: 'mock-zone', - OPERATION_NAME: 'mock-operation-name', - OPERATION_TARGET_LINK: 'https://mock/mock-cluster' -} + PROJECT_ID: "mock-project-id", + ACCESS_TOKEN: "mock-access-token", + ZONE: "mock-zone", + OPERATION_NAME: "mock-operation-name", + OPERATION_TARGET_LINK: "https://mock/mock-cluster", +}; -const cluster = JSON.parse(fs.readFileSync(path.join(__dirname, 'cluster.json'))) +const cluster = JSON.parse( + fs.readFileSync(path.join(__dirname, "cluster.json")) +); class MockGKE { constructor() { - this.auth = new MockAuth() + this.auth = new MockAuth(); } async getProjectId() { - return MOCK.PROJECT_ID + return MOCK.PROJECT_ID; } async createCluster() { - return [new MockOp()] + return [new MockOp()]; } async deleteCluster() { - return [new MockOp()] + return [new MockOp()]; } async getCluster() { - return [cluster] + return [cluster]; } async getOperation() { - return [new MockOp()] + return [new MockOp()]; } - } class MockAuth { getAccessToken() { - return MOCK.ACCESS_TOKEN + return MOCK.ACCESS_TOKEN; } - } class MockOp { constructor() { - this.name = MOCK.OPERATION_NAME - this.targetLink = cluster.selfLink - this.status = STATUS_ENUM[STATUS_ENUM.DONE] + this.name = MOCK.OPERATION_NAME; + this.targetLink = cluster.selfLink; + this.status = STATUS_ENUM[STATUS_ENUM.DONE]; } } // Return a client but with a mocked GKE client underneath for testing purposes. function Client() { - let client = new gke.Client(MOCK.ZONE, new MockGKE()) - return client + let client = new gke.Client(MOCK.ZONE, new MockGKE()); + return client; } module.exports = { MOCK, Client, MockGKE, - cluster -} + cluster, +}; diff --git a/.github/actions/provision-cluster/lib/slack.js b/.github/actions/provision-cluster/lib/slack.js index 991a4bdf..065cc315 100644 --- a/.github/actions/provision-cluster/lib/slack.js +++ b/.github/actions/provision-cluster/lib/slack.js @@ -1,33 +1,33 @@ -'use strict'; +"use strict"; -const core = require('@actions/core') -const http = require('@actions/http-client') +const core = require("@actions/core"); +const http = require("@actions/http-client"); async function notify(message) { - const slackWebhook = core.getInput('slackWebhook') + const slackWebhook = core.getInput("slackWebhook"); if (typeof slackWebhook === typeof undefined || slackWebhook === "") { - return + return; } - const channel = core.getInput('slackChannel') - const runbook = core.getInput('slackRunbook') - const username = core.getInput('slackUsername') + const channel = core.getInput("slackChannel"); + const runbook = core.getInput("slackRunbook"); + const username = core.getInput("slackUsername"); - const client = new http.HttpClient('datawire/provision-cluster') + const client = new http.HttpClient("datawire/provision-cluster"); const body = { channel: channel, username: username, text: `${message}\n\nSee runbook: ${runbook}`, - icon_emoji: `:kubernetes:` - } + icon_emoji: `:kubernetes:`, + }; - const result = await client.postJson(slackWebhook, body) + const result = await client.postJson(slackWebhook, body); if (result.statusCode != 200) { - throw new Error(`Status ${result.statusCode} posting to slack: ${result}`) + throw new Error(`Status ${result.statusCode} posting to slack: ${result}`); } } module.exports = { - notify -} + notify, +}; diff --git a/.github/actions/provision-cluster/lib/utils.js b/.github/actions/provision-cluster/lib/utils.js index e64e44f5..11b8a822 100644 --- a/.github/actions/provision-cluster/lib/utils.js +++ b/.github/actions/provision-cluster/lib/utils.js @@ -1,57 +1,61 @@ -'use strict'; +"use strict"; -const fs = require('fs') -const crypto = require('crypto') -const core = require('@actions/core') +const fs = require("fs"); +const crypto = require("crypto"); +const core = require("@actions/core"); function getUniqueClusterName(maxNameLength) { - const repoName = process.env['GITHUB_REPOSITORY'].replace(/^.*\//, '') - const branch = process.env['GITHUB_HEAD_REF'] - const sha = process.env['GITHUB_SHA'].substring(0, 8) + const repoName = process.env["GITHUB_REPOSITORY"].replace(/^.*\//, ""); + const branch = process.env["GITHUB_HEAD_REF"]; + const sha = process.env["GITHUB_SHA"].substring(0, 8); - let name = `ci-${uid()}-${repoName}-${sha}-${branch}` - let sanitizedName = name.replace(/[^A-Za-z0-9-]/g, '-').replace(/-+$/g, '').toLowerCase().substring(0, maxNameLength) + let name = `ci-${uid()}-${repoName}-${sha}-${branch}`; + let sanitizedName = name + .replace(/[^A-Za-z0-9-]/g, "-") + .replace(/-+$/g, "") + .toLowerCase() + .substring(0, maxNameLength); if (sanitizedName.endsWith("-")) { - sanitizedName = sanitizedName.substring(0, sanitizedName.length - 1) + sanitizedName = sanitizedName.substring(0, sanitizedName.length - 1); } - return sanitizedName + return sanitizedName; } function writeFile(path, contents) { - fs.writeFile(path, contents, err => { + fs.writeFile(path, contents, (err) => { if (err) { - core.setFailed(`${err}`) + core.setFailed(`${err}`); } - }) + }); } // Convenience for sleeping in async functions/methods. function sleep(ms) { - return new Promise(resolve => setTimeout(resolve, ms)) + return new Promise((resolve) => setTimeout(resolve, ms)); } // Construct a thunk that returns a fibonacci sequence using the supplied initial and max delays. function fibonacciDelaySequence(initialDelay, maxDelay) { - let prevFibonacciDelay = 0 - let curFibonacciDelay = initialDelay + let prevFibonacciDelay = 0; + let curFibonacciDelay = initialDelay; return () => { - const result = curFibonacciDelay + prevFibonacciDelay - prevFibonacciDelay = curFibonacciDelay - curFibonacciDelay = result + const result = curFibonacciDelay + prevFibonacciDelay; + prevFibonacciDelay = curFibonacciDelay; + curFibonacciDelay = result; if (typeof maxDelay === typeof undefined) { - return result + return result; } else { - return Math.min(result, maxDelay) + return Math.min(result, maxDelay); } - } + }; } // Construct a unique id. function uid() { - return crypto.randomBytes(16).toString("hex") + return crypto.randomBytes(16).toString("hex"); } class Retry extends Error {} @@ -62,9 +66,14 @@ class Transient extends Error {} // passed. Use minDelay and maxDelay to tune the delay times. The action should signal retry with // `throw new Transient(...)` or `throw new Retry(...)`, and return upon success. // The result of the final successful invocation will be returned. -async function fibonacciRetry(action, timeout=600000, minDelay=1000, maxDelay=30000) { - let start = Date.now() - let nextDelay = fibonacciDelaySequence(minDelay, maxDelay) +async function fibonacciRetry( + action, + timeout = 600000, + minDelay = 1000, + maxDelay = 30000 +) { + let start = Date.now(); + let nextDelay = fibonacciDelaySequence(minDelay, maxDelay); let count = 0; let timeoutReached = false; @@ -73,27 +82,40 @@ async function fibonacciRetry(action, timeout=600000, minDelay=1000, maxDelay=30 count++; try { - return await action() + return await action(); } catch (e) { if (!(e instanceof Transient) && !(e instanceof Retry)) { - throw e + throw e; } - let delay = nextDelay() - let elapsed = Date.now() - start - let remaining = timeout - elapsed + let delay = nextDelay(); + let elapsed = Date.now() - start; + let remaining = timeout - elapsed; if (remaining > 0) { - let t = Math.min(delay, remaining) - core.info(`Error (${e.message}) retrying after ${t/1000}s ...`) - await sleep(t) + let t = Math.min(delay, remaining); + core.info(`Error (${e.message}) retrying after ${t / 1000}s ...`); + await sleep(t); } else { timeoutReached = true; } if (timeoutReached) { - throw new Error(`Error (${e.message}) failing after ${count} attempts over ${elapsed/1000}s.`) + throw new Error( + `Error (${e.message}) failing after ${count} attempts over ${ + elapsed / 1000 + }s.` + ); } } } while (!timeoutReached); } -module.exports = { getUniqueClusterName, writeFile, sleep, fibonacciDelaySequence, uid, Retry, Transient, fibonacciRetry } +module.exports = { + getUniqueClusterName, + writeFile, + sleep, + fibonacciDelaySequence, + uid, + Retry, + Transient, + fibonacciRetry, +}; diff --git a/.github/actions/provision-cluster/lib/utils.test.js b/.github/actions/provision-cluster/lib/utils.test.js index f7408892..e6d78842 100644 --- a/.github/actions/provision-cluster/lib/utils.test.js +++ b/.github/actions/provision-cluster/lib/utils.test.js @@ -1,125 +1,143 @@ -'use strict'; +"use strict"; -const utils = require('./utils.js') +const utils = require("./utils.js"); -test('fibonacciDelaySequence unlimited', () => { - let seq = utils.fibonacciDelaySequence(1) - let prev = 0 - let cur = 1 +test("fibonacciDelaySequence unlimited", () => { + let seq = utils.fibonacciDelaySequence(1); + let prev = 0; + let cur = 1; for (let i = 0; i < 100; i++) { - let next = cur + prev - prev = cur - cur = next - expect(seq()).toBe(cur) + let next = cur + prev; + prev = cur; + cur = next; + expect(seq()).toBe(cur); } -}) +}); -test('fibonacciDelaySequence limited', () => { - let limit = 10 - let seq = utils.fibonacciDelaySequence(1, limit) - let prev = 0 - let cur = 1 +test("fibonacciDelaySequence limited", () => { + let limit = 10; + let seq = utils.fibonacciDelaySequence(1, limit); + let prev = 0; + let cur = 1; for (let i = 0; i < 100; i++) { - let next = cur + prev - prev = cur - cur = next + let next = cur + prev; + prev = cur; + cur = next; if (cur > limit) { - expect(seq()).toBe(limit) + expect(seq()).toBe(limit); } else { - expect(seq()).toBe(cur) + expect(seq()).toBe(cur); } } -}) +}); -test('uid', () => { - let ids = new Set() +test("uid", () => { + let ids = new Set(); for (let i = 0; i < 100; i++) { - let id = utils.uid() - expect(ids.has(id)).toBeFalsy() - ids.add(id) + let id = utils.uid(); + expect(ids.has(id)).toBeFalsy(); + ids.add(id); } -}) +}); -test('fibonacciRetry success', async () => { - let count = 0 - let result = await utils.fibonacciRetry(async ()=> { - count += 1 - return count - }) - expect(result).toBe(count) - expect(count).toBe(1) -}) +test("fibonacciRetry success", async () => { + let count = 0; + let result = await utils.fibonacciRetry(async () => { + count += 1; + return count; + }); + expect(result).toBe(count); + expect(count).toBe(1); +}); -test('fibonacciRetry fail some', async () => { - let count = 0 - let result = await utils.fibonacciRetry(async ()=> { - count += 1 - if (count > 3) { - return count - } - throw new utils.Transient(`${count} is not big enough`) - }, 100, 1) - expect(count).toBe(result) - expect(count).toBe(4) -}) +test("fibonacciRetry fail some", async () => { + let count = 0; + let result = await utils.fibonacciRetry( + async () => { + count += 1; + if (count > 3) { + return count; + } + throw new utils.Transient(`${count} is not big enough`); + }, + 100, + 1 + ); + expect(count).toBe(result); + expect(count).toBe(4); +}); -test('fibonacciRetry fail all', async () => { - let count = 0 - let start = Date.now() - let returned = false +test("fibonacciRetry fail all", async () => { + let count = 0; + let start = Date.now(); + let returned = false; try { - await utils.fibonacciRetry(async ()=> { - count += 1 - throw new utils.Transient('never big enough') - }, 100, 1) - returned = true + await utils.fibonacciRetry( + async () => { + count += 1; + throw new utils.Transient("never big enough"); + }, + 100, + 1 + ); + returned = true; } catch (err) { - let elapsed = Date.now() - start - expect(err.message).toContain("Error") - expect(err.message).toContain("never big enough") - expect(err.message).toContain("failing after") - expect(err.message).toContain("attempts over") - expect(count > 0).toBeTruthy() - expect(elapsed < 1000).toBeTruthy() + let elapsed = Date.now() - start; + expect(err.message).toContain("Error"); + expect(err.message).toContain("never big enough"); + expect(err.message).toContain("failing after"); + expect(err.message).toContain("attempts over"); + expect(count > 0).toBeTruthy(); + expect(elapsed < 1000).toBeTruthy(); } - expect(returned).toBeFalsy() -}) + expect(returned).toBeFalsy(); +}); -test('fibonacciRetry max delay', async () => { - let count = 0 - await utils.fibonacciRetry(async ()=> { - count += 1 - if (count > 10) { - return count - } - throw new utils.Transient('never big enough') - }, 100, 1, 10) -}) +test("fibonacciRetry max delay", async () => { + let count = 0; + await utils.fibonacciRetry( + async () => { + count += 1; + if (count > 10) { + return count; + } + throw new utils.Transient("never big enough"); + }, + 100, + 1, + 10 + ); +}); -test('fibonacciRetry error', async () => { - let count = 0 - let returned = false +test("fibonacciRetry error", async () => { + let count = 0; + let returned = false; try { - await utils.fibonacciRetry(async ()=> { - count += 1 - throw new Error('blah') - }, 100, 1, 10) - returned = true + await utils.fibonacciRetry( + async () => { + count += 1; + throw new Error("blah"); + }, + 100, + 1, + 10 + ); + returned = true; } catch (err) { - expect(err.message).toEqual('blah') - expect(count).toBe(1) + expect(err.message).toEqual("blah"); + expect(count).toBe(1); } - expect(returned).toBeFalsy() -}) + expect(returned).toBeFalsy(); +}); -test('getUniqueClusterName', () => { - process.env["GITHUB_REPOSITORY"] = "repo" - process.env["GITHUB_HEAD_REF"] = "head-ref" - process.env["GITHUB_SHA"] = "1234" +test("getUniqueClusterName", () => { + process.env["GITHUB_REPOSITORY"] = "repo"; + process.env["GITHUB_HEAD_REF"] = "head-ref"; + process.env["GITHUB_SHA"] = "1234"; for (let i = 10; i < 1000; i++) { - let name = utils.getUniqueClusterName(i) - expect(name.length <= i).toBeTruthy() - expect(name.endsWith("-")).toBeFalsy() + let name = utils.getUniqueClusterName(i); + expect(name.length <= i).toBeTruthy(); + expect(name.endsWith("-")).toBeFalsy(); } -}) +});