Skip to content

Commit

Permalink
Merge branch 'js_action' into keep_env
Browse files Browse the repository at this point in the history
  • Loading branch information
dmvict committed Sep 19, 2023
2 parents 497c8a6 + de48941 commit 59244ed
Show file tree
Hide file tree
Showing 13 changed files with 728 additions and 51 deletions.
7 changes: 4 additions & 3 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# action::retry [![status](https://github.com/Wandalen/wretry.action/actions/workflows/JsActionPublish.yml/badge.svg)](https://github.com/Wandalen/wretry.action/actions/workflows/JsActionPublish.yml) [![stable](https://img.shields.io/badge/stability-stable-brightgreen.svg)](https://github.com/emersion/stability-badges#stable)

Retries an Github Action step or command on failure.
Retries an Github Action step or a command on failure.

Works with either shell commands or other actions to retry.

Expand All @@ -24,7 +24,8 @@ It is a cause of failed jobs. For this case, the action `wretry.action` can retr
## Features

- Retries Github `JavaScript` actions. The action can be an action repository that is not published on `Marketplace`.
- Retries `bash` shell commands.
- Retries Github `Docker` actions which use `Dockerfile` as image.
- Retries shell commands. Uses default shells to run commands.
- Can retry single action or single command ( multiline command ), but not both simultaneously.
- Retries `main`, `pre` and `post` stages of external actions.
- Always has `pre` and `post` stages. If external action has `pre` or/and `post` stage, then action run it also.
Expand Down Expand Up @@ -112,7 +113,7 @@ Depends on output of given Github action.
```yaml
- uses: Wandalen/wretry.action@master
with:
action: action/node-setup@2.3.0
action: action/setup-node@2.3.0
with: |
node-version: 14.x
architecture: x64
Expand Down
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ inputs:
description: 'Pass context `matrix` into an external action. Default is context `matrix` of a job.'
required: false
default: ${{ toJSON( matrix ) }}
inputs_context:
description: 'Pass context `inputs` into an external action. The action cannot resolve context `inputs` and resolve all inputs. If you need valid context `inputs`, then add option `inputs_context : ${{ toJSON( inputs ) }}`.'
required: false
default: '{}'
attempt_limit:
description: 'Number of attempts'
required: false
Expand Down
2 changes: 1 addition & 1 deletion node_modules/github-actions-parser/dist/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
],
"dependencies": {
"wgittools": "alpha",
"github-actions-parser": "0.26.0",
"github-actions-parser": "dmvict/github-actions-parser#inputs_context_dist",
"@actions/core": "1.10.0"
},
"devDependencies": {
Expand Down
79 changes: 44 additions & 35 deletions src/Common.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function remotePathFromActionName( name )
}
else
{
name = name.replace( /^(\S*\/\S*)\//, '$1.git/' );
name = name.replace( /^([^\/]+\/[^\/]+)\//, '$1.git/' );
return _.git.path.parse( `https://github.com/${ _.str.replace( name, '@', '!' ) }` );
}
}
Expand Down Expand Up @@ -91,7 +91,7 @@ function actionConfigRead( actionDir )
if( !_.fileProvider.fileExists( configPath ) )
configPath = _.path.join( actionDir, 'action.yaml' )

_.assert( _.fileProvider.fileExists( configPath ), 'Expects action path `action.yml` or `action.yaml`' );
_.assert( _.fileProvider.fileExists( configPath ), 'Expects action path `action.yml` or `action.yaml` in the action dir: ' + actionDir );

return _.fileProvider.fileRead
({
Expand Down Expand Up @@ -199,7 +199,7 @@ function envOptionsFrom( options, inputs )
const result = Object.create( null );

for( let key in options )
result[ `INPUT_${key.replace(/ /g, '_').toUpperCase()}` ] = options[ key ];
result[ `INPUT_${ key.replace( / /g, '_' ).toUpperCase() }` ] = options[ key ];

if( inputs )
{
Expand All @@ -214,50 +214,61 @@ function envOptionsFrom( options, inputs )
{
if( GithubActionsParser === null )
GithubActionsParser = require( 'github-actions-parser' );
value = GithubActionsParser.evaluateExpression( value, { get : getContext } );
value = GithubActionsParser.evaluateExpression( value, { get : contextGet } );
}
result[ `INPUT_${key.replace(/ /g, '_').toUpperCase()}` ] = value;
}
}
}

return result;
}

/* */
//

