Skip to content

Commit

Permalink
Add subcommand recce to test-deploy
Browse files Browse the repository at this point in the history
Signed-off-by: even-wei <[email protected]>
  • Loading branch information
even-wei committed Oct 28, 2024
1 parent 0c71d5c commit a35a7ad
Show file tree
Hide file tree
Showing 2 changed files with 226 additions and 8 deletions.
202 changes: 194 additions & 8 deletions ops/external-prs/src/oso/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export class PRTestDeployCoordinator {
await this.appUtils.setStatusComment(
pr,
dedent`
Test deployment for PR #${pr} failed on commit \`${sha}\`. With error:
Test deployment for PR #${pr} failed on commit \`${sha}\`. With error:
Error stack:
\`\`\`
Expand All @@ -119,7 +119,169 @@ export class PRTestDeployCoordinator {
\`\`\`
${err.stdout}
\`\`\`
DBT stderr:
\`\`\`
${err.stderr}
\`\`\`
`,
);

return;
}
const datasetFQN = `${this.projectId}.${datasetName}`;

await this.completeCheckStatus(sha, datasetFQN);

const datasetURL = `https://console.cloud.google.com/bigquery?project=oso-pull-requests&ws=!1m4!1m3!3m2!1soso-pull-requests!2spr_${pr}`;

await this.appUtils.setStatusComment(
pr,
dedent`
Test deployment for PR #${pr} successfully deployed to [\`${datasetFQN}\`](${datasetURL}).
`,
);
}

async recce(
pr: number,
sha: string,
profilePath: string,
serviceAccountPath: string,
checkoutPath: string,
) {
// This should create a two public dataset inside a "testing" project
// specifically for a pull request and its merge base, and prepare the
// dbt artifacts for Recce.
//
// This project is intended to be only used to push the last 2 days worth of
// data into a dev environment
//
// The service account associated with this account should only have access to
// bigquery no other resources. The service account should also continously be
// rotated. So the project in use should have a very short TTL on service
// account keys.
logger.info({
mesage: "setting up Recce for test deployment",
pr: pr,
repo: this.repo,
});

const prInfo = await this.octo.rest.pulls.get({
owner: this.repo.owner,
repo: this.repo.name,
pull_number: pr,
});
const baseSha = prInfo.data.base.sha;

// Compare the baseSha and the current sha to see if anything has changed in the dbt directory.
// If not then report a success and that this check is not needed for this PR.
if (!(await this.hasDbtChanges(baseSha, sha, checkoutPath))) {
await this.noRelevantChanges(sha);
await this.appUtils.setStatusComment(
pr,
dedent`
Test deployment unnecessary, no dbt files have been changed.
`,
);
return;
}

const git = simpleGit({ baseDir: checkoutPath });
const datasetName = this.datasetNameFromPR(pr);
const baseDatasetName = `${datasetName}_base`;

// Prepare the base environment
git.checkout(baseSha);
await this.getOrCreateDataset(baseDatasetName);
await this.generateDbtProfile(
baseDatasetName,
profilePath,
serviceAccountPath,
);

// Run dbt and generate dbt docs
try {
await this.runDbt(checkoutPath, "target-base");
await this.generateDbtDocs(checkoutPath, "target-base");
} catch (e) {
const err = e as Error & {
stdout: string;
stderr: string;
code: number;
signal: string;
};
logger.error({
message: "error running dbt",
error: e,
});

await this.failCheckStatus(sha);

await this.appUtils.setStatusComment(
pr,
dedent`
Test deployment for PR #${pr} failed on commit \`${sha}\`. With error:
Error stack:
\`\`\`
${err.stack}
\`\`\`
DBT stdout:
\`\`\`
${err.stdout}
\`\`\`
DBT stderr:
\`\`\`
${err.stderr}
\`\`\`
`,
);

return;
}

// Prepare the current environment
git.checkout(sha);
await this.getOrCreateDataset(datasetName);
await this.generateDbtProfile(datasetName, profilePath, serviceAccountPath);

// Run dbt, generate dbt docs, and run Recce
try {
await this.runDbt(checkoutPath);
await this.generateDbtDocs(checkoutPath);
await this.runRecce(checkoutPath, pr);
} catch (e) {
const err = e as Error & {
stdout: string;
stderr: string;
code: number;
signal: string;
};
logger.error({
message: "error running dbt",
error: e,
});

await this.failCheckStatus(sha);

await this.appUtils.setStatusComment(
pr,
dedent`
Test deployment for PR #${pr} failed on commit \`${sha}\`. With error:
Error stack:
\`\`\`
${err.stack}
\`\`\`
DBT stdout:
\`\`\`
${err.stdout}
\`\`\`
DBT stderr:
\`\`\`
${err.stderr}
Expand Down Expand Up @@ -315,14 +477,38 @@ export class PRTestDeployCoordinator {
return await fsPromise.writeFile(profilePath, contents);
}

private async runDbt(p: string) {
private async runDbt(p: string, targetPath: string = "target") {
const absPath = path.resolve(p);
return await execPromise(`${absPath}/.venv/bin/dbt run --no-use-colors`, {
cwd: absPath,
env: {
PLAYGROUND_DAYS: "1",
return await execPromise(
`${absPath}/.venv/bin/dbt run --no-use-colors --target-path ${targetPath}`,
{
cwd: absPath,
env: {
PLAYGROUND_DAYS: "1",
},
},
});
);
}

private async generateDbtDocs(p: string, targetPath: string = "target") {
const absPath = path.resolve(p);
return await execPromise(
`${absPath}/.venv/bin/dbt docs generate --no-use-colors --target-path ${targetPath}`,
{
cwd: absPath,
env: {
PLAYGROUND_DAYS: "1",
},
},
);
}

private async runRecce(p: string, pr: number) {
const absPath = path.resolve(p);
return await execPromise(
`${absPath}/.venv/bin/recce run recce_state_pr_${pr}.json`,
{ cwd: absPath },
);
}

private async leaveDeploymentComment(pr: number, message: string) {
Expand Down
32 changes: 32 additions & 0 deletions ops/external-prs/src/oso/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,16 @@ async function testDeploySetup(args: TestDeploySetupArgs) {
);
}

async function testDeployRecce(args: TestDeploySetupArgs) {
return args.coordinator.recce(
args.pr,
args.sha,
args.profilePath,
args.serviceAccountPath,
args.checkoutPath,
);
}

async function testDeployTeardown(args: TestDeployTeardownArgs) {
return args.coordinator.teardown(args.pr);
}
Expand Down Expand Up @@ -371,6 +381,28 @@ function testDeployGroup(group: Argv) {
},
(args) => handleError(testDeploySetup(args)),
)
.command<TestDeployRecceArgs>(
"recce <pr> <sha> <profile-path> <service-account-path> <checkout-path>",
"subcommand for a setting up Recce for the test deployment",
(yags) => {
yags.positional("pr", {
description: "The PR",
});
yags.positional("sha", {
description: "the sha to deploy",
});
yags.positional("profile-path", {
description: "the profile path to write to",
});
yags.positional("service-account-path", {
description: "the profile path to write to",
});
yags.positional("checkout-path", {
description: "the path to the checked out code",
});
},
(args) => handleError(testDeployRecce(args)),
)
.command<TestDeployTeardownArgs>(
"teardown <pr>",
"subcommand for a setting up a test deployment",
Expand Down

0 comments on commit a35a7ad

Please sign in to comment.