Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Add subcommand recce to test-deploy for external PRs #2424

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions ops/external-prs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,22 @@ pnpm external-prs initialize-check {sha} {login} {check-name}

```bash
# Handle oso comments
pnpm external-prs oso parse-comment {comment} {output}
pnpm tools oso parse-comment {comment} {output}

# Refresh gcp credentials for the test deployment infrastructure
pnpm external-prs oso refresh-gcp-credentials {environment} {creds-path} {name}
pnpm tools oso refresh-gcp-credentials {environment} {creds-path} {name}

# Test deployment sub commands
pnpm external-prs oso test-deploy --help
pnpm tools oso test-deploy --help

# Test deployment setup
pnpm external-prs oso test-deploy setup {pr} {sha} {profile-path} {service-account-path} {checkout-path}
pnpm tools oso test-deploy setup {pr} {sha} {profile-path} {service-account-path} {checkout-path}

# Test deployment teardown
pnpm external-prs oso test-deploy teardown {pr}
pnpm tools oso test-deploy teardown {pr}

# Test deployment clean
pnpm external-prs oso test-deploy clean {ttl-seconds}
pnpm tools oso test-deploy clean {ttl-seconds}
```

### OSS-Directory Specific
Expand All @@ -54,7 +54,7 @@ You can run the app via:

```bash
# Handle PR validations
pnpm external-prs ossd validate-prs {pr_number} {commit_sha} {main_path} {pr_path}
pnpm tools ossd validate-prs {pr_number} {commit_sha} {main_path} {pr_path}
```

If you've configured your GitHub secrets correctly,
Expand Down
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 @@
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 @@
\`\`\`
${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);

Check failure on line 195 in ops/external-prs/src/oso/deploy.ts

View workflow job for this annotation

GitHub Actions / test (node)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
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);

Check failure on line 247 in ops/external-prs/src/oso/deploy.ts

View workflow job for this annotation

GitHub Actions / test (node)

Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the `void` operator
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 @@
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<TestDeploySetupArgs>(
"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
Loading