Repository navigation
177 lines (164 loc) · 8.11 KB
/
Copy pathdocsync.yml
File metadata and controls
177 lines (164 loc) · 8.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# Publishes the docs pages as Google Docs and turns edits made in those Docs into pull
# requests, by running `dfo-docsync` (see src/dfo_docs/docsync/cli.py's module docstring
# for what each command does).
#
# Only a `workflow_dispatch` run starts it, with one command. `init` creates the tracking
# Sheet, `push` publishes the pages, and `pull` opens pull requests from Doc edits. Run
# `pull` before `push`, so edits reach the repositories before a push replaces the Docs.
# It reads the source repositories directly, so it does not wait for the build workflow.
# A page that fails does not stop the run. The job fails if the command exited non-zero
# (1 when a page or repository failed, 2 when the run could not start). When its state
# save failed, the unsaved-states file must be restored before another run saves state
# over it.
#
# The job runs from `dev` in the `docsync` environment, which holds everything it needs:
# the variables `GCP_WIF_PROVIDER`, `DOCSYNC_SERVICE_ACCOUNT` and `DOCSYNC_APP_CLIENT_ID`,
# and the secret `DOCSYNC_APP_PRIVATE_KEY`. The environment admits only `dev`.
#
# Google access: `google-github-actions/auth` exchanges the run's OIDC token for the
# `DOCSYNC_SERVICE_ACCOUNT` service account (dfo-docs-sync) through the workload identity
# provider `GCP_WIF_PROVIDER`, and writes a credentials file whose path it exports as
# GOOGLE_APPLICATION_CREDENTIALS. The service account's binding in infra accepts
# `refs/heads/dev` together with the `docsync` environment. `dfo-docsync` finds the
# credentials through Application Default Credentials, so no Google token is passed
# explicitly.
#
# GitHub access: the `dfo-docsync` GitHub App token is minted each run from the
# `DOCSYNC_APP_CLIENT_ID` variable and the `DOCSYNC_APP_PRIVATE_KEY` secret, scoped to
# contents and pull-requests write on the repositories docsync opens pull requests against
# and keeps state in, which are the `dev` branches of docs, apis, ai and skills. To admit
# another source repository, install `dfo-docsync` on it and add its name to the
# `repositories:` input of the "Mint a token for dfo-docsync" step. The token reaches the
# `dfo-docsync` command as DFO_DOCSYNC_TOKEN, and the commits it makes are authored by the
# App's bot user. `push` and `pull` list open docsync pull requests by the author
# `app/<slug>`, with the slug the token step reports, so the listing follows the App's
# real slug whatever `pr_author` in docsync.yaml says. The checkout does not persist the
# workflow token, because git would send its read-only credential ahead of the App token.
# The Run step gives git the App token through a credential helper in its environment.
#
# A missing variable or secret fails the run at the first step that names it.
name: Sync docs with Google Docs
on:
workflow_dispatch:
inputs:
command:
description: The dfo-docsync command to run.
type: choice
options: [init, push, pull]
required: false
default: push
# Runs queue behind each other. A cancelled push or pull could stop half way through
# writing Docs, the Sheet or the state branch.
concurrency:
group: dfo-docs-sync
cancel-in-progress: false
permissions:
contents: read
id-token: write
jobs:
sync:
# The WIF binding in infra and the environment's branch policy both admit only dev,
# and the guard keeps the dfo-docsync write token off branch runs.
if: github.ref == 'refs/heads/dev'
runs-on: ubuntu-latest
environment: docsync
timeout-minutes: 60
steps:
# Full history, because hub pages are read from git (`git show`) at their baseline.
- name: Check out dfo-docs
uses: actions/checkout@v7
with:
fetch-depth: 0
persist-credentials: false
- name: Install uv
uses: astral-sh/setup-uv@v10.0.0
- name: Set up Python 3.13
run: uv python install '3.13'
- name: Install dependencies
run: uv sync --frozen
# An unset variable or secret expands to an empty string, so this step names which
# ones are missing before the auth and token actions run.
- name: Check the credentials are configured
env:
GCP_WIF_PROVIDER: ${{ vars.GCP_WIF_PROVIDER }}
DOCSYNC_SERVICE_ACCOUNT: ${{ vars.DOCSYNC_SERVICE_ACCOUNT }}
DOCSYNC_APP_CLIENT_ID: ${{ vars.DOCSYNC_APP_CLIENT_ID }}
DOCSYNC_APP_PRIVATE_KEY: ${{ secrets.DOCSYNC_APP_PRIVATE_KEY }}
run: |
missing=0
if [ -z "$GCP_WIF_PROVIDER" ]; then
echo "::error::The GCP_WIF_PROVIDER variable is not set; the run cannot authenticate to Google."
missing=1
fi
if [ -z "$DOCSYNC_SERVICE_ACCOUNT" ]; then
echo "::error::The DOCSYNC_SERVICE_ACCOUNT variable is not set; the run cannot authenticate to Google."
missing=1
fi
if [ -z "$DOCSYNC_APP_CLIENT_ID" ]; then
echo "::error::The DOCSYNC_APP_CLIENT_ID variable is not set; the run cannot open pull requests or write state."
missing=1
fi
if [ -z "$DOCSYNC_APP_PRIVATE_KEY" ]; then
echo "::error::The DOCSYNC_APP_PRIVATE_KEY secret is not set or not available to this run; the run cannot open pull requests or write state."
missing=1
fi
exit "$missing"
- name: Authenticate to Google
uses: google-github-actions/auth@v3
with:
workload_identity_provider: ${{ vars.GCP_WIF_PROVIDER }}
service_account: ${{ vars.DOCSYNC_SERVICE_ACCOUNT }}
create_credentials_file: true
# The repositories are the ones docsync reads sources from and opens pull requests
# against, plus docs, which also holds the `docsync-state` branch.
- name: Mint a token for dfo-docsync
id: dfo-docsync
uses: actions/create-github-app-token@v3
with:
client-id: ${{ vars.DOCSYNC_APP_CLIENT_ID }}
private-key: ${{ secrets.DOCSYNC_APP_PRIVATE_KEY }}
repositories: docs,apis,ai,skills
permission-contents: write
permission-pull-requests: write
# The App's bot user is `<slug>[bot]`, and its noreply address carries the user id.
# git reads the identity from the global config for every commit docsync makes.
- name: Set the git identity to the dfo-docsync user
env:
GH_TOKEN: ${{ steps.dfo-docsync.outputs.token }}
APP_SLUG: ${{ steps.dfo-docsync.outputs.app-slug }}
run: |
set -euo pipefail
bot="${APP_SLUG}[bot]"
bot_id=$(gh api "/users/${APP_SLUG}%5Bbot%5D" --jq .id)
git config --global user.name "$bot"
git config --global user.email "${bot_id}+${bot}@users.noreply.github.com"
# GIT_CONFIG_COUNT and its KEY/VALUE pair add a credential helper for github.com to
# every git command in this step and in no other. The pushes of the edit branches and
# the `docsync-state` branch need it. The helper answers only `get` and reads the
# token from the environment, so the token stays out of config files and remote URLs.
- name: Run dfo-docsync
env:
DFO_DOCSYNC_TOKEN: ${{ steps.dfo-docsync.outputs.token }}
APP_SLUG: ${{ steps.dfo-docsync.outputs.app-slug }}
COMMAND: ${{ inputs.command }}
GIT_CONFIG_COUNT: "1"
GIT_CONFIG_KEY_0: credential.https://github.com.helper
GIT_CONFIG_VALUE_0: >-
!f() { test "$1" = get || return 0; echo username=x-access-token; echo "password=$DFO_DOCSYNC_TOKEN"; }; f
run: |
set -uo pipefail
args=()
if [ "$COMMAND" != "init" ]; then
args+=(--pr-author "app/$APP_SLUG")
fi
uv run dfo-docsync "$COMMAND" "${args[@]}"
# Baselines and Doc ids of unsaved states, with no tokens. Restore a file with
# `uv run dfo-docsync restore FILE`, adding --state-dir DIR for a local state directory.
- name: Upload the unsaved states
if: failure()
uses: actions/upload-artifact@v7
with:
name: docsync-unsaved-states
path: docsync-unsaved-states-*.json
if-no-files-found: ignore
retention-days: 30