chore: sanitise more parameters

pull/413/head
Webber 2022-08-20 23:10:13 +02:00
parent 0379bceff3
commit 2574cad919
14 changed files with 159 additions and 83 deletions

2
.gitignore vendored
View File

@ -8,3 +8,5 @@ yarn-error.log
*.log *.log
logs/* logs/*
!**/.gitkeep !**/.gitkeep
.env*
!.env*.dist

View File

@ -6,4 +6,6 @@ export interface CommandInterface {
name: string; name: string;
execute: (options: Options) => Promise<boolean>; execute: (options: Options) => Promise<boolean>;
parseParameters: (input: Input, parameters: Parameters) => Promise<Partial<Parameters>>; parseParameters: (input: Input, parameters: Parameters) => Promise<Partial<Parameters>>;
configure(options: Options): this;
validate(): Promise<this>;
} }

View File

@ -1,24 +1,18 @@
import { exec, OutputMode } from 'https://deno.land/x/exec@0.0.5/mod.ts';
import { CommandInterface } from '../command-interface.ts'; import { CommandInterface } from '../command-interface.ts';
import { Options } from '../../../config/options.ts';
import { Action, Cache, Docker, ImageTag, Input, Output } from '../../../model/index.ts'; import { Action, Cache, Docker, ImageTag, Input, Output } from '../../../model/index.ts';
import PlatformSetup from '../../../model/platform-setup.ts'; import PlatformSetup from '../../../model/platform-setup.ts';
import MacBuilder from '../../../model/mac-builder.ts'; import MacBuilder from '../../../model/mac-builder.ts';
import Parameters from '../../../model/parameters.ts'; import { CommandBase } from './command-base.ts';
export class BuildCommand implements CommandInterface { export class BuildCommand extends CommandBase implements CommandInterface {
public readonly name: string; public async validate() {
await super.validate();
constructor(name: string) {
this.name = name;
} }
public async parseParameters(input: Input, parameters: Parameters) {} public async execute(): Promise<boolean> {
public async execute(options: Options): Promise<boolean> {
try { try {
const { workspace, actionFolder } = Action; const { workspace, actionFolder } = Action;
const { parameters, env } = options; const { parameters, env } = this.options;
Action.checkCompatibility(); Action.checkCompatibility();
Cache.verify(); Cache.verify();
@ -35,24 +29,24 @@ export class BuildCommand implements CommandInterface {
await Docker.run(baseImage, { workspace, actionFolder, ...parameters }); await Docker.run(baseImage, { workspace, actionFolder, ...parameters });
} }
// const result = await exec('docker run -it unityci/editor:2020.3.15f2-base-1 /bin/bash -c "echo test"', {
// output: OutputMode.Capture,
// continueOnError: true,
//
// // verbose: true,
// });
//
// log.info('result', result.output);
// const { success } = result.status;
// log.info('success', success);
//
// return success;
// Set output // Set output
await Output.setBuildVersion(parameters.buildVersion); await Output.setBuildVersion(parameters.buildVersion);
} catch (error) { } catch (error) {
log.error(error); log.error(error);
Deno.exit(1); Deno.exit(1);
} }
const result = await exec('docker run -it unityci/editor:2020.3.15f2-base-1 /bin/bash -c "echo test"', {
output: OutputMode.Capture,
continueOnError: true,
// verbose: true,
});
log.info('result', result.output);
const { success } = result.status;
log.info('success', success);
return success;
} }
} }

View File

