tests: assert BuildSucceeded; skip S3 locally; AWS describeTasks backoff; lint/format fixes

pull/729/head
Frostebite 2025-09-03 20:49:52 +01:00
parent eb8b92cda1
commit c8f881a385
6 changed files with 67 additions and 65 deletions

33
dist/index.js generated vendored
View File

@ -1954,7 +1954,11 @@ class AWSTaskRunner {
while (exitCode === undefined) {
await new Promise((resolve) => resolve(10000));
taskData = await AWSTaskRunner.describeTasks(cluster, taskArn);
containerState = taskData.containers?.[0];
const containers = taskData?.containers;
if (!containers || containers.length === 0) {
continue;
}
containerState = containers[0];
exitCode = containerState?.exitCode;
}
cloud_runner_logger_1.default.log(`Container State: ${JSON.stringify(containerState, undefined, 4)}`);
@ -1981,18 +1985,31 @@ class AWSTaskRunner {
catch (error_) {
const error = error_;
await new Promise((resolve) => setTimeout(resolve, 3000));
cloud_runner_logger_1.default.log(`Cloud runner job has ended ${(await AWSTaskRunner.describeTasks(cluster, taskArn)).containers?.[0].lastStatus}`);
const taskAfterError = await AWSTaskRunner.describeTasks(cluster, taskArn);
cloud_runner_logger_1.default.log(`Cloud runner job has ended ${taskAfterError?.containers?.[0]?.lastStatus}`);
core.setFailed(error);
core.error(error);
}
}
static async describeTasks(clusterName, taskArn) {
const tasks = await AWSTaskRunner.ECS.send(new client_ecs_1.DescribeTasksCommand({ cluster: clusterName, tasks: [taskArn] }));
if (tasks.tasks?.[0]) {
return tasks.tasks?.[0];
}
else {
throw new Error('No task found');
const maxAttempts = 6;
let delayMs = 500;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
const tasks = await AWSTaskRunner.ECS.send(new client_ecs_1.DescribeTasksCommand({ cluster: clusterName, tasks: [taskArn] }));
if (tasks.tasks?.[0]) {
return tasks.tasks?.[0];
}
throw new Error('No task found');
}
catch (error) {
const isThrottle = error?.name === 'ThrottlingException' || /rate exceeded/i.test(String(error?.message));
if (!isThrottle || attempt === maxAttempts) {
throw error;
}
await new Promise((r) => setTimeout(r, delayMs));
delayMs *= 2;
}
}
}
static async streamLogsUntilTaskStops(clusterName, taskArn, kinesisStreamName) {

2
dist/index.js.map generated vendored

File diff suppressed because one or more lines are too long

View File

@ -1,19 +1,5 @@
import {
DescribeTasksCommand,
ECS,
RunTaskCommand,
RunTaskCommandInput,
Task,
waitUntilTasksRunning,
} from '@aws-sdk/client-ecs';
import {
DescribeStreamCommand,
DescribeStreamCommandOutput,
GetRecordsCommand,
GetRecordsCommandOutput,
GetShardIteratorCommand,
Kinesis,
} from '@aws-sdk/client-kinesis';
import { DescribeTasksCommand, ECS, RunTaskCommand, waitUntilTasksRunning } from '@aws-sdk/client-ecs';
import { DescribeStreamCommand, GetRecordsCommand, GetShardIteratorCommand, Kinesis } from '@aws-sdk/client-kinesis';
import CloudRunnerEnvironmentVariable from '../../options/cloud-runner-environment-variable';
import * as core from '@actions/core';
import CloudRunnerAWSTaskDef from './cloud-runner-aws-task-def';
@ -75,7 +61,7 @@ class AWSTaskRunner {
throw new Error(`Container Overrides length must be at most 8192`);
}
const task = await AWSTaskRunner.ECS.send(new RunTaskCommand(runParameters as RunTaskCommandInput));
const task = await AWSTaskRunner.ECS.send(new RunTaskCommand(runParameters as any));
const taskArn = task.tasks?.[0].taskArn || '';
CloudRunnerLogger.log('Cloud runner job is starting');
await AWSTaskRunner.waitUntilTaskRunning(taskArn, cluster);
@ -100,7 +86,11 @@ class AWSTaskRunner {
while (exitCode === undefined) {
await new Promise((resolve) => resolve(10000));
taskData = await AWSTaskRunner.describeTasks(cluster, taskArn);
containerState = taskData.containers?.[0];
const containers = taskData?.containers as any[] | undefined;
if (!containers || containers.length === 0) {
continue;
}
containerState = containers[0];
exitCode = containerState?.exitCode;
}
CloudRunnerLogger.log(`Container State: ${JSON.stringify(containerState, undefined, 4)}`);
@ -133,11 +123,8 @@ class AWSTaskRunner {
} catch (error_) {
const error = error_ as Error;
await new Promise((resolve) => setTimeout(resolve, 3000));
CloudRunnerLogger.log(
`Cloud runner job has ended ${
(await AWSTaskRunner.describeTasks(cluster, taskArn)).containers?.[0].lastStatus
}`,
);
const taskAfterError = await AWSTaskRunner.describeTasks(cluster, taskArn);
CloudRunnerLogger.log(`Cloud runner job has ended ${taskAfterError?.containers?.[0]?.lastStatus}`);
core.setFailed(error);
core.error(error);
@ -145,11 +132,25 @@ class AWSTaskRunner {
}
static async describeTasks(clusterName: string, taskArn: string) {
const tasks = await AWSTaskRunner.ECS.send(new DescribeTasksCommand({ cluster: clusterName, tasks: [taskArn] }));
if (tasks.tasks?.[0]) {
return tasks.tasks?.[0];
} else {
throw new Error('No task found');
const maxAttempts = 6;
let delayMs = 500;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
const tasks = await AWSTaskRunner.ECS.send(
new DescribeTasksCommand({ cluster: clusterName, tasks: [taskArn] }),
);
if (tasks.tasks?.[0]) {
return tasks.tasks?.[0];
}
throw new Error('No task found');
} catch (error: any) {
const isThrottle = error?.name === 'ThrottlingException' || /rate exceeded/i.test(String(error?.message));
if (!isThrottle || attempt === maxAttempts) {
throw error;
}
await new Promise((r) => setTimeout(r, delayMs));
delayMs *= 2;
}
}
}
@ -200,7 +201,7 @@ class AWSTaskRunner {
return { iterator, shouldReadLogs, output, shouldCleanup };
}
private static checkStreamingShouldContinue(taskData: Task, timestamp: number, shouldReadLogs: boolean) {
private static checkStreamingShouldContinue(taskData: any, timestamp: number, shouldReadLogs: boolean) {
if (taskData?.lastStatus === 'UNKNOWN') {
CloudRunnerLogger.log('## Cloud runner job unknwon');
}
@ -220,7 +221,7 @@ class AWSTaskRunner {
}
private static logRecords(
records: GetRecordsCommandOutput,
records: any,
iterator: string,
shouldReadLogs: boolean,
output: string,
@ -251,7 +252,7 @@ class AWSTaskRunner {
return await AWSTaskRunner.Kinesis.send(new DescribeStreamCommand({ StreamName: kinesisStreamName }));
}
private static async getLogIterator(stream: DescribeStreamCommandOutput) {
private static async getLogIterator(stream: any) {
return (
(
await AWSTaskRunner.Kinesis.send(

View File

@ -54,21 +54,8 @@ describe('Cloud Runner pre-built S3 steps', () => {
const buildParameter2 = await CreateParameters(overrides);
const baseImage2 = new ImageTag(buildParameter2);
const results2Object = await CloudRunner.run(buildParameter2, baseImage2.toString());
const results2 = results2Object.BuildResults;
CloudRunnerLogger.log(`run 2 succeeded`);
// Look for multiple indicators of a successful build
const buildIndicators = [
'Build succeeded',
'Build succeeded!',
'Build Succeeded',
'succeeded',
'Cloud Runner finished running standard build automation',
'Cloud runner job has finished successfully',
];
const buildWasSuccessful = buildIndicators.some((indicator) => results2.includes(indicator));
expect(buildWasSuccessful).toBeTruthy();
expect(results2Object.BuildSucceeded).toBe(true);
// Only run S3 operations if we're in CI or AWS CLI is available
if (isCI || awsAvailable) {

View File

@ -44,10 +44,10 @@ describe('Cloud Runner Caching', () => {
const results = resultsObject.BuildResults;
const libraryString = 'Rebuilding Library because the asset database could not be found!';
const cachePushFail = 'Did not push source folder to cache because it was empty Library';
const buildSucceededString = 'Build succeeded';
expect(results).toContain(libraryString);
expect(results).toContain(buildSucceededString);
expect(resultsObject.BuildSucceeded).toBe(true);
// Keep minimal assertions to reduce brittleness
expect(results).not.toContain(cachePushFail);
CloudRunnerLogger.log(`run 1 succeeded`);
@ -72,7 +72,6 @@ describe('Cloud Runner Caching', () => {
CloudRunnerLogger.log(`run 2 succeeded`);
const build2ContainsCacheKey = results2.includes(buildParameter.cacheKey);
const build2ContainsBuildSucceeded = results2.includes(buildSucceededString);
const build2NotContainsZeroLibraryCacheFilesMessage = !results2.includes(
'There is 0 files/dir in the cache pulled contents for Library',
);
@ -82,8 +81,7 @@ describe('Cloud Runner Caching', () => {
expect(build2ContainsCacheKey).toBeTruthy();
expect(results2).toContain('Activation successful');
expect(build2ContainsBuildSucceeded).toBeTruthy();
expect(results2).toContain(buildSucceededString);
expect(results2Object.BuildSucceeded).toBe(true);
const splitResults = results2.split('Activation successful');
expect(splitResults[splitResults.length - 1]).not.toContain(libraryString);
expect(build2NotContainsZeroLibraryCacheFilesMessage).toBeTruthy();

View File

@ -34,10 +34,10 @@ describe('Cloud Runner Retain Workspace', () => {
const results = resultsObject.BuildResults;
const libraryString = 'Rebuilding Library because the asset database could not be found!';
const cachePushFail = 'Did not push source folder to cache because it was empty Library';
const buildSucceededString = 'Build succeeded';
expect(results).toContain(libraryString);
expect(results).toContain(buildSucceededString);
expect(resultsObject.BuildSucceeded).toBe(true);
// Keep minimal assertions to reduce brittleness
expect(results).not.toContain(cachePushFail);
if (CloudRunnerOptions.providerStrategy === `local-docker`) {
@ -61,7 +61,6 @@ describe('Cloud Runner Retain Workspace', () => {
const build2ContainsBuildGuid1FromRetainedWorkspace = results2.includes(buildParameter.buildGuid);
const build2ContainsRetainedWorkspacePhrase = results2.includes(`Retained Workspace:`);
const build2ContainsWorkspaceExistsAlreadyPhrase = results2.includes(`Retained Workspace Already Exists!`);
const build2ContainsBuildSucceeded = results2.includes(buildSucceededString);
const build2NotContainsZeroLibraryCacheFilesMessage = !results2.includes(
'There is 0 files/dir in the cache pulled contents for Library',
);
@ -73,7 +72,7 @@ describe('Cloud Runner Retain Workspace', () => {
expect(build2ContainsRetainedWorkspacePhrase).toBeTruthy();
expect(build2ContainsWorkspaceExistsAlreadyPhrase).toBeTruthy();
expect(build2ContainsBuildGuid1FromRetainedWorkspace).toBeTruthy();
expect(build2ContainsBuildSucceeded).toBeTruthy();
expect(results2Object.BuildSucceeded).toBe(true);
expect(build2NotContainsZeroLibraryCacheFilesMessage).toBeTruthy();
expect(build2NotContainsZeroLFSCacheFilesMessage).toBeTruthy();
const splitResults = results2.split('Activation successful');