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
logs/*
!**/.gitkeep
.env*
!.env*.dist

View File

@ -6,4 +6,6 @@ export interface CommandInterface {
name: string;
execute: (options: Options) => Promise<boolean>;
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 { Options } from '../../../config/options.ts';
import { Action, Cache, Docker, ImageTag, Input, Output } from '../../../model/index.ts';
import PlatformSetup from '../../../model/platform-setup.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 {
public readonly name: string;
constructor(name: string) {
this.name = name;
export class BuildCommand extends CommandBase implements CommandInterface {
public async validate() {
await super.validate();
}
public async parseParameters(input: Input, parameters: Parameters) {}
public async execute(options: Options): Promise<boolean> {
public async execute(): Promise<boolean> {
try {
const { workspace, actionFolder } = Action;
const { parameters, env } = options;
const { parameters, env } = this.options;
Action.checkCompatibility();
Cache.verify();
@ -35,24 +29,24 @@ export class BuildCommand implements CommandInterface {
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
await Output.setBuildVersion(parameters.buildVersion);
} catch (error) {
log.error(error);
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 CloudRunnerBuildGuid from '../../../model/cloud-runner/services/cloud-runner-guid.ts';
import { GithubCliReader } from '../../../model/input-readers/github-cli.ts';
import { CommandBase } from './command-base.ts';
// Todo - Verify this entire flow
export class BuildRemoteCommand implements CommandInterface {
public readonly name: string;
constructor(name: string) {
this.name = name;
export class BuildRemoteCommand extends CommandBase implements CommandInterface {
public async validate() {
await super.validate();
}
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 {
cloudRunnerCluster,
cloudRunnerBranch: input.cloudRunnerBranch.split('/').reverse()[0],
cloudRunnerIntegrationTests: input.cloudRunnerTests,
githubRepo: input.githubRepo || (await GitRepoReader.GetRemote()) || 'game-ci/unity-builder',
gitPrivateToken: parameters.gitPrivateToken || (await GithubCliReader.GetGitHubAuthToken()),
isCliMode: Cli.isCliMode,
awsStackName: input.awsBaseStackName,
cloudRunnerCluster: input.cloudRunnerCluster,
cloudRunnerBuilderPlatform: input.cloudRunnerBuilderPlatform,
awsBaseStackName: input.awsBaseStackName,
kubeConfig: input.kubeConfig,
@ -52,19 +55,14 @@ export class BuildRemoteCommand implements CommandInterface {
}
public async execute(options: Options): Promise<boolean> {
try {
const { buildParameters } = options;
const baseImage = new ImageTag(buildParameters);
const { buildParameters } = options;
const baseImage = new ImageTag(buildParameters);
const result = await CloudRunner.run(buildParameters, baseImage.toString());
const { status, output } = result;
const result = await CloudRunner.run(buildParameters, baseImage.toString());
const { status, output } = result;
await Output.setBuildVersion(buildParameters.buildVersion);
await Output.setBuildVersion(buildParameters.buildVersion);
return status.success;
} catch (error) {
log.error(error);
Deno.exit(1);
}
return status.success;
}
}

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 arch: string;
constructor(env: Deno.env) {
constructor(env: Deno.env, envFile: EnvVariables) {
// Make an immutable copy of the environment variables.
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.

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 { 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 { config, configSync } from 'https://deno.land/std@0.151.0/dotenv/mod.ts';
// Internally managed packages
import waitUntil from './modules/wait-until.ts';
@ -53,6 +54,8 @@ export {
Buffer,
Command,
compress,
config,
configSync,
core,
crypto,
exec,

View File

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

View File

@ -2,13 +2,13 @@ import { fsSync as fs } from '../../../dependencies.ts';
import { Parameters } from '../../../model/index.ts';
class ValidateWindows {
public static validate(buildParameters: Parameters) {
ValidateWindows.validateWindowsPlatformRequirements(buildParameters.targetPlatform);
if (!(Deno.env.get('UNITY_EMAIL') && Deno.env.get('UNITY_PASSWORD'))) {
public static validate(parameters: Parameters) {
ValidateWindows.validateWindowsPlatformRequirements(parameters.targetPlatform);
if (!parameters.unityEmail || !parameters.unityPassword) {
throw new Error(String.dedent`
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');
if (!windows10SDKPathExists) {
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/
`);
@ -54,7 +54,7 @@ class ValidateWindows {
throw new Error(String.dedent`
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/
`);

View File

@ -25,14 +25,17 @@ class ImageEnvironmentFactory {
return string;
}
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[] = [
{ name: 'UNITY_LICENSE', value: Deno.env.get('UNITY_LICENSE') || ReadLicense() },
{ name: 'UNITY_LICENSE_FILE', value: Deno.env.get('UNITY_LICENSE_FILE') },
{ name: 'UNITY_EMAIL', value: Deno.env.get('UNITY_EMAIL') },
{ name: 'UNITY_PASSWORD', value: Deno.env.get('UNITY_PASSWORD') },
{ name: 'UNITY_LICENSE', value: parameters.unityLicense || ReadLicense(parameters) },
{ name: 'UNITY_LICENSE_FILE', value: parameters.unityLicenseFile },
{ name: 'UNITY_EMAIL', value: parameters.unityEmail },
{ name: 'UNITY_PASSWORD', value: parameters.unityPassword },
{ name: 'UNITY_SERIAL', value: parameters.unitySerial },
{ 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: 'BUILD_TARGET', value: parameters.targetPlatform },
{ name: 'BUILD_NAME', value: parameters.buildName },

View File

@ -1,10 +1,11 @@
import { fsSync as fs, path, yaml, __dirname } from '../../dependencies.ts';
import Input from '../input.ts';
export function ReadLicense() {
if (Input.cloudRunnerCluster === 'local') {
export function ReadLicense(parameters) {
if (parameters.cloudRunnerCluster === 'local') {
return '';
}
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 : '';

View File

@ -113,6 +113,30 @@ class Input {
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() {
return this.getInput('customImage') || '';
}
@ -248,15 +272,6 @@ class Input {
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() {
return this.getInput('cloudRunnerCpu');
}

View File

@ -98,15 +98,24 @@ class Parameters {
);
log.debug('androidSdkManagerParameters', androidSdkManagerParameters);
let unitySerial = '';
if (!this.env.UNITY_SERIAL && this.input.githubInputEnabled) {
// Commandline takes precedence over environment variables
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
if (!this.env.UNITY_LICENSE) {
throw new Error(`Missing Unity License File and no Serial was found. If this
is a personal license, make sure to follow the activation
steps and set the UNITY_LICENSE GitHub secret or enter a Unity
serial number inside the UNITY_SERIAL GitHub secret.`);
if (!unityLicense) {
throw new Error(String.dedent`
Missing Unity License File and no Serial was found. If this is a personal license,
make sure to follow the activation steps and set the UNITY_LICENSE variable or enter
a Unity serial number inside the UNITY_SERIAL variable.
`);
}
unitySerial = this.getSerialFromLicenseFile(this.env.UNITY_LICENSE);
} else {
unitySerial = this.env.UNITY_SERIAL!;
@ -124,7 +133,12 @@ class Parameters {
const parameters = {
editorVersion,
customImage: this.input.customImage,
unityEmail,
unityPassword,
unityLicense,
unityLicenseFile,
unitySerial,
usymUploadAuthToken: this.input.usymUploadAuthToken || this.env.get('USYM_UPLOAD_AUTH_TOKEN'),
runnerTempPath: this.env.RUNNER_TEMP,
targetPlatform,
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';
class PlatformSetup {
static async setup(buildParameters: Parameters, actionFolder: string) {
static async setup(parameters: Parameters, actionFolder: string) {
switch (process.platform) {
case 'win32':
ValidateWindows.validate(buildParameters);
SetupWindows.setup(buildParameters);
ValidateWindows.validate(parameters);
SetupWindows.setup(parameters);
break;
case 'darwin':
await SetupMac.setup(buildParameters, actionFolder);
await SetupMac.setup(parameters, actionFolder);
break;
// Add other baseOS's here