function getContext( contextName )
function contextGet( contextName )
{
if( contextName === 'env' )
{
if( contextName === 'env' )
{
let envContext = JSON.parse( core.getInput( 'env_context' ) );
if( _.map.keys( envContext ).length === 0 )
return process.env;
return envContext;
}
else if( contextName === 'github' )
{
let githubContext = JSON.parse( core.getInput( 'github_context' ) );
githubContext = githubContextUpdate( githubContext );
return githubContext;
}
else if( contextName === 'job' )
{
const jobContext = JSON.parse( core.getInput( 'job_context' ) );
return jobContext;
}
else if( contextName === 'matrix' )
{
const matrixContext = JSON.parse( core.getInput( 'matrix_context' ) );
return matrixContext;
}

_.assert( false, `The requested context "${ contextName }" does not supported by action. Please, open an issue with the request for the feature.` );
let envContext = JSON.parse( core.getInput( 'env_context' ) );
if( _.map.keys( envContext ).length === 0 )
return process.env;
return envContext;
}
else if( contextName === 'github' )
{
let githubContext = JSON.parse( core.getInput( 'github_context' ) );
githubContext = githubContextUpdate( githubContext );
return githubContext;
}
else if( contextName === 'job' )
{
const jobContext = JSON.parse( core.getInput( 'job_context' ) );
return jobContext;
}
else if( contextName === 'matrix' )
{
const matrixContext = JSON.parse( core.getInput( 'matrix_context' ) );
return matrixContext;
}
else if( contextName === 'inputs' )
{
const inputsContext = JSON.parse( core.getInput( 'inputs_context' ) );
return inputsContext;
}

_.sure
(
false,
`The requested context "${ contextName }" does not supported by action.`
+ '\nPlease, open an issue with the request for the feature.'
);

/* */

function githubContextUpdate( githubContext )
{
console.log( process.env.RETRY_ACTION );
const remoteActionPath = remotePathFromActionName( process.env.RETRY_ACTION );
const localActionPath = _.path.nativize( _.path.join( __dirname, '../../../', remoteActionPath.repo ) );
githubContext.action_path = localActionPath;
Expand All @@ -271,10 +282,7 @@ function envOptionsFrom( options, inputs )
function envOptionsSetup( options )
{
for( let key in options )
{
core.exportVariable( key, options[ key ] );
process.env[ key ] = options[ key ];
}
core.exportVariable( key, options[ key ] );
}

// --
Expand All @@ -289,6 +297,7 @@ const Self =
actionConfigRead,
actionOptionsParse,
envOptionsFrom,
contextGet,
envOptionsSetup,
};

Expand Down
212 changes: 212 additions & 0 deletions src/Docker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
const core = require( '@actions/core' );
if( typeof wTools === 'undefined' )
require( '../node_modules/Joined.s' );
const _ = wTools;
const ChildProcess = require( 'child_process' );
let common = require( './Common.js' );
if( _.map.keys( common ).length === 0 )
{
const path = require.resolve( './Common.js' );
common = require.cache[ path ].exports;
}
let GithubActionsParser = null;

//

/*
To run commands synchronously and with no `deasync` this wrapper is used because
_.process uses Consequence in each `start*` routine and for synchronous execution it requires `deasync`.
`deasync` is the binary module. To prevent failures during action build and decrease time of setup the action,
we exclude `deasync` and compile action code.
*/

function execSyncNonThrowing( command )
{
try
{
return ChildProcess.execSync( command, { stdio : 'pipe' } );
}
catch( err )
{
_.error.attend( err );
return err;
}
}

//

function exists()
{
return !_.error.is( execSyncNonThrowing( 'docker -v' ) );
}

//

function imageBuild( actionPath, image )
{
const docker = this;

_.sure
(
docker.exists(),
'Current OS has no Docker utility.\n'
+ 'Please, visit '
+ 'https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#preinstalled-software\n'
+ 'and select valid workflow runner.'
);

if( image === 'Dockerfile' )
{
const actionName = _.path.name( actionPath );
const imageName = `${ actionName }_repo:${ actionName }_tag`.toLowerCase();
const dockerfilePath = _.path.nativize( _.path.join( actionPath, 'Dockerfile' ) );
const command = `docker build -t ${ imageName } -f ${ dockerfilePath } ${ _.path.nativize( actionPath ) }`;
const build = execSyncNonThrowing( command );
if( _.error.is( build ) )
throw _.error.brief( build );

core.info( `Dockerfile for action : ${ dockerfilePath }.` );
core.info( command );
core.info( build.toString() );

return imageName;
}

_.sure
(
false,
`The action does not support requested Docker image type "${ image }".`
+ '\nPlease, open an issue with the request for the feature.'
);
}

//

function runCommandForm( imageName, inputs )
{
const [ repo, tag ] = imageName.split( ':' );
_.sure( _.str.defined( repo ) && _.str.defined( tag ), 'Expects image name in format "[repo]:[tag]".' );
const command = [ `docker run --name ${ tag } --label ${ repo } --workdir /github/workspace --rm` ];
const env_keys = _.map.keys( JSON.parse( core.getInput( 'env_context' ) ) );
const inputs_keys = _.map.keys( inputs );
const postfix_command_envs =
[
'HOME',
'GITHUB_JOB',
'GITHUB_REF',
'GITHUB_SHA',
'GITHUB_REPOSITORY',
'GITHUB_REPOSITORY_OWNER',
'GITHUB_REPOSITORY_OWNER_ID',
'GITHUB_RUN_ID',
'GITHUB_RUN_NUMBER',
'GITHUB_RETENTION_DAYS',
'GITHUB_RUN_ATTEMPT',
'GITHUB_REPOSITORY_ID',
'GITHUB_ACTOR_ID',
'GITHUB_ACTOR',
'GITHUB_TRIGGERING_ACTOR',
'GITHUB_WORKFLOW',
'GITHUB_HEAD_REF',
'GITHUB_BASE_REF',
'GITHUB_EVENT_NAME',
'GITHUB_SERVER_URL',
'GITHUB_API_URL',
'GITHUB_GRAPHQL_URL',
'GITHUB_REF_NAME',
'GITHUB_REF_PROTECTED',
'GITHUB_REF_TYPE',
'GITHUB_WORKFLOW_REF',
'GITHUB_WORKFLOW_SHA',
'GITHUB_WORKSPACE',
'GITHUB_ACTION',
'GITHUB_EVENT_PATH',
'GITHUB_ACTION_REPOSITORY',
'GITHUB_ACTION_REF',
'GITHUB_PATH',
'GITHUB_ENV',
'GITHUB_STEP_SUMMARY',
'GITHUB_STATE',
'GITHUB_OUTPUT',
'RUNNER_OS',
'RUNNER_ARCH',
'RUNNER_NAME',
'RUNNER_ENVIRONMENT',
'RUNNER_TOOL_CACHE',
'RUNNER_TEMP',
'RUNNER_WORKSPACE',
'ACTIONS_RUNTIME_URL',
'ACTIONS_RUNTIME_TOKEN',
'ACTIONS_CACHE_URL',
'GITHUB_ACTIONS=true',
'CI=true',
];
const postfix_command_paths =
[
'"/var/run/docker.sock":"/var/run/docker.sock"',
'"/home/runner/work/_temp/_github_home":"/github/home"',
'"/home/runner/work/_temp/_github_workflow":"/github/workflow"',
'"/home/runner/work/_temp/_runner_file_commands":"/github/file_commands"',
`"${ process.env.GITHUB_WORKSPACE }":"/github/workspace"`,
`"${ process.env.GITHUB_OUTPUT }":"${ process.env.GITHUB_OUTPUT }"`,
];

/* */

if( env_keys.length > 0 )
command.push( '-e', env_keys.join( ' -e ' ) );
if( inputs_keys.length > 0 )
command.push( '-e', inputs_keys.join( ' -e ' ) );
command.push( '-e', postfix_command_envs.join( ' -e ' ) );
command.push( '-v', postfix_command_paths.join( ' -v ' ) );
command.push( imageName );

return command.join( ' ' );
}

//

function commandArgsFrom( args, inputs )
{
const result = [];
if( args === undefined )
return result;

for( let i = 0 ; i < args.length ; i++ )
{
let value = args[ i ];
if( _.str.is( value ) )
if( value.startsWith( '${{' ) && value.endsWith( '}}' ) )
{
if( GithubActionsParser === null )
GithubActionsParser = require( 'github-actions-parser' );
value = GithubActionsParser.evaluateExpression( value,
{
get : ( name ) =>
{
let context = common.contextGet( name );
if( _.map.keys( context ).length === 0 && name === 'inputs' )
return inputs;
return context;
}
});
}
result.push( value );
}

return result;
}

// --
// export
// --

const Self =
{
exists,
imageBuild,
runCommandForm,
commandArgsFrom,
};

module.exports = Self;
Loading

0 comments on commit 59244ed

Please sign in to comment.