pull/437/head
Frostebite 2022-10-14 20:04:14 +01:00
parent 7fd983757d
commit a320cf893c
6 changed files with 198 additions and 197 deletions

View File

@ -21,7 +21,8 @@ env:
AWS_DEFAULT_REGION: eu-west-2 AWS_DEFAULT_REGION: eu-west-2
AWS_BASE_STACK_NAME: game-ci-github-pipelines AWS_BASE_STACK_NAME: game-ci-github-pipelines
CLOUD_RUNNER_BRANCH: ${{ github.ref }} CLOUD_RUNNER_BRANCH: ${{ github.ref }}
CLOUD_RUNNER_TESTS: true CLOUD_RUNNER_DEBUG: true
CLOUD_RUNNER_DEBUG_TREE: true
DEBUG: true DEBUG: true
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }} UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
PROJECT_PATH: test-project PROJECT_PATH: test-project

378
dist/index.js vendored
View File

@ -595,194 +595,6 @@ __decorate([
exports.Cli = Cli; exports.Cli = Cli;
/***/ }),
/***/ 67170:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
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) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.SharedWorkspaceLocking = void 0;
const cloud_runner_system_1 = __nccwpck_require__(99393);
const fs = __importStar(__nccwpck_require__(57147));
const cloud_runner_logger_1 = __importDefault(__nccwpck_require__(22855));
const cloud_runner_options_1 = __importDefault(__nccwpck_require__(96552));
class SharedWorkspaceLocking {
static GetAllWorkspaces(buildParametersContext) {
return __awaiter(this, void 0, void 0, function* () {
return (yield SharedWorkspaceLocking.ReadLines(`aws s3 ls ${SharedWorkspaceLocking.workspaceRoot}${buildParametersContext.cacheKey}/`)).map((x) => x.replace(`/`, ``));
});
}
static DoesWorkspaceTopLevelExist(buildParametersContext) {
return __awaiter(this, void 0, void 0, function* () {
const results = (yield SharedWorkspaceLocking.ReadLines(`aws s3 ls ${SharedWorkspaceLocking.workspaceRoot}`)).map((x) => x.replace(`/`, ``));
return results.includes(buildParametersContext.cacheKey);
});
}
static GetAllLocks(workspace, buildParametersContext) {
return __awaiter(this, void 0, void 0, function* () {
if (!(yield SharedWorkspaceLocking.DoesWorkspaceExist(workspace, buildParametersContext))) {
throw new Error("Workspace doesn't exist, can't call get all locks");
}
return (yield SharedWorkspaceLocking.ReadLines(`aws s3 ls ${SharedWorkspaceLocking.workspaceRoot}${buildParametersContext.cacheKey}/${workspace}/`)).map((x) => x.replace(`/`, ``));
});
}
static GetOrCreateLockedWorkspace(workspaceIfCreated, runId, buildParametersContext) {
return __awaiter(this, void 0, void 0, function* () {
if (!cloud_runner_options_1.default.retainWorkspaces) {
return;
}
cloud_runner_logger_1.default.log(`run agent ${runId} is trying to access a workspace`);
if (yield SharedWorkspaceLocking.DoesWorkspaceTopLevelExist(buildParametersContext)) {
const workspaces = yield SharedWorkspaceLocking.GetFreeWorkspaces(buildParametersContext);
for (const element of workspaces) {
if (yield SharedWorkspaceLocking.LockWorkspace(element, runId, buildParametersContext)) {
cloud_runner_logger_1.default.log(`run agent ${runId} locked workspace: ${element}`);
return element;
}
}
}
const workspace = yield SharedWorkspaceLocking.CreateWorkspace(workspaceIfCreated, buildParametersContext, runId);
cloud_runner_logger_1.default.log(`run agent ${runId} didn't find a free workspace so created: ${workspace}`);
return workspace;
});
}
static DoesWorkspaceExist(workspace, buildParametersContext) {
return __awaiter(this, void 0, void 0, function* () {
return (yield SharedWorkspaceLocking.GetAllWorkspaces(buildParametersContext)).includes(workspace);
});
}
static HasWorkspaceLock(workspace, runId, buildParametersContext) {
return __awaiter(this, void 0, void 0, function* () {
if (!(yield SharedWorkspaceLocking.DoesWorkspaceExist(workspace, buildParametersContext))) {
return false;
}
cloud_runner_logger_1.default.log(`Checking has workspace ${workspace} was locked`);
const locks = yield SharedWorkspaceLocking.GetAllLocks(workspace, buildParametersContext);
const includesRunLock = locks.filter((x) => x.includes(runId)).length > 0;
cloud_runner_logger_1.default.log(`Locks ${locks}, includes ${includesRunLock}`);
return includesRunLock;
});
}
static GetFreeWorkspaces(buildParametersContext) {
return __awaiter(this, void 0, void 0, function* () {
const result = [];
const workspaces = yield SharedWorkspaceLocking.GetAllWorkspaces(buildParametersContext);
for (const element of workspaces) {
if (!(yield SharedWorkspaceLocking.IsWorkspaceLocked(element, buildParametersContext))) {
result.push(element);
}
}
return result;
});
}
static IsWorkspaceLocked(workspace, buildParametersContext) {
return __awaiter(this, void 0, void 0, function* () {
if (!(yield SharedWorkspaceLocking.DoesWorkspaceExist(workspace, buildParametersContext))) {
return false;
}
const files = yield SharedWorkspaceLocking.ReadLines(`aws s3 ls ${SharedWorkspaceLocking.workspaceRoot}${buildParametersContext.cacheKey}/${workspace}/`);
const workspaceFileDoesNotExists = files.filter((x) => {
return x.includes(`_workspace`);
}).length === 0;
const lockFilesExist = files.filter((x) => {
return x.includes(`_lock`);
}).length > 0;
return workspaceFileDoesNotExists || lockFilesExist;
});
}
static CreateWorkspace(workspace, buildParametersContext, lockId = ``) {
return __awaiter(this, void 0, void 0, function* () {
if (lockId !== ``) {
yield SharedWorkspaceLocking.LockWorkspace(workspace, lockId, buildParametersContext);
}
const file = `${Date.now()}_workspace`;
fs.writeFileSync(file, '');
yield cloud_runner_system_1.CloudRunnerSystem.Run(`aws s3 cp ./${file} ${SharedWorkspaceLocking.workspaceRoot}${buildParametersContext.cacheKey}/${workspace}/${file}`, false, true);
fs.rmSync(file);
const workspaces = yield SharedWorkspaceLocking.ReadLines(`aws s3 ls ${SharedWorkspaceLocking.workspaceRoot}${buildParametersContext.cacheKey}/`);
cloud_runner_logger_1.default.log(`All workspaces ${workspaces}`);
return workspace;
});
}
static LockWorkspace(workspace, runId, buildParametersContext) {
return __awaiter(this, void 0, void 0, function* () {
const file = `${Date.now()}_${runId}_lock`;
fs.writeFileSync(file, '');
yield cloud_runner_system_1.CloudRunnerSystem.Run(`aws s3 cp ./${file} ${SharedWorkspaceLocking.workspaceRoot}${buildParametersContext.cacheKey}/${workspace}/${file}`, false, true);
fs.rmSync(file);
return SharedWorkspaceLocking.HasWorkspaceLock(workspace, runId, buildParametersContext);
});
}
static ReleaseWorkspace(workspace, runId, buildParametersContext) {
return __awaiter(this, void 0, void 0, function* () {
if (!(yield SharedWorkspaceLocking.DoesWorkspaceExist(workspace, buildParametersContext))) {
return true;
}
const file = (yield SharedWorkspaceLocking.GetAllLocks(workspace, buildParametersContext)).filter((x) => x.includes(`_${runId}_lock`));
cloud_runner_logger_1.default.log(`${JSON.stringify(yield SharedWorkspaceLocking.GetAllLocks(workspace, buildParametersContext))}`);
cloud_runner_logger_1.default.log(`Deleting file ${file}`);
cloud_runner_logger_1.default.log(`aws s3 rm ${SharedWorkspaceLocking.workspaceRoot}${buildParametersContext.cacheKey}/${workspace}/${file}`);
yield cloud_runner_system_1.CloudRunnerSystem.Run(`aws s3 rm ${SharedWorkspaceLocking.workspaceRoot}${buildParametersContext.cacheKey}/${workspace}/${file}`, false, true);
return !SharedWorkspaceLocking.HasWorkspaceLock(workspace, runId, buildParametersContext);
});
}
static CleanupWorkspace(workspace, buildParametersContext) {
return __awaiter(this, void 0, void 0, function* () {
yield cloud_runner_system_1.CloudRunnerSystem.Run(`aws s3 rm ${SharedWorkspaceLocking.workspaceRoot}${buildParametersContext.cacheKey}/${workspace} --recursive`, false, true);
});
}
static ReadLines(command) {
return __awaiter(this, void 0, void 0, function* () {
const result = yield cloud_runner_system_1.CloudRunnerSystem.Run(command, false, true);
return result
.split(`\n`)
.map((x) => x.replace(`\r`, ``))
.filter((x) => x !== ``)
.map((x) => {
const lineValues = x.split(` `);
return lineValues[lineValues.length - 1];
});
});
}
}
exports.SharedWorkspaceLocking = SharedWorkspaceLocking;
SharedWorkspaceLocking.workspaceRoot = `s3://game-ci-test-storage/locks/`;
exports["default"] = SharedWorkspaceLocking;
/***/ }), /***/ }),
/***/ 96552: /***/ 96552:
@ -5215,6 +5027,194 @@ __decorate([
exports.LfsHashing = LfsHashing; exports.LfsHashing = LfsHashing;
/***/ }),
/***/ 87562:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
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) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.SharedWorkspaceLocking = void 0;
const cloud_runner_system_1 = __nccwpck_require__(99393);
const fs = __importStar(__nccwpck_require__(57147));
const cloud_runner_logger_1 = __importDefault(__nccwpck_require__(22855));
const cloud_runner_options_1 = __importDefault(__nccwpck_require__(96552));
class SharedWorkspaceLocking {
static GetAllWorkspaces(buildParametersContext) {
return __awaiter(this, void 0, void 0, function* () {
return (yield SharedWorkspaceLocking.ReadLines(`aws s3 ls ${SharedWorkspaceLocking.workspaceRoot}${buildParametersContext.cacheKey}/`)).map((x) => x.replace(`/`, ``));
});
}
static DoesWorkspaceTopLevelExist(buildParametersContext) {
return __awaiter(this, void 0, void 0, function* () {
const results = (yield SharedWorkspaceLocking.ReadLines(`aws s3 ls ${SharedWorkspaceLocking.workspaceRoot}`)).map((x) => x.replace(`/`, ``));
return results.includes(buildParametersContext.cacheKey);
});
}
static GetAllLocks(workspace, buildParametersContext) {
return __awaiter(this, void 0, void 0, function* () {
if (!(yield SharedWorkspaceLocking.DoesWorkspaceExist(workspace, buildParametersContext))) {
throw new Error("Workspace doesn't exist, can't call get all locks");
}
return (yield SharedWorkspaceLocking.ReadLines(`aws s3 ls ${SharedWorkspaceLocking.workspaceRoot}${buildParametersContext.cacheKey}/${workspace}/`)).map((x) => x.replace(`/`, ``));
});
}
static GetOrCreateLockedWorkspace(workspaceIfCreated, runId, buildParametersContext) {
return __awaiter(this, void 0, void 0, function* () {
if (!cloud_runner_options_1.default.retainWorkspaces) {
return;
}
cloud_runner_logger_1.default.log(`run agent ${runId} is trying to access a workspace`);
if (yield SharedWorkspaceLocking.DoesWorkspaceTopLevelExist(buildParametersContext)) {
const workspaces = yield SharedWorkspaceLocking.GetFreeWorkspaces(buildParametersContext);
for (const element of workspaces) {
if (yield SharedWorkspaceLocking.LockWorkspace(element, runId, buildParametersContext)) {
cloud_runner_logger_1.default.log(`run agent ${runId} locked workspace: ${element}`);
return element;
}
}
}
const workspace = yield SharedWorkspaceLocking.CreateWorkspace(workspaceIfCreated, buildParametersContext, runId);
cloud_runner_logger_1.default.log(`run agent ${runId} didn't find a free workspace so created: ${workspace}`);
return workspace;
});
}
static DoesWorkspaceExist(workspace, buildParametersContext) {
return __awaiter(this, void 0, void 0, function* () {
return (yield SharedWorkspaceLocking.GetAllWorkspaces(buildParametersContext)).includes(workspace);
});
}
static HasWorkspaceLock(workspace, runId, buildParametersContext) {
return __awaiter(this, void 0, void 0, function* () {
if (!(yield SharedWorkspaceLocking.DoesWorkspaceExist(workspace, buildParametersContext))) {
return false;
}
cloud_runner_logger_1.default.log(`Checking has workspace ${workspace} was locked`);
const locks = yield SharedWorkspaceLocking.GetAllLocks(workspace, buildParametersContext);
const includesRunLock = locks.filter((x) => x.includes(runId)).length > 0;
cloud_runner_logger_1.default.log(`Locks ${locks}, includes ${includesRunLock}`);
return includesRunLock;
});
}
static GetFreeWorkspaces(buildParametersContext) {
return __awaiter(this, void 0, void 0, function* () {
const result = [];
const workspaces = yield SharedWorkspaceLocking.GetAllWorkspaces(buildParametersContext);
for (const element of workspaces) {
if (!(yield SharedWorkspaceLocking.IsWorkspaceLocked(element, buildParametersContext))) {
result.push(element);
}
}
return result;
});
}
static IsWorkspaceLocked(workspace, buildParametersContext) {
return __awaiter(this, void 0, void 0, function* () {
if (!(yield SharedWorkspaceLocking.DoesWorkspaceExist(workspace, buildParametersContext))) {
return false;
}
const files = yield SharedWorkspaceLocking.ReadLines(`aws s3 ls ${SharedWorkspaceLocking.workspaceRoot}${buildParametersContext.cacheKey}/${workspace}/`);
const workspaceFileDoesNotExists = files.filter((x) => {
return x.includes(`_workspace`);
}).length === 0;
const lockFilesExist = files.filter((x) => {
return x.includes(`_lock`);
}).length > 0;
return workspaceFileDoesNotExists || lockFilesExist;
});
}
static CreateWorkspace(workspace, buildParametersContext, lockId = ``) {
return __awaiter(this, void 0, void 0, function* () {
if (lockId !== ``) {
yield SharedWorkspaceLocking.LockWorkspace(workspace, lockId, buildParametersContext);
}
const file = `${Date.now()}_workspace`;
fs.writeFileSync(file, '');
yield cloud_runner_system_1.CloudRunnerSystem.Run(`aws s3 cp ./${file} ${SharedWorkspaceLocking.workspaceRoot}${buildParametersContext.cacheKey}/${workspace}/${file}`, false, true);
fs.rmSync(file);
const workspaces = yield SharedWorkspaceLocking.ReadLines(`aws s3 ls ${SharedWorkspaceLocking.workspaceRoot}${buildParametersContext.cacheKey}/`);
cloud_runner_logger_1.default.log(`All workspaces ${workspaces}`);
return workspace;
});
}
static LockWorkspace(workspace, runId, buildParametersContext) {
return __awaiter(this, void 0, void 0, function* () {
const file = `${Date.now()}_${runId}_lock`;
fs.writeFileSync(file, '');
yield cloud_runner_system_1.CloudRunnerSystem.Run(`aws s3 cp ./${file} ${SharedWorkspaceLocking.workspaceRoot}${buildParametersContext.cacheKey}/${workspace}/${file}`, false, true);
fs.rmSync(file);
return SharedWorkspaceLocking.HasWorkspaceLock(workspace, runId, buildParametersContext);
});
}
static ReleaseWorkspace(workspace, runId, buildParametersContext) {
return __awaiter(this, void 0, void 0, function* () {
if (!(yield SharedWorkspaceLocking.DoesWorkspaceExist(workspace, buildParametersContext))) {
return true;
}
const file = (yield SharedWorkspaceLocking.GetAllLocks(workspace, buildParametersContext)).filter((x) => x.includes(`_${runId}_lock`));
cloud_runner_logger_1.default.log(`${JSON.stringify(yield SharedWorkspaceLocking.GetAllLocks(workspace, buildParametersContext))}`);
cloud_runner_logger_1.default.log(`Deleting file ${file}`);
cloud_runner_logger_1.default.log(`aws s3 rm ${SharedWorkspaceLocking.workspaceRoot}${buildParametersContext.cacheKey}/${workspace}/${file}`);
yield cloud_runner_system_1.CloudRunnerSystem.Run(`aws s3 rm ${SharedWorkspaceLocking.workspaceRoot}${buildParametersContext.cacheKey}/${workspace}/${file}`, false, true);
return !SharedWorkspaceLocking.HasWorkspaceLock(workspace, runId, buildParametersContext);
});
}
static CleanupWorkspace(workspace, buildParametersContext) {
return __awaiter(this, void 0, void 0, function* () {
yield cloud_runner_system_1.CloudRunnerSystem.Run(`aws s3 rm ${SharedWorkspaceLocking.workspaceRoot}${buildParametersContext.cacheKey}/${workspace} --recursive`, false, true);
});
}
static ReadLines(command) {
return __awaiter(this, void 0, void 0, function* () {
const result = yield cloud_runner_system_1.CloudRunnerSystem.Run(command, false, true);
return result
.split(`\n`)
.map((x) => x.replace(`\r`, ``))
.filter((x) => x !== ``)
.map((x) => {
const lineValues = x.split(` `);
return lineValues[lineValues.length - 1];
});
});
}
}
exports.SharedWorkspaceLocking = SharedWorkspaceLocking;
SharedWorkspaceLocking.workspaceRoot = `s3://game-ci-test-storage/locks/`;
exports["default"] = SharedWorkspaceLocking;
/***/ }), /***/ }),
/***/ 35346: /***/ 35346:
@ -5457,7 +5457,7 @@ const cloud_runner_custom_hooks_1 = __nccwpck_require__(58873);
const path_1 = __importDefault(__nccwpck_require__(71017)); const path_1 = __importDefault(__nccwpck_require__(71017));
const cloud_runner_1 = __importDefault(__nccwpck_require__(79144)); const cloud_runner_1 = __importDefault(__nccwpck_require__(79144));
const cloud_runner_options_1 = __importDefault(__nccwpck_require__(96552)); const cloud_runner_options_1 = __importDefault(__nccwpck_require__(96552));
const shared_workspace_locking_1 = __importDefault(__nccwpck_require__(67170)); const shared_workspace_locking_1 = __importDefault(__nccwpck_require__(87562));
const cloud_runner_custom_steps_1 = __nccwpck_require__(96455); const cloud_runner_custom_steps_1 = __nccwpck_require__(96455);
class BuildAutomationWorkflow { class BuildAutomationWorkflow {
run(cloudRunnerStepState) { run(cloudRunnerStepState) {

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

View File

@ -1,8 +1,8 @@
import { CloudRunnerSystem } from '../cloud-runner/services/cloud-runner-system'; import { CloudRunnerSystem } from './cloud-runner-system';
import * as fs from 'fs'; import * as fs from 'fs';
import CloudRunnerLogger from '../cloud-runner/services/cloud-runner-logger'; import CloudRunnerLogger from './cloud-runner-logger';
import CloudRunnerOptions from '../cloud-runner/cloud-runner-options'; import CloudRunnerOptions from '../cloud-runner-options';
import BuildParameters from '../build-parameters'; import BuildParameters from '../../build-parameters';
export class SharedWorkspaceLocking { export class SharedWorkspaceLocking {
private static readonly workspaceRoot = `s3://game-ci-test-storage/locks/`; private static readonly workspaceRoot = `s3://game-ci-test-storage/locks/`;
public static async GetAllWorkspaces(buildParametersContext: BuildParameters): Promise<string[]> { public static async GetAllWorkspaces(buildParametersContext: BuildParameters): Promise<string[]> {

View File

@ -1,4 +1,4 @@
import SharedWorkspaceLocking from '../../cli/shared-workspace-locking'; import SharedWorkspaceLocking from '../services/shared-workspace-locking';
import { Cli } from '../../cli/cli'; import { Cli } from '../../cli/cli';
import setups from './cloud-runner-suite.test'; import setups from './cloud-runner-suite.test';
import CloudRunnerLogger from '../services/cloud-runner-logger'; import CloudRunnerLogger from '../services/cloud-runner-logger';

View File

@ -7,7 +7,7 @@ import { CloudRunnerCustomHooks } from '../services/cloud-runner-custom-hooks';
import path from 'path'; import path from 'path';
import CloudRunner from '../cloud-runner'; import CloudRunner from '../cloud-runner';
import CloudRunnerOptions from '../cloud-runner-options'; import CloudRunnerOptions from '../cloud-runner-options';
import SharedWorkspaceLocking from '../../cli/shared-workspace-locking'; import SharedWorkspaceLocking from '../services/shared-workspace-locking';
import { CloudRunnerCustomSteps } from '../services/cloud-runner-custom-steps'; import { CloudRunnerCustomSteps } from '../services/cloud-runner-custom-steps';
export class BuildAutomationWorkflow implements WorkflowInterface { export class BuildAutomationWorkflow implements WorkflowInterface {