cherry-picked v1.1 commit

This commit is contained in:
Atharva Mulmuley
2021-05-18 17:57:55 +05:30
parent e8a7e17aad
commit 6e2f9f1d81
8 changed files with 496 additions and 36 deletions

83
src/arc-login.ts Normal file
View File

@ -0,0 +1,83 @@
import * as core from '@actions/core';
import * as path from 'path';
import {spawn} from 'child_process';
import * as fs from 'fs';
import * as io from '@actions/io';
import * as exec from '@actions/exec';
var azPath: string;
const kubeconfig_timeout = 120;//timeout in seconds
export async function getArcKubeconfig(): Promise<string> {
try {
let method = core.getInput('method');
if (method != 'service-account' && method != 'service-principal'){
throw Error("Supported methods for arc cluster are 'service-account' and 'service-principal'.");
}
let resourceGroupName = core.getInput('resource-group');
let clusterName = core.getInput('cluster-name');
if(!resourceGroupName){
throw Error("'resourceGroupName' is not passed for arc cluster.")
}
if(!clusterName){
throw Error("'clusterName' is not passed for arc cluster.")
}
azPath = await io.which("az", true);
await executeAzCliCommand(`account show`, false);
try{
await executeAzCliCommand(`extension remove -n connectedk8s`, false);
}
catch{
//ignore if this causes an error
}
await executeAzCliCommand(`extension add -n connectedk8s`, false);
await executeAzCliCommand(`extension list`, false);
const runnerTempDirectory = process.env['RUNNER_TEMP']; // Using process.env until the core libs are updated
const kubeconfigPath = path.join(runnerTempDirectory, `kubeconfig_${Date.now()}`);
if (method == 'service-account'){
let saToken = core.getInput('token');
if(!saToken){
throw Error("'saToken' is not passed for 'service-account' method.")
}
console.log("using 'service-account' method for authenticating to arc cluster.")
const proc=spawn(azPath,['connectedk8s','proxy','-n',clusterName,'-g',resourceGroupName,'-f',kubeconfigPath,'--token',saToken], {
detached: true,
stdio: 'ignore'
});
proc.unref();
} else{
console.log("using 'service-principal' method for authenticating to arc cluster.")
const proc=spawn(azPath,['connectedk8s','proxy','-n',clusterName,'-g',resourceGroupName,'-f',kubeconfigPath], {
detached: true,
stdio: 'ignore'
});
proc.unref();
}
console.log(`Waiting for ${kubeconfig_timeout} seconds for kubeconfig to be merged....`)
await sleep(kubeconfig_timeout*1000) //sleeping for 2 minutes to allow kubeconfig to be merged
fs.chmodSync(kubeconfigPath, '600');
core.exportVariable('KUBECONFIG', kubeconfigPath);
console.log('KUBECONFIG environment variable is set');
} catch (ex) {
return Promise.reject(ex);
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
async function executeAzCliCommand(
command: string,
silent?: boolean,
execOptions: any = {},
args: any = []) {
execOptions.silent = !!silent;
try {
await exec.exec(`"${azPath}" ${command}`, args, execOptions);
}
catch (error) {
throw new Error(error);
}
}

103
src/client.ts Normal file
View File

@ -0,0 +1,103 @@
import util = require("util");
import fs = require('fs');
import httpClient = require("typed-rest-client/HttpClient");
import * as core from '@actions/core';
var httpCallbackClient = new httpClient.HttpClient('GITHUB_RUNNER', null, {});
export class WebRequest {
public method: string;
public uri: string;
// body can be string or ReadableStream
public body: string | NodeJS.ReadableStream;
public headers: any;
}
export class WebResponse {
public statusCode: number;
public statusMessage: string;
public headers: any;
public body: any;
}
export class WebRequestOptions {
public retriableErrorCodes?: string[];
public retryCount?: number;
public retryIntervalInSeconds?: number;
public retriableStatusCodes?: number[];
public retryRequestTimedout?: boolean;
}
export async function sendRequest(request: WebRequest, options?: WebRequestOptions): Promise<WebResponse> {
let i = 0;
let retryCount = options && options.retryCount ? options.retryCount : 5;
let retryIntervalInSeconds = options && options.retryIntervalInSeconds ? options.retryIntervalInSeconds : 2;
let retriableErrorCodes = options && options.retriableErrorCodes ? options.retriableErrorCodes : ["ETIMEDOUT", "ECONNRESET", "ENOTFOUND", "ESOCKETTIMEDOUT", "ECONNREFUSED", "EHOSTUNREACH", "EPIPE", "EA_AGAIN"];
let retriableStatusCodes = options && options.retriableStatusCodes ? options.retriableStatusCodes : [408, 409, 500, 502, 503, 504];
let timeToWait: number = retryIntervalInSeconds;
while (true) {
try {
if (request.body && typeof (request.body) !== 'string' && !request.body["readable"]) {
request.body = fs.createReadStream(request.body["path"]);
}
let response: WebResponse = await sendRequestInternal(request);
if (retriableStatusCodes.indexOf(response.statusCode) != -1 && ++i < retryCount) {
core.debug(util.format("Encountered a retriable status code: %s. Message: '%s'.", response.statusCode, response.statusMessage));
await sleepFor(timeToWait);
timeToWait = timeToWait * retryIntervalInSeconds + retryIntervalInSeconds;
continue;
}
return response;
}
catch (error) {
if (retriableErrorCodes.indexOf(error.code) != -1 && ++i < retryCount) {
core.debug(util.format("Encountered a retriable error:%s. Message: %s.", error.code, error.message));
await sleepFor(timeToWait);
timeToWait = timeToWait * retryIntervalInSeconds + retryIntervalInSeconds;
}
else {
if (error.code) {
core.debug("error code =" + error.code);
}
throw error;
}
}
}
}
export function sleepFor(sleepDurationInSeconds: number): Promise<any> {
return new Promise((resolve, reject) => {
setTimeout(resolve, sleepDurationInSeconds * 1000);
});
}
async function sendRequestInternal(request: WebRequest): Promise<WebResponse> {
core.debug(util.format("[%s]%s", request.method, request.uri));
var response: httpClient.HttpClientResponse = await httpCallbackClient.request(request.method, request.uri, request.body, request.headers);
return await toWebResponse(response);
}
async function toWebResponse(response: httpClient.HttpClientResponse): Promise<WebResponse> {
var res = new WebResponse();
if (response) {
res.statusCode = response.message.statusCode;
res.statusMessage = response.message.statusMessage;
res.headers = response.message.headers;
var body = await response.readBody();
if (body) {
try {
res.body = JSON.parse(body);
}
catch (error) {
core.debug("Could not parse response: " + JSON.stringify(error));
core.debug("Response: " + JSON.stringify(res.body));
res.body = body;
}
}
}
return res;
}

View File

@ -7,18 +7,19 @@ import * as os from 'os';
import { ToolRunner } from "@actions/exec/lib/toolrunner";
import * as jsyaml from 'js-yaml';
import * as util from 'util';
import { getArcKubeconfig } from './arc-login';
export function getKubeconfig(): string {
const method = core.getInput('method', {required: true});
function getKubeconfig(): string {
const method = core.getInput('method', { required: true });
if (method == 'kubeconfig') {
const kubeconfig = core.getInput('kubeconfig', {required : true});
const kubeconfig = core.getInput('kubeconfig', { required: true });
core.debug("Setting context using kubeconfig");
return kubeconfig;
}
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});
let k8sSecret = core.getInput('k8s-secret', { required: true });
var parsedk8sSecret = jsyaml.safeLoad(k8sSecret);
let kubernetesServiceAccountSecretFieldNotPresent = 'The service account secret yaml does not contain %s; field. Make sure that its present and try again.';
if (!parsedk8sSecret) {
@ -101,16 +102,29 @@ export async function setContext(kubeconfigPath: string) {
}
}
export async function run() {
let kubeconfig = getKubeconfig();
const runnerTempDirectory = process.env['RUNNER_TEMP']; // Using process.env until the core libs are updated
const kubeconfigPath = path.join(runnerTempDirectory, `kubeconfig_${Date.now()}`);
core.debug(`Writing kubeconfig contents to ${kubeconfigPath}`);
fs.writeFileSync(kubeconfigPath, kubeconfig);
fs.chmodSync(kubeconfigPath, '600');
core.exportVariable('KUBECONFIG', kubeconfigPath);
console.log('KUBECONFIG environment variable is set');
await setContext(kubeconfigPath);
async function run() {
try {
let kubeconfig = '';
const cluster_type = core.getInput('cluster-type', { required: true });
if (cluster_type == 'arc') {
await getArcKubeconfig().catch(ex => {
throw new Error('Error: Could not get the KUBECONFIG for arc cluster: ' + ex);
});
}
else {
const runnerTempDirectory = process.env['RUNNER_TEMP']; // Using process.env until the core libs are updated
const kubeconfigPath = path.join(runnerTempDirectory, `kubeconfig_${Date.now()}`);
kubeconfig = getKubeconfig();
core.debug(`Writing kubeconfig contents to ${kubeconfigPath}`);
fs.writeFileSync(kubeconfigPath, kubeconfig);
fs.chmodSync(kubeconfigPath, '600');
core.exportVariable('KUBECONFIG', kubeconfigPath);
console.log('KUBECONFIG environment variable is set');
await setContext(kubeconfigPath);
}
} catch (ex) {
return Promise.reject(ex);
}
}
run().catch(core.setFailed);