Skip to content

Commit ef0ce0c

Browse files
authored
Ignore failures when trying to kill the ssh-agent (#33)
1 parent 5ef9e03 commit ef0ce0c

7 files changed

+284
-109
lines changed

CHANGELOG.md

+21-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10-
## v0.2.0
10+
## v0.4.0
11+
12+
### Changed
13+
14+
* A failure to kill the agent in the post-action step will no longer fail the workflow run. That way, you can kill the agent yourself when necessary (#33).
15+
16+
## v0.3.0 [2020-05-18]
17+
18+
### Added
19+
20+
* A new post-action step will automatically clean up the running agent at the end of a job. This helps with self-hosted runners, which are non-ephemeral. (@thommyhh, #27)
21+
22+
### Changed
23+
24+
* Unless the SSH_AUTH_SOCK is configured explicitly, the SSH agent will now use a random file name for the socket. That way, multiple, concurrent SSH agents can be used on self-hosted runners. (@thommyhh, #27)
25+
26+
## v0.2.0 [2020-01-14]
1127

1228
### Added
1329

@@ -16,3 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1632

1733
* Catch empty ssh-private-key input values and exit with a helpful
1834
error message right away.
35+
36+
## v0.1.0 [2019-09-15]
37+
38+
Initial release.

README.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ jobs:
2727
...
2828
steps:
2929
- actions/checkout@v1
30-
# Make sure the @v0.3.0 matches the current version of the
30+
# Make sure the @v0.4.0 matches the current version of the
3131
# action
32-
- uses: webfactory/ssh-agent@v0.3.0
32+
- uses: webfactory/ssh-agent@v0.4.0
3333
with:
3434
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
3535
- ... other steps
@@ -44,7 +44,7 @@ In that case, you can set-up the different keys as multiple secrets and pass the
4444

4545
```yaml
4646
# ... contens as before
47-
- uses: webfactory/ssh-agent@v0.3.0
47+
- uses: webfactory/ssh-agent@v0.4.0
4848
with:
4949
ssh-private-key: |
5050
${{ secrets.FIRST_KEY }}
@@ -55,10 +55,10 @@ In that case, you can set-up the different keys as multiple secrets and pass the
5555
The `ssh-agent` will load all of the keys and try each one in order when establishing SSH connections.
5656

5757
There's one **caveat**, though: SSH servers may abort the connection attempt after a number of mismatching keys have been presented. So if, for example, you have
58-
six different keys loaded into the `ssh-agent`, but the server aborts after five unknown keys, the last key (which might be the right one) will never even be tried.
58+
six different keys loaded into the `ssh-agent`, but the server aborts after five unknown keys, the last key (which might be the right one) will never even be tried. If you don't need all of the keys at the same time, you could try to `run: kill $SSH_AGENT_PID` to kill the currently running `ssh-agent` and use the action again in a following step to start another instance.
5959

6060
## Exported variables
61-
The action exports `SSH_AUTH_SOCK` and `SSH_AGENT_PID` through the Github Actions core module.
61+
The action exports the `SSH_AUTH_SOCK` and `SSH_AGENT_PID` environment variables through the Github Actions core module.
6262
The `$SSH_AUTH_SOCK` is used by several applications like git or rsync to connect to the SSH authentication agent.
6363
The `$SSH_AGENT_PID` contains the process id of the agent. This is used to kill the agent in post job action.
6464

@@ -118,7 +118,7 @@ To actually grant the SSH key access, you can – on GitHub – use at least two
118118
As a note to my future self, in order to work on this repo:
119119

120120
* Clone it
121-
* Run `npm install` to fetch dependencies
121+
* Run `yarn install` to fetch dependencies
122122
* _hack hack hack_
123123
* `node index.js`. Inputs are passed through `INPUT_` env vars with their names uppercased. Use `env "INPUT_SSH-PRIVATE-KEY=\`cat file\`" node index.js` for this action.
124124
* Run `npm run build` to update `dist/*`, which holds the files actually run

cleanup.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ try {
66
console.log('Stopping SSH agent')
77
execSync('kill ${SSH_AGENT_PID}', { stdio: 'inherit' })
88
} catch (error) {
9-
core.setFailed(error.message)
9+
console.log(error.message);
10+
console.log('Error stopping the SSH agent, proceeding anyway');
1011
}

dist/cleanup.js

+122-41
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ try {
6868
console.log('Stopping SSH agent')
6969
execSync('kill ${SSH_AGENT_PID}', { stdio: 'inherit' })
7070
} catch (error) {
71-
core.setFailed(error.message)
71+
console.log(error.message);
72+
console.log('Error stopping the SSH agent, proceeding anyway');
7273
}
7374

7475

@@ -79,17 +80,24 @@ try {
7980

8081
"use strict";
8182

83+
var __importStar = (this && this.__importStar) || function (mod) {
84+
if (mod && mod.__esModule) return mod;
85+
var result = {};
86+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
87+
result["default"] = mod;
88+
return result;
89+
};
8290
Object.defineProperty(exports, "__esModule", { value: true });
83-
const os = __webpack_require__(87);
91+
const os = __importStar(__webpack_require__(87));
8492
/**
8593
* Commands
8694
*
8795
* Command Format:
88-
* ##[name key=value;key=value]message
96+
* ::name key=value,key=value::message
8997
*
9098
* Examples:
91-
* ##[warning]This is the user warning message
92-
* ##[set-secret name=mypassword]definitelyNotAPassword!
99+
* ::warning::This is the message
100+
* ::set-env name=MY_VAR::some value
93101
*/
94102
function issueCommand(command, properties, message) {
95103
const cmd = new Command(command, properties, message);
@@ -100,7 +108,7 @@ function issue(name, message = '') {
100108
issueCommand(name, {}, message);
101109
}
102110
exports.issue = issue;
103-
const CMD_PREFIX = '##[';
111+
const CMD_STRING = '::';
104112
class Command {
105113
constructor(command, properties, message) {
106114
if (!command) {
@@ -111,37 +119,56 @@ class Command {
111119
this.message = message;
112120
}
113121
toString() {
114-
let cmdStr = CMD_PREFIX + this.command;
122+
let cmdStr = CMD_STRING + this.command;
115123
if (this.properties && Object.keys(this.properties).length > 0) {
116124
cmdStr += ' ';
125+
let first = true;
117126
for (const key in this.properties) {
118127
if (this.properties.hasOwnProperty(key)) {
119128
const val = this.properties[key];
120129
if (val) {
121-
// safely append the val - avoid blowing up when attempting to
122-
// call .replace() if message is not a string for some reason
123-
cmdStr += `${key}=${escape(`${val || ''}`)};`;
130+
if (first) {
131+
first = false;
132+
}
133+
else {
134+
cmdStr += ',';
135+
}
136+
cmdStr += `${key}=${escapeProperty(val)}`;
124137
}
125138
}
126139
}
127140
}
128-
cmdStr += ']';
129-
// safely append the message - avoid blowing up when attempting to
130-
// call .replace() if message is not a string for some reason
131-
const message = `${this.message || ''}`;
132-
cmdStr += escapeData(message);
141+
cmdStr += `${CMD_STRING}${escapeData(this.message)}`;
133142
return cmdStr;
134143
}
135144
}
145+
/**
146+
* Sanitizes an input into a string so it can be passed into issueCommand safely
147+
* @param input input to sanitize into a string
148+
*/
149+
function toCommandValue(input) {
150+
if (input === null || input === undefined) {
151+
return '';
152+
}
153+
else if (typeof input === 'string' || input instanceof String) {
154+
return input;
155+
}
156+
return JSON.stringify(input);
157+
}
158+
exports.toCommandValue = toCommandValue;
136159
function escapeData(s) {
137-
return s.replace(/\r/g, '%0D').replace(/\n/g, '%0A');
160+
return toCommandValue(s)
161+
.replace(/%/g, '%25')
162+
.replace(/\r/g, '%0D')
163+
.replace(/\n/g, '%0A');
138164
}
139-
function escape(s) {
140-
return s
165+
function escapeProperty(s) {
166+
return toCommandValue(s)
167+
.replace(/%/g, '%25')
141168
.replace(/\r/g, '%0D')
142169
.replace(/\n/g, '%0A')
143-
.replace(/]/g, '%5D')
144-
.replace(/;/g, '%3B');
170+
.replace(/:/g, '%3A')
171+
.replace(/,/g, '%2C');
145172
}
146173
//# sourceMappingURL=command.js.map
147174

@@ -161,9 +188,17 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
161188
step((generator = generator.apply(thisArg, _arguments || [])).next());
162189
});
163190
};
191+
var __importStar = (this && this.__importStar) || function (mod) {
192+
if (mod && mod.__esModule) return mod;
193+
var result = {};
194+
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
195+
result["default"] = mod;
196+
return result;
197+
};
164198
Object.defineProperty(exports, "__esModule", { value: true });
165199
const command_1 = __webpack_require__(431);
166-
const path = __webpack_require__(622);
200+
const os = __importStar(__webpack_require__(87));
201+
const path = __importStar(__webpack_require__(622));
167202
/**
168203
* The code to exit an action
169204
*/
@@ -182,28 +217,25 @@ var ExitCode;
182217
// Variables
183218
//-----------------------------------------------------------------------
184219
/**
185-
* sets env variable for this action and future actions in the job
220+
* Sets env variable for this action and future actions in the job
186221
* @param name the name of the variable to set
187-
* @param val the value of the variable
222+
* @param val the value of the variable. Non-string values will be converted to a string via JSON.stringify
188223
*/
224+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
189225
function exportVariable(name, val) {
190-
process.env[name] = val;
191-
command_1.issueCommand('set-env', { name }, val);
226+
const convertedVal = command_1.toCommandValue(val);
227+
process.env[name] = convertedVal;
228+
command_1.issueCommand('set-env', { name }, convertedVal);
192229
}
193230
exports.exportVariable = exportVariable;
194231
/**
195-
* exports the variable and registers a secret which will get masked from logs
196-
* @param name the name of the variable to set
197-
* @param val value of the secret
232+
* Registers a secret which will get masked from logs
233+
* @param secret value of the secret
198234
*/
199-
function exportSecret(name, val) {
200-
exportVariable(name, val);
201-
// the runner will error with not implemented
202-
// leaving the function but raising the error earlier
203-
command_1.issueCommand('set-secret', {}, val);
204-
throw new Error('Not implemented.');
235+
function setSecret(secret) {
236+
command_1.issueCommand('add-mask', {}, secret);
205237
}
206-
exports.exportSecret = exportSecret;
238+
exports.setSecret = setSecret;
207239
/**
208240
* Prepends inputPath to the PATH (for this action and future actions)
209241
* @param inputPath
@@ -221,7 +253,7 @@ exports.addPath = addPath;
221253
* @returns string
222254
*/
223255
function getInput(name, options) {
224-
const val = process.env[`INPUT_${name.replace(' ', '_').toUpperCase()}`] || '';
256+
const val = process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] || '';
225257
if (options && options.required && !val) {
226258
throw new Error(`Input required and not supplied: ${name}`);
227259
}
@@ -232,12 +264,22 @@ exports.getInput = getInput;
232264
* Sets the value of an output.
233265
*
234266
* @param name name of the output to set
235-
* @param value value to store
267+
* @param value value to store. Non-string values will be converted to a string via JSON.stringify
236268
*/
269+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
237270
function setOutput(name, value) {
238271
command_1.issueCommand('set-output', { name }, value);
239272
}
240273
exports.setOutput = setOutput;
274+
/**
275+
* Enables or disables the echoing of commands into stdout for the rest of the step.
276+
* Echoing is disabled by default if ACTIONS_STEP_DEBUG is not set.
277+
*
278+
*/
279+
function setCommandEcho(enabled) {
280+
command_1.issue('echo', enabled ? 'on' : 'off');
281+
}
282+
exports.setCommandEcho = setCommandEcho;
241283
//-----------------------------------------------------------------------
242284
// Results
243285
//-----------------------------------------------------------------------
@@ -254,6 +296,13 @@ exports.setFailed = setFailed;
254296
//-----------------------------------------------------------------------
255297
// Logging Commands
256298
//-----------------------------------------------------------------------
299+
/**
300+
* Gets whether Actions Step Debug is on or not
301+
*/
302+
function isDebug() {
303+
return process.env['RUNNER_DEBUG'] === '1';
304+
}
305+
exports.isDebug = isDebug;
257306
/**
258307
* Writes debug message to user log
259308
* @param message debug message
@@ -264,20 +313,28 @@ function debug(message) {
264313
exports.debug = debug;
265314
/**
266315
* Adds an error issue
267-
* @param message error issue message
316+
* @param message error issue message. Errors will be converted to string via toString()
268317
*/
269318
function error(message) {
270-
command_1.issue('error', message);
319+
command_1.issue('error', message instanceof Error ? message.toString() : message);
271320
}
272321
exports.error = error;
273322
/**
274323
* Adds an warning issue
275-
* @param message warning issue message
324+
* @param message warning issue message. Errors will be converted to string via toString()
276325
*/
277326
function warning(message) {
278-
command_1.issue('warning', message);
327+
command_1.issue('warning', message instanceof Error ? message.toString() : message);
279328
}
280329
exports.warning = warning;
330+
/**
331+
* Writes info to log with console.log.
332+
* @param message info message
333+
*/
334+
function info(message) {
335+
process.stdout.write(message + os.EOL);
336+
}
337+
exports.info = info;
281338
/**
282339
* Begin an output group.
283340
*
@@ -318,6 +375,30 @@ function group(name, fn) {
318375
});
319376
}
320377
exports.group = group;
378+
//-----------------------------------------------------------------------
379+
// Wrapper action state
380+
//-----------------------------------------------------------------------
381+
/**
382+
* Saves state for current action, the state can only be retrieved by this action's post job execution.
383+
*
384+
* @param name name of the state to store
385+
* @param value value to store. Non-string values will be converted to a string via JSON.stringify
386+
*/
387+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
388+
function saveState(name, value) {
389+
command_1.issueCommand('save-state', { name }, value);
390+
}
391+
exports.saveState = saveState;
392+
/**
393+
* Gets the value of an state set by this action's main execution.
394+
*
395+
* @param name name of the state to get
396+
* @returns string
397+
*/
398+
function getState(name) {
399+
return process.env[`STATE_${name}`] || '';
400+
}
401+
exports.getState = getState;
321402
//# sourceMappingURL=core.js.map
322403

323404
/***/ }),

0 commit comments

Comments
 (0)