diff --git a/action.yml b/action.yml index 2f877376..8c656ae2 100644 --- a/action.yml +++ b/action.yml @@ -2,6 +2,10 @@ name: 'Kubernetes set context' description: 'Kubernetes set context' inputs: # Used for setting the target K8s cluster context which will be used by other actions like azure/k8s-actions/k8s-deploy or azure/k8s-actions/k8s-create-secret + method: + description: 'Acceptable values: kubeconfig or service-account' + required: true + default: 'kubeconfig' kubeconfig: description: 'Kubernetes Config' required: false @@ -16,7 +20,7 @@ inputs: required: false default: '' k8s-secret: - description: 'Service account token' + description: 'Service account secret. Run kubectl get serviceaccounts -o yaml and copy the service-account-secret-name. Copy the ouptut of kubectl get secret -o yaml' required: false default: '' diff --git a/lib/login.js b/lib/login.js index 0b896d39..1dfc2210 100644 --- a/lib/login.js +++ b/lib/login.js @@ -1,9 +1,10 @@ "use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; @@ -16,34 +17,59 @@ const io = require("@actions/io"); const toolCache = require("@actions/tool-cache"); const os = require("os"); const toolrunner_1 = require("@actions/exec/lib/toolrunner"); +const jsyaml = require("js-yaml"); +const util = require("util"); function getKubeconfig() { - const kubeconfig = core.getInput('kubeconfig'); - if (kubeconfig) { + const method = core.getInput('method', { required: true }); + if (method == 'kubeconfig') { + const kubeconfig = core.getInput('kubeconfig', { required: true }); core.debug("Setting context using kubeconfig"); return kubeconfig; } - const clusterUrl = core.getInput('k8s-url', { required: true }); - core.debug("Found clusterUrl, creating kubeconfig using certificate and token"); - let token = Buffer.from(core.getInput('k8s-secret'), 'base64').toString(); - const kubeconfigObject = { - "apiVersion": "v1", - "kind": "Config", - "clusters": [ - { - "cluster": { - "server": clusterUrl + else if (method == 'service-account') { + const clusterUrl = core.getInput('k8s-url', { required: true }); + core.debug("Found clusterUrl, creating kubeconfig using certificate and token"); + let k8sSecret = core.getInput('k8s-secret', { required: true }); + var parsedk8sSecret = jsyaml.safeLoad(k8sSecret); + let kubernetesServiceAccountSecretFieldNotPresent = 'The service acount secret yaml does not contain %s; field. Make sure that its present and try again.'; + if (!parsedk8sSecret) { + throw Error("The service account secret yaml specified is invalid. Make sure that its a valid yaml and try again."); + } + if (!parsedk8sSecret.data) { + throw Error(util.format(kubernetesServiceAccountSecretFieldNotPresent, "data")); + } + if (!parsedk8sSecret.data.token) { + throw Error(util.format(kubernetesServiceAccountSecretFieldNotPresent, "data.token")); + } + if (!parsedk8sSecret.data["ca.crt"]) { + throw Error(util.format(kubernetesServiceAccountSecretFieldNotPresent, "data[ca.crt]")); + } + const certAuth = parsedk8sSecret.data["ca.crt"]; + const token = Buffer.from(parsedk8sSecret.data.token, 'base64').toString(); + const kubeconfigObject = { + "apiVersion": "v1", + "kind": "Config", + "clusters": [ + { + "cluster": { + "certificate-authority-data": certAuth, + "server": clusterUrl + } } - } - ], - "users": [ - { - "user": { - "token": token + ], + "users": [ + { + "user": { + "token": token + } } - } - ] - }; - return JSON.stringify(kubeconfigObject); + ] + }; + return JSON.stringify(kubeconfigObject); + } + else { + throw Error("Invalid method specified. Acceptable values are kubeconfig and service-account."); + } } function getExecutableExtension() { if (os.type().match(/^Win/)) { diff --git a/package-lock.json b/package-lock.json index 79a6b117..147048e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,11 +38,38 @@ "integrity": "sha512-W0+n1Y+gK/8G2P/piTkBBN38Qc5Q1ZSO6B5H3QmPCUewaiXOo2GCAWZ4ElZCcNhjJuBSUSLGFUJnmlCn5+nxOQ==", "dev": true }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, "tunnel": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.4.tgz", @@ -57,6 +84,12 @@ "underscore": "1.8.3" } }, + "typescript": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.2.tgz", + "integrity": "sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==", + "dev": true + }, "underscore": { "version": "1.8.3", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", diff --git a/package.json b/package.json index 36b9aac0..9c1f4bef 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,11 @@ "dependencies": { "@actions/core": "^1.0.0", "@actions/exec": "^1.0.0", - "@actions/tool-cache": "^1.0.0" + "@actions/tool-cache": "^1.0.0", + "js-yaml": "^3.13.1" }, "devDependencies": { - "@types/node": "^12.0.4" + "@types/node": "^12.0.4", + "typescript": "^3.5.2" } } diff --git a/src/login.ts b/src/login.ts index 11ce17aa..ea88d437 100644 --- a/src/login.ts +++ b/src/login.ts @@ -6,36 +6,65 @@ import * as io from '@actions/io'; import * as toolCache from '@actions/tool-cache'; import * as os from 'os'; import { ToolRunner } from "@actions/exec/lib/toolrunner"; +import * as jsyaml from 'js-yaml'; +import * as util from 'util'; function getKubeconfig(): string { - const kubeconfig = core.getInput('kubeconfig'); - if (kubeconfig) { + const method = core.getInput('method', {required: true}); + if (method == 'kubeconfig') { + const kubeconfig = core.getInput('kubeconfig', {required : true}); core.debug("Setting context using kubeconfig"); return kubeconfig; } - const clusterUrl = core.getInput('k8s-url', { required: true }); - core.debug("Found clusterUrl, creating kubeconfig using certificate and token"); - let token = Buffer.from(core.getInput('k8s-secret'), 'base64').toString(); - const kubeconfigObject = { - "apiVersion": "v1", - "kind": "Config", - "clusters": [ - { - "cluster": { - "server": clusterUrl - } - } - ], - "users": [ - { - "user": { - "token": token - } - } - ] - }; + else if (method == 'service-account') { + const clusterUrl = core.getInput('k8s-url', { required: true }); + core.debug("Found clusterUrl, creating kubeconfig using certificate and token"); + let k8sSecret = core.getInput('k8s-secret', {required : true}); + var parsedk8sSecret = jsyaml.safeLoad(k8sSecret); + let kubernetesServiceAccountSecretFieldNotPresent = 'The service acount secret yaml does not contain %s; field. Make sure that its present and try again.'; + if (!parsedk8sSecret) { + throw Error("The service account secret yaml specified is invalid. Make sure that its a valid yaml and try again."); + } - return JSON.stringify(kubeconfigObject); + if (!parsedk8sSecret.data) { + throw Error(util.format(kubernetesServiceAccountSecretFieldNotPresent, "data")); + } + + if (!parsedk8sSecret.data.token) { + throw Error(util.format(kubernetesServiceAccountSecretFieldNotPresent, "data.token")); + } + + if (!parsedk8sSecret.data["ca.crt"]) { + throw Error(util.format(kubernetesServiceAccountSecretFieldNotPresent, "data[ca.crt]")); + } + + const certAuth = parsedk8sSecret.data["ca.crt"]; + const token = Buffer.from(parsedk8sSecret.data.token, 'base64').toString(); + const kubeconfigObject = { + "apiVersion": "v1", + "kind": "Config", + "clusters": [ + { + "cluster": { + "certificate-authority-data": certAuth, + "server": clusterUrl + } + } + ], + "users": [ + { + "user": { + "token": token + } + } + ] + }; + + return JSON.stringify(kubeconfigObject); + } + else { + throw Error("Invalid method specified. Acceptable values are kubeconfig and service-account."); + } } function getExecutableExtension(): string {