Add action logic
parent
d105f8c891
commit
d6c937fe37
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,23 @@
|
||||||
|
import * as core from '@actions/core';
|
||||||
|
import { Action, Docker, Input, ImageTag, Output } from './model';
|
||||||
|
|
||||||
|
async function action() {
|
||||||
|
Action.checkCompatibility();
|
||||||
|
|
||||||
|
const { dockerfile, workspace, actionFolder } = Action;
|
||||||
|
const { unityVersion, projectPath, artifactsPath } = Input.getFromUser();
|
||||||
|
const baseImage = ImageTag.createForBase(unityVersion);
|
||||||
|
|
||||||
|
// Build docker image
|
||||||
|
const actionImage = await Docker.build({ path: actionFolder, dockerfile, baseImage });
|
||||||
|
|
||||||
|
// Run docker image
|
||||||
|
await Docker.run(actionImage, { workspace, unityVersion, projectPath, artifactsPath });
|
||||||
|
|
||||||
|
// Set output
|
||||||
|
await Output.setArtifactsPath(artifactsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
action().catch(error => {
|
||||||
|
core.setFailed(error.message);
|
||||||
|
});
|
|
@ -0,0 +1,48 @@
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
class Action {
|
||||||
|
static get supportedPlatforms() {
|
||||||
|
return ['linux'];
|
||||||
|
}
|
||||||
|
|
||||||
|
static get isRunningLocally() {
|
||||||
|
return process.env.RUNNER_WORKSPACE === undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get isRunningFromSource() {
|
||||||
|
return path.basename(__dirname) === 'model';
|
||||||
|
}
|
||||||
|
|
||||||
|
static get name() {
|
||||||
|
return 'unity-test-runner';
|
||||||
|
}
|
||||||
|
|
||||||
|
static get rootFolder() {
|
||||||
|
if (Action.isRunningFromSource) {
|
||||||
|
return path.dirname(path.dirname(path.dirname(__filename)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.dirname(path.dirname(__filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
static get actionFolder() {
|
||||||
|
return `${Action.rootFolder}/action`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get dockerfile() {
|
||||||
|
return `${Action.actionFolder}/Dockerfile`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static get workspace() {
|
||||||
|
return process.env.GITHUB_WORKSPACE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static checkCompatibility() {
|
||||||
|
const currentPlatform = process.platform;
|
||||||
|
if (!Action.supportedPlatforms.includes(currentPlatform)) {
|
||||||
|
throw new Error(`Currently ${currentPlatform}-platform is not supported`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Action;
|
|
@ -0,0 +1,36 @@
|
||||||
|
import path from 'path';
|
||||||
|
import fs from 'fs';
|
||||||
|
import Action from './action';
|
||||||
|
|
||||||
|
describe('Action', () => {
|
||||||
|
describe('compatibility check', () => {
|
||||||
|
it('throws for anything other than linux', () => {
|
||||||
|
if (process.platform !== 'linux') {
|
||||||
|
expect(() => Action.checkCompatibility()).toThrow();
|
||||||
|
} else {
|
||||||
|
expect(() => Action.checkCompatibility()).not.toThrow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the root folder of the action', () => {
|
||||||
|
const { rootFolder, name } = Action;
|
||||||
|
|
||||||
|
expect(path.basename(rootFolder)).toStrictEqual(name);
|
||||||
|
expect(fs.existsSync(rootFolder)).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the action folder', () => {
|
||||||
|
const { actionFolder } = Action;
|
||||||
|
|
||||||
|
expect(path.basename(actionFolder)).toStrictEqual('action');
|
||||||
|
expect(fs.existsSync(actionFolder)).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the docker file', () => {
|
||||||
|
const { dockerfile } = Action;
|
||||||
|
|
||||||
|
expect(path.basename(dockerfile)).toStrictEqual('Dockerfile');
|
||||||
|
expect(fs.existsSync(dockerfile)).toStrictEqual(true);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { exec } from '@actions/exec';
|
||||||
|
import ImageTag from './image-tag';
|
||||||
|
|
||||||
|
class Docker {
|
||||||
|
static async build(buildParameters, silent = false) {
|
||||||
|
const { path, dockerfile, baseImage } = buildParameters;
|
||||||
|
const { version } = baseImage;
|
||||||
|
|
||||||
|
const tag = ImageTag.createForAction(version);
|
||||||
|
const command = `docker build ${path} \
|
||||||
|
--file ${dockerfile} \
|
||||||
|
--build-arg IMAGE=${baseImage} \
|
||||||
|
--tag ${tag}`;
|
||||||
|
|
||||||
|
await exec(command, null, { silent });
|
||||||
|
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async run(image, parameters, silent = false) {
|
||||||
|
const { unityVersion, workspace, projectPath, artifactsPath } = parameters;
|
||||||
|
|
||||||
|
const command = `docker run \
|
||||||
|
--workdir /github/workspace \
|
||||||
|
--rm \
|
||||||
|
--env UNITY_LICENSE \
|
||||||
|
--env UNITY_EMAIL \
|
||||||
|
--env UNITY_PASSWORD \
|
||||||
|
--env UNITY_SERIAL \
|
||||||
|
--env UNITY_VERSION=${unityVersion} \
|
||||||
|
--env PROJECT_PATH=${projectPath} \
|
||||||
|
--env ARTIFACTS_PATH=${artifactsPath}
|
||||||
|
--env HOME=/github/home \
|
||||||
|
--env GITHUB_REF \
|
||||||
|
--env GITHUB_SHA \
|
||||||
|
--env GITHUB_REPOSITORY \
|
||||||
|
--env GITHUB_ACTOR \
|
||||||
|
--env GITHUB_WORKFLOW \
|
||||||
|
--env GITHUB_HEAD_REF \
|
||||||
|
--env GITHUB_BASE_REF \
|
||||||
|
--env GITHUB_EVENT_NAME \
|
||||||
|
--env GITHUB_WORKSPACE=/github/workspace \
|
||||||
|
--env GITHUB_ACTION \
|
||||||
|
--env GITHUB_EVENT_PATH \
|
||||||
|
--env RUNNER_OS \
|
||||||
|
--env RUNNER_TOOL_CACHE \
|
||||||
|
--env RUNNER_TEMP \
|
||||||
|
--env RUNNER_WORKSPACE \
|
||||||
|
--volume "/var/run/docker.sock":"/var/run/docker.sock" \
|
||||||
|
--volume "/home/runner/work/_temp/_github_home":"/github/home" \
|
||||||
|
--volume "/home/runner/work/_temp/_github_workflow":"/github/workflow" \
|
||||||
|
--volume "${workspace}":"/github/workspace" \
|
||||||
|
${image}`;
|
||||||
|
|
||||||
|
await exec(command, null, { silent });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Docker;
|
|
@ -0,0 +1,20 @@
|
||||||
|
import Action from './action';
|
||||||
|
import Docker from './docker';
|
||||||
|
import ImageTag from './image-tag';
|
||||||
|
|
||||||
|
describe('Docker', () => {
|
||||||
|
it('builds', async () => {
|
||||||
|
const path = Action.actionFolder;
|
||||||
|
const dockerfile = `${path}/Dockerfile`;
|
||||||
|
const baseImage = new ImageTag({
|
||||||
|
repository: '',
|
||||||
|
name: 'alpine',
|
||||||
|
version: '3',
|
||||||
|
});
|
||||||
|
|
||||||
|
const tag = await Docker.build({ path, dockerfile, baseImage }, true);
|
||||||
|
|
||||||
|
expect(tag).toBeInstanceOf(ImageTag);
|
||||||
|
expect(tag.toString()).toStrictEqual('unity-action:3');
|
||||||
|
}, 240000);
|
||||||
|
});
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { trimStart } from 'lodash-es';
|
||||||
|
|
||||||
|
class ImageTag {
|
||||||
|
static createForBase(version) {
|
||||||
|
const repository = 'gableroux';
|
||||||
|
const name = 'unity3d';
|
||||||
|
return new this({ repository, name, version });
|
||||||
|
}
|
||||||
|
|
||||||
|
static createForAction(version) {
|
||||||
|
const repository = '';
|
||||||
|
const name = 'unity-action';
|
||||||
|
return new this({ repository, name, version });
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor({ repository = '', name, version }) {
|
||||||
|
if (!ImageTag.versionPattern.test(version)) {
|
||||||
|
throw new Error(`Invalid version "${version}".`);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, { repository, name, version });
|
||||||
|
}
|
||||||
|
|
||||||
|
static get versionPattern() {
|
||||||
|
return /^20\d{2}\.\d\.\w{3,4}|3$/;
|
||||||
|
}
|
||||||
|
|
||||||
|
get tag() {
|
||||||
|
return this.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
get image() {
|
||||||
|
return trimStart(`${this.repository}/${this.name}`, '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `${this.image}:${this.tag}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ImageTag;
|
|
@ -0,0 +1,38 @@
|
||||||
|
import ImageTag from './image-tag';
|
||||||
|
|
||||||
|
describe('UnityImageVersion', () => {
|
||||||
|
describe('constructor', () => {
|
||||||
|
const some = {
|
||||||
|
name: 'someName',
|
||||||
|
version: '2020.0.00f0',
|
||||||
|
};
|
||||||
|
|
||||||
|
it('can be called', () => {
|
||||||
|
expect(() => new ImageTag(some)).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('accepts parameters and sets the right properties', () => {
|
||||||
|
const image = new ImageTag(some);
|
||||||
|
|
||||||
|
expect(image.repository).toStrictEqual('');
|
||||||
|
expect(image.name).toStrictEqual(some.name);
|
||||||
|
expect(image.version).toStrictEqual(some.version);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each(['2000.0.0f0', '2011.1.11f1'])('accepts %p version format', version => {
|
||||||
|
expect(() => new ImageTag({ version })).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test.each(['some version', '', 1, null])('throws for incorrect versions %p', version => {
|
||||||
|
expect(() => new ImageTag({ version })).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('toString', () => {
|
||||||
|
it('returns the correct version', () => {
|
||||||
|
const image = ImageTag.createForBase('2099.1.1111');
|
||||||
|
|
||||||
|
expect(image.toString()).toStrictEqual(`gableroux/unity3d:2099.1.1111`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,7 @@
|
||||||
|
import Action from './action';
|
||||||
|
import Docker from './docker';
|
||||||
|
import Input from './input';
|
||||||
|
import ImageTag from './image-tag';
|
||||||
|
import Output from './output';
|
||||||
|
|
||||||
|
export { Action, Docker, Input, ImageTag, Output };
|
|
@ -0,0 +1,7 @@
|
||||||
|
import * as Index from '.';
|
||||||
|
|
||||||
|
describe('Index', () => {
|
||||||
|
test.each(['Action', 'Docker', 'ImageTag', 'Input', 'Output'])('exports %s', exportedModule => {
|
||||||
|
expect(typeof Index[exportedModule]).toStrictEqual('function');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,35 @@
|
||||||
|
import { getInput } from '@actions/core';
|
||||||
|
import { includes } from 'lodash-es';
|
||||||
|
|
||||||
|
class Input {
|
||||||
|
static get testModes() {
|
||||||
|
return ['all', 'playmode', 'editmode'];
|
||||||
|
}
|
||||||
|
|
||||||
|
static getFromUser() {
|
||||||
|
// Input variables specified in workflow using "with" prop.
|
||||||
|
const unityVersion = getInput('unityVersion') || '2019.2.11f1';
|
||||||
|
const testMode = getInput('testMode') || 'all';
|
||||||
|
const rawProjectPath = getInput('testMode') || '.';
|
||||||
|
const rawArtifactsPath = getInput('testMode') || 'artifacts';
|
||||||
|
|
||||||
|
// Validate input
|
||||||
|
if (!includes(this.testModes, testMode)) {
|
||||||
|
throw new Error(`Invalid testMode ${testMode}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanitise input
|
||||||
|
const projectPath = rawProjectPath.replace(/\/$/, '');
|
||||||
|
const artifactsPath = rawArtifactsPath.replace(/\/$/, '');
|
||||||
|
|
||||||
|
// Return sanitised input
|
||||||
|
return {
|
||||||
|
unityVersion,
|
||||||
|
projectPath,
|
||||||
|
testMode,
|
||||||
|
artifactsPath,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Input;
|
|
@ -0,0 +1,13 @@
|
||||||
|
import Input from './input';
|
||||||
|
|
||||||
|
describe('Input', () => {
|
||||||
|
describe('getFromUser', () => {
|
||||||
|
it('does not throw', () => {
|
||||||
|
expect(() => Input.getFromUser()).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an object', () => {
|
||||||
|
expect(typeof Input.getFromUser()).toStrictEqual('object');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,9 @@
|
||||||
|
const core = require('@actions/core');
|
||||||
|
|
||||||
|
class Output {
|
||||||
|
static async setArtifactsPath(artifactsPath) {
|
||||||
|
await core.setOutput('artifactsPath', artifactsPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Output;
|
|
@ -0,0 +1,9 @@
|
||||||
|
import Output from './output';
|
||||||
|
|
||||||
|
describe('Output', () => {
|
||||||
|
describe('setArtifactsPath', () => {
|
||||||
|
it('does not throw', async () => {
|
||||||
|
await expect(Output.setArtifactsPath()).resolves.not.toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue