Skip to content

Commit 96b97a3

Browse files
authored
[patch port] ci: setup automatic deployment for the docs app (#24552)
* ci: setup automatic deployment for the docs app See the following document for the overall plan that this commit implements: https://docs.google.com/document/d/1xkrSOFa6WeFqyg1cTwMhl_wB8ygbVwdSxr3K2-cps14/edit * build: do not reserve `GOOGLE_APPLICATION_CREDENTIALS` for RBE auth We currently configure RBE by setting `GOOGLE_APPLICATION_CREDENTIALS` into the `$BASH_ENV` variable, ensuring RBE is configured everywhere on CI. This worked nicely but now with automatic docs deployment turned out to be problematic since it prevents scripts from defining `GOOGLE_APPLICATION_CREDENTIALS` themselves/overriding it. the reason is that `$BASH_ENV` always runs in new child processes (like when firebase is initialized) and then overrides the credentials back to the RBE service key. We can simplify this code by using a dedicated Bazel flag.
1 parent 41320d0 commit 96b97a3

20 files changed

+758
-208
lines changed

.circleci/config.yml

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,6 @@ var_14: &publish_branches_filter
8686
- master
8787
# 6.0.x, 7.1.x, etc.
8888
- /\d+\.\d+\.x/
89-
# 6.x, 7.x, 8.x etc
90-
- /\d+\.x/
9189

9290
# Branch filter that is usually applied to all jobs. Since there is no way within CircleCI to
9391
# exclude a branch for all defined jobs, we need to manually specify the filters for each job.
@@ -395,6 +393,42 @@ jobs:
395393
- store_artifacts:
396394
path: dist/release-archives
397395

396+
# ----------------------------------------
397+
# Job that publishes the docs site
398+
# ----------------------------------------
399+
deploy_docs_site:
400+
docker:
401+
- image: *docker-browser-image
402+
resource_class: xlarge
403+
environment:
404+
GCP_DECRYPT_TOKEN: *gcp_decrypt_token
405+
steps:
406+
- checkout_and_rebase
407+
- *restore_cache
408+
- *setup_bazel_ci_config
409+
- *setup_bazel_remote_execution
410+
- *yarn_install
411+
- *setup_bazel_binary
412+
413+
- run: yarn ci-push-deploy-docs-app
414+
- *slack_notify_on_failure
415+
416+
# ----------------------------------------
417+
# Job that monitors the docs site, ensuring
418+
# the docs site is online and works as expected.
419+
# ----------------------------------------
420+
monitor_docs_site:
421+
docker:
422+
- image: *docker-browser-image
423+
resource_class: xlarge
424+
steps:
425+
- checkout_and_rebase
426+
- *restore_cache
427+
- *yarn_install
428+
429+
- run: yarn ci-docs-monitor-test
430+
- *slack_notify_on_failure
431+
398432
# ----------------------------------------
399433
# Job that publishes the build snapshots
400434
# ----------------------------------------
@@ -609,6 +643,12 @@ workflows:
609643
filters: *publish_branches_filter
610644
requires:
611645
- build_release_packages
646+
- deploy_docs_site:
647+
filters: *publish_branches_filter
648+
requires:
649+
- lint
650+
- build_release_packages
651+
- tests_local_browsers
612652

613653
# Snapshot tests workflow that is scheduled to run all specified jobs every hour.
614654
# This workflow runs various jobs against the Angular snapshot builds from Github.
@@ -623,6 +663,8 @@ workflows:
623663
filters: *only_main_branch_filter
624664
- snapshot_linker_tests:
625665
filters: *only_main_branch_filter
666+
- monitor_docs_site:
667+
filters: *only_main_branch_filter
626668

627669
triggers:
628670
- schedule:

package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"postinstall": "node tools/postinstall/apply-patches.js",
1818
"build": "ts-node --project scripts/tsconfig.json ./scripts/build-packages-dist.ts",
1919
"build-and-check-release-output": "ts-node --project scripts/tsconfig.json scripts/build-and-check-release-output.ts",
20-
"build-docs-content": "node ./scripts/build-docs-content.js",
20+
"build-docs-content": "ts-node --project scripts/tsconfig.json ./scripts/build-docs-content.ts",
2121
"dev-app": "ibazel run //src/dev-app:devserver",
2222
"test": "node ./scripts/run-component-tests.js",
2323
"test-local": "yarn -s test --local",
@@ -48,6 +48,8 @@
4848
"check-mdc-exports": "ts-node --project scripts/tsconfig.json scripts/check-mdc-exports.ts",
4949
"check-tooling-setup": "yarn tsc --project tools/tsconfig.json && yarn tsc --project .ng-dev/tsconfig.json",
5050
"tsc": "node ./node_modules/typescript/bin/tsc",
51+
"ci-push-deploy-docs-app": "ts-node --project scripts/tsconfig.json scripts/docs-deploy/deploy-ci-push.ts",
52+
"ci-docs-monitor-test": "ts-node --project scripts/tsconfig.json scripts/docs-deploy/monitoring/ci-test.ts",
5153
"prepare": "husky install"
5254
},
5355
"version": "13.2.6",
@@ -73,7 +75,7 @@
7375
"@angular/bazel": "13.2.0",
7476
"@angular/cli": "13.2.0",
7577
"@angular/compiler-cli": "13.2.0",
76-
"@angular/dev-infra-private": "https://github.com/angular/dev-infra-private-builds.git#b6656cffbd46bb3637b09b08561e5f1a147603c9",
78+
"@angular/dev-infra-private": "https://github.com/angular/dev-infra-private-builds.git#7544378ad9aa94cdfe256aaaa923c107f45175a2",
7779
"@angular/localize": "13.2.0",
7880
"@angular/platform-browser-dynamic": "13.2.0",
7981
"@angular/platform-server": "13.2.0",
@@ -217,7 +219,8 @@
217219
"typescript-4.4": "npm:[email protected]",
218220
"vrsource-tslint-rules": "6.0.0",
219221
"yaml": "^1.10.2",
220-
"yargs": "^17.3.1"
222+
"yargs": "^17.3.1",
223+
"zx": "^4.3.0"
221224
},
222225
"resolutions": {
223226
"@angular/dev-infra-private/typescript": "~4.5.2",

scripts/bazel/setup-remote-execution.sh

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,10 @@ fi
1111

1212
# Decode the GCP token that is needed to authenticate the Bazel remote execution.
1313
openssl aes-256-cbc -d -in scripts/bazel/gcp_token -md md5 -k ${GCP_DECRYPT_TOKEN} \
14-
-out $HOME/.gcp_credentials
15-
16-
# Set the "GOOGLE_APPLICATION_CREDENTIALS" environment variable. It should point to the GCP credentials
17-
# file. Bazel will then automatically picks up the credentials from that variable.
18-
# https://docs.bazel.build/versions/main/command-line-reference.html#flag--google_default_credentials
19-
# https://cloud.google.com/docs/authentication/production.
20-
if [[ ! -z "${BASH_ENV}" ]]; then
21-
# CircleCI uses the `BASH_ENV` variable for environment variables.
22-
echo "export GOOGLE_APPLICATION_CREDENTIALS=${HOME}/.gcp_credentials" >> ${BASH_ENV}
23-
elif [[ ! -z "${GITHUB_ENV}" ]]; then
24-
# Github actions use the `GITHUB_ENV` variable for environment variables.
25-
echo "GOOGLE_APPLICATION_CREDENTIALS=${HOME}/.gcp_credentials" >> ${GITHUB_ENV}
26-
fi
14+
-out $HOME/.gcp_rbe_credentials
2715

2816
# Update the project Bazel configuration to always use remote execution.
2917
# Note: We add the remote config flag to the user bazelrc file that is not tracked
3018
# by Git. This is necessary to avoid stamping builds with `.with-local-changes`.
3119
echo "build --config=remote" >> .bazelrc.user
20+
echo "build:remote --google_credentials=\"$HOME/.gcp_rbe_credentials\"" >> .bazelrc.user

scripts/build-docs-content.js

Lines changed: 0 additions & 52 deletions
This file was deleted.

scripts/build-docs-content.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* Script that builds the docs content NPM package and moves it into a conveniently
5+
* accessible distribution directory (the project `dist/` directory).
6+
*/
7+
8+
import {cd, chmod, cp, exec, mkdir, rm, set} from 'shelljs';
9+
10+
import {BuiltPackage} from '@angular/dev-infra-private/ng-dev';
11+
import {join} from 'path';
12+
13+
/** Path to the project directory. */
14+
const projectDir = join(__dirname, '../');
15+
16+
/** Path to the distribution directory. */
17+
const distDir = join(projectDir, 'dist/');
18+
19+
/**
20+
* Path to the directory where the docs-content package is copied to. Note: When
21+
* changing the path, also change the path in the docs-content deploy script.
22+
*/
23+
const outputDir = join(distDir, 'docs-content-pkg');
24+
25+
/** Command that runs Bazel. */
26+
const bazelCmd = process.env.BAZEL || `yarn -s bazel`;
27+
28+
/**
29+
* Builds the docs content NPM package in snapshot mode.
30+
*
31+
* @returns an object describing the built package and its output path.
32+
*/
33+
export function buildDocsContentPackage(): BuiltPackage {
34+
// ShellJS should exit if a command fails.
35+
set('-e');
36+
37+
// Go to project directory.
38+
cd(projectDir);
39+
40+
/** Path to the bazel bin output directory. */
41+
const bazelBinPath = exec(`${bazelCmd} info bazel-bin`).stdout.trim();
42+
43+
/** Path where the NPM package is built into by Bazel. */
44+
const bazelBinOutDir = join(bazelBinPath, 'src/components-examples/npm_package');
45+
46+
// Clean the output directory to ensure that the docs-content package
47+
// will not contain outdated files from previous builds.
48+
rm('-rf', outputDir);
49+
mkdir('-p', distDir);
50+
51+
// Build the docs-content package with the snapshot-build mode. That will help
52+
// determining which commit is associated with the built docs-content.
53+
exec(`${bazelCmd} build src/components-examples:npm_package --config=snapshot-build`);
54+
55+
// Copy the package output into the dist path. Also update the permissions
56+
// as Bazel by default marks files in the bazel-out as readonly.
57+
cp('-R', bazelBinOutDir, outputDir);
58+
chmod('-R', 'u+w', outputDir);
59+
60+
return {
61+
name: '@angular/components-examples',
62+
outputPath: outputDir,
63+
};
64+
}
65+
66+
if (require.main === module) {
67+
const builtPackage = buildDocsContentPackage();
68+
console.info(`Built docs-content into: ${builtPackage.outputPath}`);
69+
}

scripts/build-packages-dist.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const releaseTargetTag = 'release-package';
2121
const projectDir = join(__dirname, '../');
2222

2323
/** Command that runs Bazel. */
24-
const bazelCmd = process.env.BAZEL_COMMAND || `bazel`;
24+
const bazelCmd = process.env.BAZEL || `yarn -s bazel`;
2525

2626
/** Command that queries Bazel for all release package targets. */
2727
const queryPackagesCmd =
@@ -69,7 +69,8 @@ function buildReleasePackages(distPath: string, isSnapshotBuild: boolean): Built
6969
const targets = exec(queryPackagesCmd, true).split(/\r?\n/);
7070
const packageNames = getPackageNamesOfTargets(targets);
7171
const bazelBinPath = exec(`${bazelCmd} info bazel-bin`, true);
72-
const getOutputPath = (pkgName: string) => join(bazelBinPath, 'src', pkgName, 'npm_package');
72+
const getBazelOutputPath = (pkgName: string) => join(bazelBinPath, 'src', pkgName, 'npm_package');
73+
const getDistPath = (pkgName: string) => join(distPath, pkgName);
7374

7475
// Build with "--config=release" or `--config=snapshot-build` so that Bazel
7576
// runs the workspace stamping script. The stamping script ensures that the
@@ -80,7 +81,7 @@ function buildReleasePackages(distPath: string, isSnapshotBuild: boolean): Built
8081
// a workaround for: https://github.com/bazelbuild/rules_nodejs/issues/1219. We need to
8182
// do this to ensure that the version placeholders are properly populated.
8283
packageNames.forEach(pkgName => {
83-
const outputPath = getOutputPath(pkgName);
84+
const outputPath = getBazelOutputPath(pkgName);
8485
if (test('-d', outputPath)) {
8586
chmod('-R', 'u+w', outputPath);
8687
rm('-rf', outputPath);
@@ -96,18 +97,17 @@ function buildReleasePackages(distPath: string, isSnapshotBuild: boolean): Built
9697

9798
// Copy the package output into the specified distribution folder.
9899
packageNames.forEach(pkgName => {
99-
const outputPath = getOutputPath(pkgName);
100-
const targetFolder = join(distPath, pkgName);
100+
const outputPath = getBazelOutputPath(pkgName);
101+
const targetFolder = getDistPath(pkgName);
101102
console.log(`> Copying package output to "${targetFolder}"`);
102103
cp('-R', outputPath, targetFolder);
103104
chmod('-R', 'u+w', targetFolder);
104105
});
105106

106107
return packageNames.map(pkg => {
107-
const outputPath = getOutputPath(pkg);
108108
return {
109109
name: `@angular/${pkg}`,
110-
outputPath,
110+
outputPath: getDistPath(pkg),
111111
};
112112
});
113113
}

scripts/deploy/publish-docs-content.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ docsDistPath="${projectPath}/dist/docs"
2424
docsContentPath="${projectPath}/tmp/material2-docs-content"
2525

2626
# Path to the build output of the Bazel "@angular/components-examples" NPM package.
27-
# Note: When changing this, also change the path in `scripts/build-docs-content.js`.
27+
# Note: When changing this, also change the path in `scripts/build-docs-content.ts`.
2828
examplesPackagePath="${projectPath}/dist/docs-content-pkg/"
2929

3030
# Git clone URL for the material2-docs-content repository.

scripts/docs-deploy/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
### Docs app deployment
2+
3+
https://docs.google.com/document/d/1xkrSOFa6WeFqyg1cTwMhl_wB8ygbVwdSxr3K2-cps14/edit?usp=sharing
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import * as fs from 'fs';
2+
import * as path from 'path';
3+
4+
import {$} from 'zx';
5+
import {projectDir} from './utils';
6+
7+
/** Git repository HTTP url pointing to the docs repository. */
8+
export const docsRepoUrl = 'https://github.com/angular/material.angular.io.git';
9+
10+
/**
11+
* Clones the docs repository for the given major into a
12+
* temporary directory.
13+
*
14+
* @returns An absolute path to the temporary directory.
15+
*/
16+
export async function cloneDocsRepositoryForMajor(major: number): Promise<string> {
17+
const repoTmpDir = path.join(projectDir, 'tmp/docs-repo');
18+
const baseCloneArgs = [docsRepoUrl, repoTmpDir, '--single-branch', '--depth=1'];
19+
const majorDocsBranchName = getDocsBranchNameForMajor(major);
20+
21+
// Cleanup the temporary repository directory if it exists.
22+
try {
23+
await fs.promises.rm(repoTmpDir, {recursive: true});
24+
} catch {}
25+
26+
// Clone the docs app (either the main branch, or a dedicated major branch if available).
27+
if (await hasUpstreamDocsBranch(majorDocsBranchName)) {
28+
console.log(`Cloning docs app with dedicated branch: ${majorDocsBranchName}`);
29+
await $`git clone ${baseCloneArgs} --branch=${majorDocsBranchName}`;
30+
} else {
31+
console.log(`Cloning docs app with default branch (no dedicated branch for major).`);
32+
await $`git clone ${baseCloneArgs}`;
33+
}
34+
35+
return repoTmpDir;
36+
}
37+
38+
/**
39+
* Gets whether the specified branch exists in the specified remote URL.
40+
*/
41+
async function hasUpstreamDocsBranch(branchName: string): Promise<boolean> {
42+
try {
43+
const proc = await $`git ls-remote ${docsRepoUrl} refs/heads/${branchName}`;
44+
return proc.stdout.trim() !== '';
45+
} catch {
46+
return false;
47+
}
48+
}
49+
50+
/**
51+
* Gets the name of a potential dedicated branch for this major in the
52+
* docs repository.
53+
*
54+
* e.g. if a branch like `13.x` exists and we intend to deploy v13, then
55+
* this branch can be used as revision for the docs-app.
56+
*
57+
* More details on why this is preferred:
58+
* https://docs.google.com/document/d/1xkrSOFa6WeFqyg1cTwMhl_wB8ygbVwdSxr3K2-cps14/edit#heading=h.nsf3ag63jpwu.
59+
*/
60+
function getDocsBranchNameForMajor(major: number): string {
61+
return 'firebase-target';
62+
// TODO return `${major}.x`;
63+
}

0 commit comments

Comments
 (0)