@ -8,24 +8,27 @@ import { Cli } from '../../../model/cli/cli.ts';
import CloudRunnerConstants from '../../../model/cloud-runner/services/cloud-runner-constants.ts'; import CloudRunnerConstants from '../../../model/cloud-runner/services/cloud-runner-constants.ts';
import CloudRunnerBuildGuid from '../../../model/cloud-runner/services/cloud-runner-guid.ts'; import CloudRunnerBuildGuid from '../../../model/cloud-runner/services/cloud-runner-guid.ts';
import { GithubCliReader } from '../../../model/input-readers/github-cli.ts'; import { GithubCliReader } from '../../../model/input-readers/github-cli.ts';
import { CommandBase } from './command-base.ts';
// Todo - Verify this entire flow // Todo - Verify this entire flow
export class BuildRemoteCommand implements CommandInterface { export class BuildRemoteCommand extends CommandBase implements CommandInterface {
public readonly name: string; public async validate() {
await super.validate();
constructor(name: string) {
this.name = name;
} }
public async parseParameters(input: Input, parameters: Parameters) { public async parseParameters(input: Input, parameters: Parameters): Promise<Partial<Parameters>> {
const cloudRunnerCluster = Cli.isCliMode
? this.input.getInput('cloudRunnerCluster') || 'aws'
: this.input.getInput('cloudRunnerCluster') || 'local';
return { return {
cloudRunnerCluster,
cloudRunnerBranch: input.cloudRunnerBranch.split('/').reverse()[0], cloudRunnerBranch: input.cloudRunnerBranch.split('/').reverse()[0],
cloudRunnerIntegrationTests: input.cloudRunnerTests, cloudRunnerIntegrationTests: input.cloudRunnerTests,
githubRepo: input.githubRepo || (await GitRepoReader.GetRemote()) || 'game-ci/unity-builder', githubRepo: input.githubRepo || (await GitRepoReader.GetRemote()) || 'game-ci/unity-builder',
gitPrivateToken: parameters.gitPrivateToken || (await GithubCliReader.GetGitHubAuthToken()), gitPrivateToken: parameters.gitPrivateToken || (await GithubCliReader.GetGitHubAuthToken()),
isCliMode: Cli.isCliMode, isCliMode: Cli.isCliMode,
awsStackName: input.awsBaseStackName, awsStackName: input.awsBaseStackName,
cloudRunnerCluster: input.cloudRunnerCluster,
cloudRunnerBuilderPlatform: input.cloudRunnerBuilderPlatform, cloudRunnerBuilderPlatform: input.cloudRunnerBuilderPlatform,
awsBaseStackName: input.awsBaseStackName, awsBaseStackName: input.awsBaseStackName,
kubeConfig: input.kubeConfig, kubeConfig: input.kubeConfig,
@ -52,19 +55,14 @@ export class BuildRemoteCommand implements CommandInterface {
} }
public async execute(options: Options): Promise<boolean> { public async execute(options: Options): Promise<boolean> {
try { const { buildParameters } = options;
const { buildParameters } = options; const baseImage = new ImageTag(buildParameters);
const baseImage = new ImageTag(buildParameters);
const result = await CloudRunner.run(buildParameters, baseImage.toString()); const result = await CloudRunner.run(buildParameters, baseImage.toString());
const { status, output } = result; const { status, output } = result;
await Output.setBuildVersion(buildParameters.buildVersion); await Output.setBuildVersion(buildParameters.buildVersion);
return status.success; return status.success;
} catch (error) {
log.error(error);
Deno.exit(1);
}
} }
} }

View File

@ -0,0 +1,30 @@
import { Options } from '../../../config/options.ts';
import { Input } from '../../../model/index.ts';
import Parameters from '../../../model/parameters.ts';
export class CommandBase {
public readonly name: string;
private options: Options;
constructor(name: string) {
this.name = name;
}
public configure(options: Options): this {
this.options = options;
return this;
}
public async validate(): Promise<this> {
return this;
}
public async parseParameters(input: Input, parameters: Parameters): Promise<Partial<Parameters>> {
return {};
}
public async execute(): Promise<boolean> {
throw new Error('Method not implemented.');
}
}

View File

@ -4,10 +4,21 @@ export class Environment implements EnvVariables {
public readonly os: string; public readonly os: string;
public readonly arch: string; public readonly arch: string;
constructor(env: Deno.env) { constructor(env: Deno.env, envFile: EnvVariables) {
// Make an immutable copy of the environment variables. // Make an immutable copy of the environment variables.
for (const [key, value] of Object.entries(env.toObject())) { for (const [key, value] of Object.entries(env.toObject())) {
if (value !== undefined) this[key] = value; // Todo - check if this ever happens at all
if (value === undefined) {
// eslint-disable-next-line no-console
console.error(`Environment variable ${key} is undefined.`);
}
this[key] = value;
}
// Override any env variables that are set in a .env file.
for (const [key, value] of Object.entries(envFile)) {
this[key] = value;
} }
// Override specific variables. // Override specific variables.

View File

@ -19,6 +19,7 @@ import * as string from 'https://deno.land/std@0.36.0/strings/mod.ts';
import { Command } from 'https://deno.land/x/cmd@v1.2.0/commander/index.ts'; import { Command } from 'https://deno.land/x/cmd@v1.2.0/commander/index.ts';
import { getUnityChangeset as getUnityChangeSet } from 'https://deno.land/x/unity_changeset@2.0.0/src/index.ts'; import { getUnityChangeset as getUnityChangeSet } from 'https://deno.land/x/unity_changeset@2.0.0/src/index.ts';
import { Buffer } from 'https://deno.land/std@0.151.0/io/buffer.ts'; import { Buffer } from 'https://deno.land/std@0.151.0/io/buffer.ts';
import { config, configSync } from 'https://deno.land/std@0.151.0/dotenv/mod.ts';
// Internally managed packages // Internally managed packages
import waitUntil from './modules/wait-until.ts'; import waitUntil from './modules/wait-until.ts';
@ -53,6 +54,8 @@ export {
Buffer, Buffer,
Command, Command,
compress, compress,
config,
configSync,
core, core,
crypto, crypto,
exec, exec,

View File

@ -1,4 +1,4 @@
import './dependencies.ts'; import { configSync } from './dependencies.ts';
import { configureLogger } from './core/logger/index.ts'; import { configureLogger } from './core/logger/index.ts';
import { Options } from './config/options.ts'; import { Options } from './config/options.ts';
import { CommandFactory } from './commands/command-factory.ts'; import { CommandFactory } from './commands/command-factory.ts';
@ -10,23 +10,26 @@ export class GameCI {
private readonly env: Environment; private readonly env: Environment;
constructor() { constructor() {
this.env = new Environment(Deno.env); this.env = new Environment(Deno.env, configSync());
this.args = Deno.args; this.args = Deno.args;
} }
public async run() { public async run() {
try { try {
// Infallible configuration
const { commandName, subCommands, args, verbosity } = new ArgumentsParser().parse(this.args); const { commandName, subCommands, args, verbosity } = new ArgumentsParser().parse(this.args);
await configureLogger(verbosity); await configureLogger(verbosity);
// Determine the command and its options
const { engine, engineVersion } = await new EngineDetector(subCommands, args).detect(); const { engine, engineVersion } = await new EngineDetector(subCommands, args).detect();
const command = new CommandFactory().selectEngine(engine, engineVersion).createCommand(commandName, subCommands); const command = new CommandFactory().selectEngine(engine, engineVersion).createCommand(commandName, subCommands);
const options = await new Options(command, this.env).registerCommand(command).generateParameters(args); const options = await new Options(command, this.env).registerCommand(command).generateParameters(args);
await command.configure(options).validate();
// Execute
if (log.isVerbose) log.info('Executing', command.name); if (log.isVerbose) log.info('Executing', command.name);
const success = await command.execute();
await command.execute(options); if (!success) log.warning(`Command ${command.name} failed.`);
} catch (error) { } catch (error) {
log.error(error); log.error(error);
Deno.exit(1); Deno.exit(1);

View File

@ -2,13 +2,13 @@ import { fsSync as fs } from '../../../dependencies.ts';
import { Parameters } from '../../../model/index.ts'; import { Parameters } from '../../../model/index.ts';
class ValidateWindows { class ValidateWindows {
public static validate(buildParameters: Parameters) { public static validate(parameters: Parameters) {
ValidateWindows.validateWindowsPlatformRequirements(buildParameters.targetPlatform); ValidateWindows.validateWindowsPlatformRequirements(parameters.targetPlatform);
if (!(Deno.env.get('UNITY_EMAIL') && Deno.env.get('UNITY_PASSWORD'))) { if (!parameters.unityEmail || !parameters.unityPassword) {
throw new Error(String.dedent` throw new Error(String.dedent`
Unity email and password must be set for Windows based builds to authenticate the license. Unity email and password must be set for Windows based builds to authenticate the license.
Make sure to set them inside UNITY_EMAIL and UNITY_PASSWORD in Github Secrets and pass them into the environment. Please make sure to set the unityEmail (UNITY_EMAIL) and unityPassword (UNITY_PASSWORD) parameters.
`); `);
} }
} }
@ -38,7 +38,7 @@ class ValidateWindows {
const windows10SDKPathExists = fs.existsSync('C:/Program Files (x86)/Windows Kits'); const windows10SDKPathExists = fs.existsSync('C:/Program Files (x86)/Windows Kits');
if (!windows10SDKPathExists) { if (!windows10SDKPathExists) {
throw new Error(String.dedent` throw new Error(String.dedent`
Windows 10 SDK not found in default location. Make sure this machine has a Windows 10 SDK installed. Windows 10 SDK not found in default location. Please make sure this machine has a Windows 10 SDK installed.
Download here: https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/ Download here: https://developer.microsoft.com/en-us/windows/downloads/windows-sdk/
`); `);
@ -54,7 +54,7 @@ class ValidateWindows {
throw new Error(String.dedent` throw new Error(String.dedent`
Visual Studio not found at the default location. Visual Studio not found at the default location.
Make sure the runner has Visual Studio installed in the default location Please make sure the runner has Visual Studio installed in the default location
Download here: https://visualstudio.microsoft.com/downloads/ Download here: https://visualstudio.microsoft.com/downloads/
`); `);

View File

@ -25,14 +25,17 @@ class ImageEnvironmentFactory {
return string; return string;
} }
public static getEnvironmentVariables(parameters: Parameters) { public static getEnvironmentVariables(parameters: Parameters) {
// Todo - replace with simple for of loop, mapping parameters to this specific format
// All parameters should be straight forward at this point in the process.
// We can convert between camelCase and UPPER_SNAKE_CASE relatively easily.
const environmentVariables: Parameter[] = [ const environmentVariables: Parameter[] = [
{ name: 'UNITY_LICENSE', value: Deno.env.get('UNITY_LICENSE') || ReadLicense() }, { name: 'UNITY_LICENSE', value: parameters.unityLicense || ReadLicense(parameters) },
{ name: 'UNITY_LICENSE_FILE', value: Deno.env.get('UNITY_LICENSE_FILE') }, { name: 'UNITY_LICENSE_FILE', value: parameters.unityLicenseFile },
{ name: 'UNITY_EMAIL', value: Deno.env.get('UNITY_EMAIL') }, { name: 'UNITY_EMAIL', value: parameters.unityEmail },
{ name: 'UNITY_PASSWORD', value: Deno.env.get('UNITY_PASSWORD') }, { name: 'UNITY_PASSWORD', value: parameters.unityPassword },
{ name: 'UNITY_SERIAL', value: parameters.unitySerial }, { name: 'UNITY_SERIAL', value: parameters.unitySerial },
{ name: 'UNITY_VERSION', value: parameters.editorVersion }, { name: 'UNITY_VERSION', value: parameters.editorVersion },
{ name: 'USYM_UPLOAD_AUTH_TOKEN', value: Deno.env.get('USYM_UPLOAD_AUTH_TOKEN') }, { name: 'USYM_UPLOAD_AUTH_TOKEN', value: parameters.uploadAuthToken },
{ name: 'PROJECT_PATH', value: parameters.projectPath }, { name: 'PROJECT_PATH', value: parameters.projectPath },
{ name: 'BUILD_TARGET', value: parameters.targetPlatform }, { name: 'BUILD_TARGET', value: parameters.targetPlatform },
{ name: 'BUILD_NAME', value: parameters.buildName }, { name: 'BUILD_NAME', value: parameters.buildName },

View File

@ -1,10 +1,11 @@
import { fsSync as fs, path, yaml, __dirname } from '../../dependencies.ts'; import { fsSync as fs, path, yaml, __dirname } from '../../dependencies.ts';
import Input from '../input.ts'; import Input from '../input.ts';
export function ReadLicense() { export function ReadLicense(parameters) {
if (Input.cloudRunnerCluster === 'local') { if (parameters.cloudRunnerCluster === 'local') {
return ''; return '';
} }
const pipelineFile = path.join(__dirname, `.github`, `workflows`, `cloud-runner-k8s-pipeline.yml`); const pipelineFile = path.join(__dirname, `.github`, `workflows`, `cloud-runner-k8s-pipeline.yml`);
return fs.existsSync(pipelineFile) ? yaml.parse(Deno.readTextFileSync(pipelineFile, 'utf8')).env.UNITY_LICENSE : ''; return fs.existsSync(pipelineFile) ? yaml.parse(Deno.readTextFileSync(pipelineFile, 'utf8')).env.UNITY_LICENSE : '';

View File

@ -113,6 +113,30 @@ class Input {
return this.getInput('unityVersion') || 'auto'; return this.getInput('unityVersion') || 'auto';
} }
public get unityEmail() {
return this.getInput('unityEmail') || '';
}
public get unityPassword() {
return this.getInput('unityPassword') || '';
}
public get unityLicense() {
return this.getInput('unityLicense') || '';
}
public get unityLicenseFile() {
return this.getInput('unityLicenseFile') || '';
}
public get unitySerial() {
return this.getInput('unitySerial') || '';
}
public get usymUploadAuthToken() {
return this.getInput('usymUploadAuthToken') || '';
}
public get customImage() { public get customImage() {
return this.getInput('customImage') || ''; return this.getInput('customImage') || '';
} }
@ -248,15 +272,6 @@ class Input {
return this.getInput('awsBaseStackName') || 'game-ci'; return this.getInput('awsBaseStackName') || 'game-ci';
} }
// Todo - move to parameters
public static get cloudRunnerCluster() {
if (Cli.isCliMode) {
return this.getInput('cloudRunnerCluster') || 'aws';
}
return this.getInput('cloudRunnerCluster') || 'local';
}
public get cloudRunnerCpu() { public get cloudRunnerCpu() {
return this.getInput('cloudRunnerCpu'); return this.getInput('cloudRunnerCpu');
} }

View File

@ -98,15 +98,24 @@ class Parameters {
); );
log.debug('androidSdkManagerParameters', androidSdkManagerParameters); log.debug('androidSdkManagerParameters', androidSdkManagerParameters);
let unitySerial = ''; // Commandline takes precedence over environment variables
if (!this.env.UNITY_SERIAL && this.input.githubInputEnabled) { const unityEmail = this.input.unityEmail || this.env.get('UNITY_EMAIL');
const unityPassword = this.input.unityPassword || this.env.get('UNITY_PASSWORD');
const unityLicense = this.input.unityLicense || this.env.get('UNITY_LICENSE');
const unityLicenseFile = this.input.unityLicenseFile || this.env.get('UNITY_LICENSE_FILE');
let unitySerial = this.input.unitySerial || this.env.get('UNITY_SERIAL');
// For Windows, we need to use the serial from the license file
if (!unitySerial && this.input.githubInputEnabled) {
// No serial was present, so it is a personal license that we need to convert // No serial was present, so it is a personal license that we need to convert
if (!this.env.UNITY_LICENSE) { if (!unityLicense) {
throw new Error(`Missing Unity License File and no Serial was found. If this throw new Error(String.dedent`
is a personal license, make sure to follow the activation Missing Unity License File and no Serial was found. If this is a personal license,
steps and set the UNITY_LICENSE GitHub secret or enter a Unity make sure to follow the activation steps and set the UNITY_LICENSE variable or enter
serial number inside the UNITY_SERIAL GitHub secret.`); a Unity serial number inside the UNITY_SERIAL variable.
`);
} }
unitySerial = this.getSerialFromLicenseFile(this.env.UNITY_LICENSE); unitySerial = this.getSerialFromLicenseFile(this.env.UNITY_LICENSE);
} else { } else {
unitySerial = this.env.UNITY_SERIAL!; unitySerial = this.env.UNITY_SERIAL!;
@ -124,7 +133,12 @@ class Parameters {
const parameters = { const parameters = {
editorVersion, editorVersion,
customImage: this.input.customImage, customImage: this.input.customImage,
unityEmail,
unityPassword,
unityLicense,
unityLicenseFile,
unitySerial, unitySerial,
usymUploadAuthToken: this.input.usymUploadAuthToken || this.env.get('USYM_UPLOAD_AUTH_TOKEN'),
runnerTempPath: this.env.RUNNER_TEMP, runnerTempPath: this.env.RUNNER_TEMP,
targetPlatform, targetPlatform,
projectPath, projectPath,

View File

@ -3,14 +3,14 @@ import { SetupMac, SetupWindows } from '../logic/unity/platform-setup/index.ts';
import ValidateWindows from '../logic/unity/platform-validation/validate-windows.ts'; import ValidateWindows from '../logic/unity/platform-validation/validate-windows.ts';
class PlatformSetup { class PlatformSetup {
static async setup(buildParameters: Parameters, actionFolder: string) { static async setup(parameters: Parameters, actionFolder: string) {
switch (process.platform) { switch (process.platform) {
case 'win32': case 'win32':
ValidateWindows.validate(buildParameters); ValidateWindows.validate(parameters);
SetupWindows.setup(buildParameters); SetupWindows.setup(parameters);
break; break;
case 'darwin': case 'darwin':
await SetupMac.setup(buildParameters, actionFolder); await SetupMac.setup(parameters, actionFolder);
break; break;
// Add other baseOS's here // Add other baseOS's here