For generic secret arguments, from-literal will be parsed into from-file (#6) (#7)

* For generic secret arguments, from-literal will be parsed into from-file

* removed export

* review comments fix

* refractor

* refractor

* Test cases

* TestCases in Typescript

* Undoing package-lock.json

* Removing Tests from lib

* New Test cases

* Bug fix and test cases

* Bug fix and test cases

* Bug fix and test cases

Co-authored-by: rgsubh <rgsubh@github.com>

Co-authored-by: rgsubh <rgsubh@github.com>
This commit is contained in:
rgsubh 2020-06-03 12:15:22 +05:30 коммит произвёл GitHub
Родитель 726f54ce5c
Коммит 05de697464
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
11 изменённых файлов: 326 добавлений и 8 удалений

17
.github/workflows/test.yml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,17 @@
name: "build-test"
on: # rebuild any PRs and main branch changes
pull_request:
push:
branches:
- master
- 'releases/*'
jobs:
build: # make sure build/ci works properly
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- run: |
npm install
npm build
npm test

5
.gitignore поставляемый
Просмотреть файл

@ -24,6 +24,9 @@ bld/
[Oo]bj/
[Ll]og/
# dependencies
/node_modules
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
@ -326,4 +329,4 @@ ASALocalRun/
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
.mfractor/

173
__tests__/run.test.ts Normal file
Просмотреть файл

@ -0,0 +1,173 @@
import { fromLiteralsToFromFile } from "../src/run"
import * as fs from 'fs';
import * as path from 'path';
import { mocked } from 'ts-jest/utils'
const fileUtility = mocked(fs, true);
beforeAll(() => {
process.env['RUNNER_TEMP'] = '/home/runner/work/_temp';
})
test('Literal converted to file', () => {
var filePath = path.join(process.env['RUNNER_TEMP'], "key1");
fileUtility.writeFileSync = jest.fn();
expect(fromLiteralsToFromFile("--from-literal=key1=value")).toBe('--from-file=' + filePath);
expect(fileUtility.writeFileSync).toBeCalledWith(filePath, "value");
})
test('Multiple literal converted to file', () => {
var filePath1 = path.join(process.env['RUNNER_TEMP'], "key1");
var filePath2 = path.join(process.env['RUNNER_TEMP'], "key2");
fileUtility.writeFileSync = jest.fn();
expect(fromLiteralsToFromFile("--from-literal=key1=value1 --from-literal=key2=value2")).toBe('--from-file=' + filePath1 + ' --from-file=' + filePath2);
expect(fileUtility.writeFileSync.mock.calls).toEqual([
[filePath1, "value1"],
[filePath2, "value2"]
])
})
test('File and other argument maintained maintained as-is', () => {
expect(fromLiteralsToFromFile("--from-file=./filepath --otherArgument=value")).toBe('--from-file=./filepath --otherArgument=value')
})
test('File and other argument maintained with trailing spaces maintained as-is', () => {
expect(fromLiteralsToFromFile("--from-file=./filepath --otherArgument=value "))
.toBe('--from-file=./filepath --otherArgument=value')
})
test('File and other argument maintained with trailing spaces maintained as-is', () => {
expect(fromLiteralsToFromFile("--from-file=./filepath 00 --otherArgument=value "))
.toBe('--from-file=./filepath 00 --otherArgument=value')
})
test('File and other argument maintained with trailing spaces maintained as-is', () => {
expect(fromLiteralsToFromFile("--from-file=./filepath 00 --otherArgument=\"value 0\" --otherArgument=value "))
.toBe('--from-file=./filepath 00 --otherArgument=\"value 0\" --otherArgument=value')
})
test('File and other argument maintained maintained as-is', () => {
expect(fromLiteralsToFromFile("--from-file=./filepath --otherArgument=\"value \""))
.toBe('--from-file=./filepath --otherArgument=\"value \"')
})
test('File maintained as-is', () => {
expect(fromLiteralsToFromFile("--from-file=./filepath")).toBe('--from-file=./filepath')
})
test('Any other argument maintained as-is', () => {
expect(fromLiteralsToFromFile("--otherArgument=value")).toBe('--otherArgument=value')
})
test('Any other arguments maintained as-is', () => {
expect(fromLiteralsToFromFile("--otherArgument=value --otherArgument=value")).toBe('--otherArgument=value --otherArgument=value')
})
test('Invalid case, no value for secret', () => {
expect(() => fromLiteralsToFromFile("--from-literal=key")).toThrow(Error);
})
test('Multiple commnads combined', () => {
var filePath = path.join(process.env['RUNNER_TEMP'], "key2");
fileUtility.writeFileSync = jest.fn();
expect(fromLiteralsToFromFile("--from-literal=key2=value --from-file=./filepath --otherArgument=value"))
.toBe('--from-file=' + filePath + ' --from-file=./filepath --otherArgument=value');
expect(fileUtility.writeFileSync).toBeCalledWith(filePath, "value");
})
test('In-valid trailing space in secret is igonered', () => {
var filePath = path.join(process.env['RUNNER_TEMP'], "key2");
fileUtility.writeFileSync = jest.fn();
expect(fromLiteralsToFromFile("--from-literal=key2=value --from-file=./filepath --otherArgument=value"))
.toBe('--from-file=' + filePath + ' --from-file=./filepath --otherArgument=value');
expect(fileUtility.writeFileSync).toBeCalledWith(filePath, "value");
})
test('Missplaced values ignored', () => {
var filePath = path.join(process.env['RUNNER_TEMP'], "key2");
fileUtility.writeFileSync = jest.fn();
expect(fromLiteralsToFromFile("--from-literal=key2=value 0 --from-file=./filepath --otherArgument=value"))
.toBe('--from-file=' + filePath + ' --from-file=./filepath --otherArgument=value');
expect(fileUtility.writeFileSync).toBeCalledWith(filePath, "value");
})
test('Valid Trailing space in secret', () => {
var filePath = path.join(process.env['RUNNER_TEMP'], "key2");
fileUtility.writeFileSync = jest.fn();
expect(fromLiteralsToFromFile("--from-literal=key2=\"value 0\" --from-file=./filepath --otherArgument=value"))
.toBe('--from-file=' + filePath + ' --from-file=./filepath --otherArgument=value');
expect(fileUtility.writeFileSync).toBeCalledWith(filePath, "value 0");
})
test('Valid space in secret Case1', () => {
var filePath = path.join(process.env['RUNNER_TEMP'], "key2");
fileUtility.writeFileSync = jest.fn();
expect(fromLiteralsToFromFile("--from-literal=key2=\"value 023\" --from-file=./filepath --otherArgument=value"))
.toBe('--from-file=' + filePath + ' --from-file=./filepath --otherArgument=value');
expect(fileUtility.writeFileSync).toBeCalledWith(filePath, "value 023");
})
test('Valid space in secret Case2', () => {
var filePath = path.join(process.env['RUNNER_TEMP'], "key2");
fileUtility.writeFileSync = jest.fn();
expect(fromLiteralsToFromFile("--from-literal=key2=\" Value 023\" --from-file=./filepath --otherArgument=value"))
.toBe('--from-file=' + filePath + ' --from-file=./filepath --otherArgument=value');
expect(fileUtility.writeFileSync).toBeCalledWith(filePath, " Value 023");
})
test('Valid " Case1', () => {
var filePath = path.join(process.env['RUNNER_TEMP'], "key2");
fileUtility.writeFileSync = jest.fn();
expect(fromLiteralsToFromFile("--from-literal=key2=value\"ksk --from-file=./filepath --otherArgument=value"))
.toBe('--from-file=' + filePath + ' --from-file=./filepath --otherArgument=value');
expect(fileUtility.writeFileSync).toBeCalledWith(filePath, "value\"ksk");
})
test('Valid " Case2', () => {
var filePath = path.join(process.env['RUNNER_TEMP'], "key2");
fileUtility.writeFileSync = jest.fn();
expect(fromLiteralsToFromFile("--from-literal=key2=\"valueksk --from-file=./filepath --otherArgument=value"))
.toBe('--from-file=' + filePath + ' --from-file=./filepath --otherArgument=value');
expect(fileUtility.writeFileSync).toBeCalledWith(filePath, "\"valueksk");
})
test('Valid " Case3', () => {
var filePath = path.join(process.env['RUNNER_TEMP'], "key2");
fileUtility.writeFileSync = jest.fn();
expect(fromLiteralsToFromFile("--from-literal=key2=valueksk\" --from-file=./filepath --otherArgument=value"))
.toBe('--from-file=' + filePath + ' --from-file=./filepath --otherArgument=value');
expect(fileUtility.writeFileSync).toBeCalledWith(filePath, "valueksk\"");
})
test('No separator ', () => {
expect(fromLiteralsToFromFile("test=this")).toBe('test=this')
})
test('Special characters & in value', () => {
var filePath = path.join(process.env['RUNNER_TEMP'], "key3");
fileUtility.writeFileSync = jest.fn();
expect(fromLiteralsToFromFile("--from-literal=key3=hello&world")).toBe('--from-file=' + filePath);
expect(fileUtility.writeFileSync).toBeCalledWith(filePath, "hello&world");
})
test('Special characters # in value', () => {
var filePath = path.join(process.env['RUNNER_TEMP'], "key4");
fileUtility.writeFileSync = jest.fn();
expect(fromLiteralsToFromFile("--from-literal=key4=hello#world")).toBe('--from-file=' + filePath);
expect(fileUtility.writeFileSync).toBeCalledWith(filePath, "hello#world");
})
test('Special characters = in value', () => {
var filePath = path.join(process.env['RUNNER_TEMP'], "key5");
fileUtility.writeFileSync = jest.fn();
expect(fromLiteralsToFromFile("--from-literal=key5=hello=world")).toBe('--from-file=' + filePath);
expect(fileUtility.writeFileSync).toBeCalledWith(filePath, "hello=world");
})
test('Special characters in value', () => {
var filePath = path.join(process.env['RUNNER_TEMP'], "key6");
fileUtility.writeFileSync = jest.fn();
expect(fromLiteralsToFromFile("--from-literal=key6=&^)@!&^@)")).toBe('--from-file=' + filePath);
expect(fileUtility.writeFileSync).toBeCalledWith(filePath, "&^)@!&^@)");
})

10
jest.config.js Normal file
Просмотреть файл

@ -0,0 +1,10 @@
module.exports = {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
transform: {
'^.+\\.ts$': 'ts-jest'
},
verbose: true
}

22
lib/file.utility.js Normal file
Просмотреть файл

@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createFile = void 0;
const fs = require("fs");
const path = require("path");
/**
*
* @param fileName The fileName in case of a file needs to be created in TEMP folder else the entire filepath
* @param data File data
* @param inTempFolder Boolean to indicate if file needs to be created in TEMP folder
*/
function createFile(fileName, data, inTempFolder) {
const filePath = inTempFolder ? path.join(process.env['RUNNER_TEMP'], fileName) : fileName;
try {
fs.writeFileSync(filePath, data);
}
catch (err) {
throw err;
}
return filePath;
}
exports.createFile = createFile;

Просмотреть файл

@ -1,19 +1,22 @@
"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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.fromLiteralsToFromFile = void 0;
const toolCache = require("@actions/tool-cache");
const core = require("@actions/core");
const toolrunner_1 = require("@actions/exec/lib/toolrunner");
const path = require("path");
const os = require("os");
const io = require("@actions/io");
const fileUtility = require("./file.utility");
let kubectlPath = "";
function checkAndSetKubectlPath() {
return __awaiter(this, void 0, void 0, function* () {
@ -90,10 +93,43 @@ function getDockerSecretArguments(secretName) {
}
function getGenericSecretArguments(secretName) {
const secretArguments = core.getInput('arguments');
const parsedArgument = fromLiteralsToFromFile(secretArguments);
let args = ['create', 'secret', 'generic', secretName];
args.push(...toolrunner_1.argStringToArray(secretArguments));
args.push(...toolrunner_1.argStringToArray(parsedArgument));
return args;
}
/**
* Takes a valid kubectl arguments and parses --from-literal to --from-file
* @param secretArguments
*/
function fromLiteralsToFromFile(secretArguments) {
const parsedArgument = secretArguments.split("--").reduce((argumentsBuilder, argument) => {
if (argument && !argument.startsWith("from-literal=")) {
argumentsBuilder = argumentsBuilder.trim() + " --" + argument;
}
else if (argument && argument.startsWith("from-literal=")) {
const command = argument.substring("from-literal=".length);
/* The command starting after 'from-literal=' contanis a 'key=value' format. The secret itself might contain a '=',
Hence the substring than a split*/
if (command.indexOf("=") == -1)
throw new Error('Invalid from-literal input. It should contain a key and value');
const secretName = command.substring(0, command.indexOf("=")).trim();
const secretValue = command.substring(command.indexOf("=") + 1).trim();
//Secret with spaces will be enclosed in quotes -> "secret "
if (secretValue && secretValue.indexOf("\"") == 0 && secretValue.lastIndexOf("\"") == secretValue.length - 1) {
const secret = secretValue.substring(1, secretValue.lastIndexOf("\""));
argumentsBuilder += " --from-file=" + fileUtility.createFile(secretName, secret, true);
}
else {
const secret = secretValue.substring(0, secretValue.indexOf(" ") == -1 ? secretValue.length : secretValue.indexOf(" "));
argumentsBuilder += " --from-file=" + fileUtility.createFile(secretName, secret, true);
}
}
return argumentsBuilder;
});
return parsedArgument.trim();
}
exports.fromLiteralsToFromFile = fromLiteralsToFromFile;
function checkClusterContext() {
if (!process.env["KUBECONFIG"]) {
throw new Error('Cluster context not set. Use k8s-set-context/aks-set-context action to set cluster context');

2
package-lock.json сгенерированный
Просмотреть файл

@ -68,4 +68,4 @@
"integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
}
}
}
}

Просмотреть файл

@ -4,7 +4,8 @@
"private": true,
"main": "lib/run.js",
"scripts": {
"build": "tsc --outDir .\\lib\\ --rootDir .\\src\\"
"build": "tsc --outDir .\\lib\\ --rootDir .\\src\\",
"test": "jest"
},
"keywords": [
"actions",
@ -19,6 +20,10 @@
"@actions/tool-cache": "^1.0.0"
},
"devDependencies": {
"@types/node": "^12.0.4"
"@types/node": "^12.0.4",
"jest": "^26.0.1",
"@types/jest": "^25.2.2",
"ts-jest": "^25.5.1",
"typescript": "^3.9.2"
}
}

19
src/file.utility.ts Normal file
Просмотреть файл

@ -0,0 +1,19 @@
import fs = require("fs");
import * as path from 'path';
/**
*
* @param fileName The fileName in case of a file needs to be created in TEMP folder else the entire filepath
* @param data File data
* @param inTempFolder Boolean to indicate if file needs to be created in TEMP folder
*/
export function createFile(fileName: string, data: string, inTempFolder: boolean): string {
const filePath = inTempFolder ? path.join(process.env['RUNNER_TEMP'], fileName) : fileName;
try {
fs.writeFileSync(filePath, data);
}
catch (err) {
throw err;
}
return filePath;
}

Просмотреть файл

@ -6,6 +6,8 @@ import * as path from 'path';
import * as os from 'os';
import * as io from '@actions/io';
import fileUtility = require('./file.utility')
let kubectlPath = "";
async function checkAndSetKubectlPath() {
@ -95,11 +97,41 @@ function getDockerSecretArguments(secretName: string): string[] {
function getGenericSecretArguments(secretName: string): string[] {
const secretArguments = core.getInput('arguments');
const parsedArgument = fromLiteralsToFromFile(secretArguments);
let args = ['create', 'secret', 'generic', secretName];
args.push(...argStringToArray(secretArguments));
args.push(...argStringToArray(parsedArgument));
return args;
}
/**
* Takes a valid kubectl arguments and parses --from-literal to --from-file
* @param secretArguments
*/
export function fromLiteralsToFromFile(secretArguments: string): string {
const parsedArgument = secretArguments.split("--").reduce((argumentsBuilder, argument) => {
if (argument && !argument.startsWith("from-literal=")) {
argumentsBuilder = argumentsBuilder.trim() + " --" + argument;
} else if (argument && argument.startsWith("from-literal=")) {
const command = argument.substring("from-literal=".length);
/* The command starting after 'from-literal=' contanis a 'key=value' format. The secret itself might contain a '=',
Hence the substring than a split*/
if (command.indexOf("=") == -1) throw new Error('Invalid from-literal input. It should contain a key and value');
const secretName = command.substring(0, command.indexOf("=")).trim();
const secretValue = command.substring(command.indexOf("=") + 1).trim();
//Secret with spaces will be enclosed in quotes -> "secret "
if (secretValue && secretValue.indexOf("\"") == 0 && secretValue.lastIndexOf("\"") == secretValue.length - 1) {
const secret = secretValue.substring(1, secretValue.lastIndexOf("\""));
argumentsBuilder += " --from-file=" + fileUtility.createFile(secretName, secret, true);
} else {
const secret = secretValue.substring(0, secretValue.indexOf(" ") == -1 ? secretValue.length : secretValue.indexOf(" "));
argumentsBuilder += " --from-file=" + fileUtility.createFile(secretName, secret, true);
}
}
return argumentsBuilder;
});
return parsedArgument.trim();
}
function checkClusterContext() {
if (!process.env["KUBECONFIG"]) {
throw new Error('Cluster context not set. Use k8s-set-context/aks-set-context action to set cluster context');

Просмотреть файл

@ -4,6 +4,7 @@
"module": "commonjs"
},
"exclude": [
"node_modules"
"node_modules",
"__tests__"
]
}