Skip to content

Commit

Permalink
[build-tools] Add eas/create_submission_entity (#445)
Browse files Browse the repository at this point in the history
# Why

We want to create submission entities from job runs.

# How

Added an `eas/create_submission_entity` calling our API.

# Test Plan

Tested this manually.
  • Loading branch information
sjchmiela authored Nov 12, 2024
1 parent e83cece commit 659d3d4
Show file tree
Hide file tree
Showing 9 changed files with 199 additions and 0 deletions.
4 changes: 4 additions & 0 deletions packages/build-tools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@
"nullthrows": "^1.1.1",
"plist": "^3.1.0",
"promise-limit": "^2.7.0",
"promise-retry": "^2.0.1",
"resolve-from": "^5.0.0",
"retry": "^0.13.1",
"semver": "^7.6.2"
},
"devDependencies": {
Expand All @@ -56,6 +58,8 @@
"@types/node-fetch": "^2.6.11",
"@types/node-forge": "^1.3.11",
"@types/plist": "^3.0.5",
"@types/promise-retry": "^1.1.6",
"@types/retry": "^0.12.5",
"@types/semver": "^7.5.8",
"@types/uuid": "^9.0.8",
"jest": "^29.7.0",
Expand Down
1 change: 1 addition & 0 deletions packages/build-tools/src/customBuildContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export class CustomBuildContext<TJob extends Job = Job> implements ExternalBuild
public staticContext(): Omit<StaticJobInterpolationContext, 'steps'> {
return {
...this.job.workflowInterpolationContext,
expoApiServerURL: this.env.__API_SERVER_URL,
job: this.job,
metadata: this.metadata ?? null,
env: this.env,
Expand Down
3 changes: 3 additions & 0 deletions packages/build-tools/src/steps/easFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { createResolveBuildConfigBuildFunction } from './functions/resolveBuildC
import { calculateEASUpdateRuntimeVersionFunction } from './functions/calculateEASUpdateRuntimeVersion';
import { createRepackBuildFunction } from './functions/repack';
import { eagerBundleBuildFunction } from './functions/eagerBundle';
import { createSubmissionEntityFunction } from './functions/createSubmissionEntity';

export function getEasFunctions(ctx: CustomBuildContext): BuildFunction[] {
const functions = [
Expand Down Expand Up @@ -56,6 +57,8 @@ export function getEasFunctions(ctx: CustomBuildContext): BuildFunction[] {
calculateEASUpdateRuntimeVersionFunction(),

createRepackBuildFunction(),

createSubmissionEntityFunction(),
];

if (ctx.hasBuildJob()) {
Expand Down
133 changes: 133 additions & 0 deletions packages/build-tools/src/steps/functions/createSubmissionEntity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { BuildFunction, BuildStepInput, BuildStepInputValueTypeName } from '@expo/steps';
import { asyncResult } from '@expo/results';

import { retryOnDNSFailure } from '../../utils/retryOnDNSFailure';

export function createSubmissionEntityFunction(): BuildFunction {
return new BuildFunction({
namespace: 'eas',
id: 'create_submission_entity',
name: 'Create Submission Entity',
inputProviders: [
BuildStepInput.createProvider({
id: 'build_id',
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
required: true,
}),

// AndroidSubmissionConfig
BuildStepInput.createProvider({
id: 'track',
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
required: false,
}),
BuildStepInput.createProvider({
id: 'release_status',
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
required: false,
}),
BuildStepInput.createProvider({
id: 'rollout',
allowedValueTypeName: BuildStepInputValueTypeName.NUMBER,
required: false,
}),
BuildStepInput.createProvider({
id: 'changes_not_sent_for_review',
allowedValueTypeName: BuildStepInputValueTypeName.BOOLEAN,
required: false,
}),

// IosSubmissionConfig
BuildStepInput.createProvider({
id: 'apple_id_username',
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
required: false,
}),
BuildStepInput.createProvider({
id: 'asc_app_identifier',
allowedValueTypeName: BuildStepInputValueTypeName.STRING,
required: false,
}),
],
fn: async (stepsCtx, { inputs }) => {
const robotAccessToken = stepsCtx.global.staticContext.job.secrets?.robotAccessToken;
if (!robotAccessToken) {
stepsCtx.logger.error('Failed to create submission entity: no robot access token found');
return;
}

const buildId = inputs.build_id.value;
if (!buildId) {
stepsCtx.logger.error('Failed to create submission entity: no build ID provided');
return;
}

const workflowJobId = stepsCtx.global.staticContext.env.__WORKFLOW_JOB_ID;
if (!workflowJobId) {
stepsCtx.logger.error('Failed to create submission entity: no workflow job ID found');
return;
}

// This is supposed to provide fallback for `''` -> `undefined`.
// We _not_ want to use nullish coalescing.
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
const track = inputs.track.value || undefined;
const releaseStatus = inputs.release_status.value || undefined;
const rollout = inputs.rollout.value || undefined;
const changesNotSentForReview = inputs.changes_not_sent_for_review.value || undefined;

const appleIdUsername = inputs.apple_id_username.value || undefined;
const ascAppIdentifier = inputs.asc_app_identifier.value || undefined;
/* eslint-enable @typescript-eslint/prefer-nullish-coalescing */

try {
const response = await retryOnDNSFailure(fetch)(
new URL('/v2/app-store-submissions/', stepsCtx.global.staticContext.expoApiServerURL),
{
method: 'POST',
headers: {
Authorization: `Bearer ${robotAccessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
workflowJobId,
turtleBuildId: buildId,
// We can pass mixed object here because the configs are disjoint.
config: {
// AndroidSubmissionConfig
track,
releaseStatus,
rollout,
changesNotSentForReview,

// IosSubmissionConfig
appleIdUsername,
ascAppIdentifier,
},
}),
}
);

if (!response.ok) {
const textResult = await asyncResult(response.text());
throw new Error(
`Unexpected response from server (${response.status}): ${textResult.value}`
);
}

const jsonResult = await asyncResult(response.json());
if (!jsonResult.ok) {
stepsCtx.logger.warn(
`Submission created. Failed to parse response. ${jsonResult.reason}`
);
return;
}

const data = jsonResult.value.data;
stepsCtx.logger.info(`Submission created:\n ID: ${data.id}\n URL: ${data.url}`);
} catch (e) {
stepsCtx.logger.error(`Failed to create submission entity. ${e}`);
}
},
});
}
20 changes: 20 additions & 0 deletions packages/build-tools/src/utils/promiseRetryWithCondition.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import promiseRetry from 'promise-retry';
import { OperationOptions } from 'retry';

export function promiseRetryWithCondition<TFn extends (...args: any[]) => Promise<any>>(
fn: TFn,
retryConditionFn: (error: any) => boolean,
options: OperationOptions = { retries: 3, factor: 2 }
): (...funcArgs: Parameters<TFn>) => Promise<ReturnType<TFn>> {
return (...funcArgs) =>
promiseRetry<ReturnType<TFn>>(async (retry) => {
try {
return await fn(...funcArgs);
} catch (e) {
if (retryConditionFn(e)) {
retry(e);
}
throw e;
}
}, options);
}
19 changes: 19 additions & 0 deletions packages/build-tools/src/utils/retryOnDNSFailure.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { OperationOptions } from 'retry';

import { promiseRetryWithCondition } from './promiseRetryWithCondition';

export function isDNSError(e: Error & { code: any }): boolean {
return e.code === 'ENOTFOUND' || e.code === 'EAI_AGAIN';
}

export function retryOnDNSFailure<TFn extends (...args: any[]) => Promise<any>>(
fn: TFn,
options?: OperationOptions
): (...funcArgs: Parameters<TFn>) => Promise<ReturnType<TFn>> {
return promiseRetryWithCondition(fn, isDNSError, {
retries: 3,
factor: 2,
minTimeout: 100,
...options,
});
}
1 change: 1 addition & 0 deletions packages/eas-build-job/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type StaticJobOnlyInterpolationContext = {
outputs: Record<string, string | undefined>;
}
>;
expoApiServerURL: string;
};

export type StaticJobInterpolationContext =
Expand Down
1 change: 1 addition & 0 deletions packages/steps/src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export class CliContextProvider implements ExternalBuildContextProvider {
job: {} as Job,
metadata: {} as Metadata,
env: this.env as Env,
expoApiServerURL: 'http://api.expo.test',
};
}
public updateEnv(env: BuildStepEnv): void {
Expand Down
17 changes: 17 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1982,6 +1982,13 @@
"@types/node" "*"
xmlbuilder ">=11.0.1"

"@types/promise-retry@^1.1.6":
version "1.1.6"
resolved "https://registry.yarnpkg.com/@types/promise-retry/-/promise-retry-1.1.6.tgz#3c48826d8a27f68f9d4900fc7448f08a1532db44"
integrity sha512-EC1+OMXV0PZb0pf+cmyxc43MEP2CDumZe4AfuxWboxxEixztIebknpJPZAX5XlodGF1OY+C1E/RAeNGzxf+bJA==
dependencies:
"@types/retry" "*"

"@types/prompts@^2.4.9":
version "2.4.9"
resolved "https://registry.yarnpkg.com/@types/prompts/-/prompts-2.4.9.tgz#8775a31e40ad227af511aa0d7f19a044ccbd371e"
Expand All @@ -1997,6 +2004,11 @@
dependencies:
"@types/node" "*"

"@types/retry@*", "@types/retry@^0.12.5":
version "0.12.5"
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.5.tgz#f090ff4bd8d2e5b940ff270ab39fd5ca1834a07e"
integrity sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw==

"@types/semver@^7.5.8":
version "7.5.8"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e"
Expand Down Expand Up @@ -8313,6 +8325,11 @@ retry@^0.12.0:
resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==

retry@^0.13.1:
version "0.13.1"
resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658"
integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==

reusify@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
Expand Down

0 comments on commit 659d3d4

Please sign in to comment.