Skip to content

Commit 334b18b

Browse files
authored
Merge branch 'main-enterprise' into yj-repo-in-many-suborgs
2 parents 4497c13 + 20a4508 commit 334b18b

File tree

11 files changed

+663
-243
lines changed

11 files changed

+663
-243
lines changed

.github/workflows/deploy-k8s.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,35 +29,35 @@ jobs:
2929
steps:
3030
- name: Checkout repository
3131
uses: actions/checkout@v4
32-
- uses: azure/login@v1
32+
- uses: azure/login@v2
3333
with:
3434
client-id: ${{ secrets.AZURE_CLIENT_ID }}
3535
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
3636
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
37-
- uses: azure/aks-set-context@v3
37+
- uses: azure/aks-set-context@v4
3838
with:
3939
resource-group: ${{env.AZURE_RESOURCE_GROUP}}
4040
cluster-name: ${{env.AZURE_AKS_CLUSTER}}
4141
id: login
4242
- run: |
4343
kubectl get deployment
4444
- name: app-env
45-
uses: azure/k8s-create-secret@v4
45+
uses: azure/k8s-create-secret@v5
4646
with:
4747
namespace: 'default'
4848
secret-type: 'generic'
4949
arguments: --from-literal=APP_ID=${{ secrets.APP_ID }} --from-literal=PRIVATE_KEY=${{ secrets.PRIVATE_KEY }} --from-literal=WEBHOOK_SECRET=${{ secrets.WEBHOOK_SECRET }}
5050
secret-name: app-env
5151
- name: Set imagePullSecret
52-
uses: azure/k8s-create-secret@v4
52+
uses: azure/k8s-create-secret@v5
5353
with:
5454
namespace: ${{env.AZURE_AKS_NAMESPACE}}
5555
container-registry-url: ${{env.IMAGE_REGISTRY_URL}}
5656
container-registry-username: ${{ secrets.DOCKER_USERNAME }}
5757
container-registry-password: ${{ secrets.DOCKER_PASSWORD }}
5858
secret-name: 'image-pull-secret'
5959
id: create-secret
60-
- uses: Azure/k8s-deploy@v4.10
60+
- uses: Azure/k8s-deploy@v5
6161
with:
6262
namespace: ${{env.AZURE_AKS_NAMESPACE}}
6363
manifests: |

README.md

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,12 @@
3232
>
3333
> Settings files must have a `.yml` extension only. For now, the `.yaml` extension is ignored.
3434
35+
3536
## How it works
3637

38+
`Safe-settings` is designed to run as a service listening for webhook events or as a scheduled job running on some regular cadence. It can also be triggered through GitHub Actions. (See the [How to use](#how-to-use) section for details on deploying and configuring.)
39+
40+
3741
### Events
3842
The App listens to the following webhook events:
3943

@@ -286,7 +290,21 @@ The following can be configured:
286290
- `Rulesets`
287291
- `Environments` - wait timer, required reviewers, prevent self review, protected branches deployment branch policy, custom deployment branch policy, variables, deployment protection rules
288292

289-
It is possible to provide an `include` or `exclude` settings to restrict the `collaborators`, `teams`, `labels` to a list of repos or exclude a set of repos for a collaborator.
293+
> [!important]
294+
> It is possible to provide an `include` or `exclude` settings to restrict the `collaborators`, `teams`, `labels` to a list of repos or exclude a set of repos for a collaborator.
295+
> The include/exclude pattern can also be for glob. For e.g.:
296+
```
297+
teams:
298+
- name: Myteam-admins
299+
permission: admin
300+
- name: Myteam-developers
301+
permission: push
302+
- name: Other-team
303+
permission: push
304+
include:
305+
- '*-config'
306+
```
307+
> Will only add `Other-team` to only `*-config` repos
290308
291309
See [`docs/sample-settings/settings.yml`](docs/sample-settings/settings.yml) for a sample settings file.
292310
@@ -364,11 +382,13 @@ You can pass environment variables; the easiest way to do it is via a `.env` fil
364382
365383
## How to use
366384
367-
1. __[Deploy and install the app](docs/deploy.md)__.
385+
1. Create an `admin` repo (or an alternative of your choosing) within your organization. Remember to set `CONFIG_REPO` if you choose something other than `admin`. See [Environment variables](#environment-variables) for more details.
386+
387+
2. Add the settings for the `org`, `suborgs`, and `repos`. Sample files can be found [here](docs/sample-settings).
388+
389+
3. __[Deploy and install the app](docs/deploy.md)__. Alternatively, the __[GitHub Actions Guide](docs/github-action.md)__ describes how to run `safe-settings` with GitHub Actions.
368390
369-
2. Create an `admin` repo (or an alternative of your choosing) within your organization. Remember to set `CONFIG_REPO` if you choose something other than `admin`. See [Environment variables](#environment-variables) for more details.
370391
371-
3. Add the settings for the `org`, `suborgs`, and `repos`. Sample files can be found [here](docs/sample-settings).
372392
373393
374394
## License

docs/github-action.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Running Safe-settings with GitHub Actions (GHA)
2+
3+
This guide describes how to schedule a full safe-settings sync using GitHub Actions. This assumes that an `admin` repository has been configured with your `safe-settings` configuration. Refer to the [How to Use](../README.md#how-to-use) docs for more details on that process.
4+
5+
6+
## GitHub App Creation
7+
Follow the [Create the GitHub App](deploy.md#create-the-github-app) guide to create an App in your GitHub account. This will allow `safe-settings` to access and modify your repos.
8+
9+
10+
## Defining the GitHub Action Workflow
11+
Running a full-sync with `safe-settings` can be done via `npm run full-sync`. This requires installing Node, such as with [actions/setup-node](https://github.com/actions/setup-node) (see example below). When doing so, the appropriate environment variables must be set (see the [Environment variables](#environment-variables) document for more details).
12+
13+
14+
### Example GHA Workflow
15+
The below example uses the GHA "cron" feature to run a full-sync every 4 hours. While not required, this example uses the `.github` repo as the `admin` repo (set via `ADMIN_REPO` env var) and the safe-settings configurations are stored in the `safe-settings/` directory (set via `CONFIG_PATH` and `DEPLOYMENT_CONFIG_FILE`).
16+
17+
```yaml
18+
name: Safe Settings Sync
19+
on:
20+
schedule:
21+
- cron: "0 */4 * * *"
22+
workflow_dispatch: {}
23+
24+
jobs:
25+
safeSettingsSync:
26+
runs-on: ubuntu-latest
27+
env:
28+
# Version/tag of github/safe-settings repo to use:
29+
SAFE_SETTINGS_VERSION: 2.1.13
30+
31+
# Path on GHA runner box where safe-settings code downloaded to:
32+
SAFE_SETTINGS_CODE_DIR: ${{ github.workspace }}/.safe-settings-code
33+
steps:
34+
# Self-checkout of 'admin' repo for access to safe-settings config:
35+
- uses: actions/checkout@v4
36+
37+
# Checkout of safe-settings repo for running full sync:
38+
- uses: actions/checkout@v4
39+
with:
40+
repository: github/safe-settings
41+
ref: $SAFE_SETTINGS_VERSION
42+
path: $SAFE_SETTINGS_CODE_DIR
43+
- uses: actions/setup-node@v4
44+
- run: npm install
45+
working-directory: $SAFE_SETTINGS_CODE_DIR
46+
- run: npm run full-sync
47+
working-directory: $SAFE_SETTINGS_CODE_DIR
48+
env:
49+
GH_ORG: ${{ vars.SAFE_SETTINGS_GH_ORG }}
50+
APP_ID: ${{ vars.SAFE_SETTINGS_APP_ID }}
51+
PRIVATE_KEY: ${{ secrets.SAFE_SETTINGS_PRIVATE_KEY }}
52+
GITHUB_CLIENT_ID: ${{ vars.SAFE_SETTINGS_GITHUB_CLIENT_ID }}
53+
GITHUB_CLIENT_SECRET: ${{ secrets.SAFE_SETTINGS_GITHUB_CLIENT_SECRET }}
54+
ADMIN_REPO: .github
55+
CONFIG_PATH: safe-settings
56+
DEPLOYMENT_CONFIG_FILE: ${{ github.workspace }}/safe-settings/deployment-settings.yml
57+
```

docs/github-settings/6. deployment-environments.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ environments:
2929
deployment_branch_policy:
3030
protected_branches: true
3131
custom_branch_policies: false
32+
deployment_protection_rules:
33+
- app_id: 25112
3234
variables:
3335
- name: MY_AWESOME_VAR
3436
value: '845705'

full-sync.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
const { createProbot } = require('probot')
2+
const appFn = require('./')
3+
4+
const probot = createProbot()
5+
probot.log.info('Starting full sync.')
6+
const app = appFn(probot, {})
7+
app.syncInstallation()
8+
.then(settings => {
9+
if (settings.errors.length > 0) {
10+
probot.log.error('Errors occurred during full sync.')
11+
process.exit(1)
12+
} else {
13+
probot.log.info('Done with full sync.')
14+
}
15+
})
16+
.catch(error => {
17+
process.stdout.write(`Unexpected error during full sync: ${error}\n`)
18+
process.exit(1)
19+
})

lib/mergeDeep.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const mergeBy = require('./mergeArrayBy')
22
const DeploymentConfig = require('./deploymentConfig')
33

4-
const NAME_FIELDS = ['name', 'username', 'actor_id', 'login', 'type', 'key_prefix']
4+
const NAME_FIELDS = ['name', 'username', 'actor_id', 'login', 'type', 'key_prefix', 'context']
55
const NAME_USERNAME_PROPERTY = item => NAME_FIELDS.find(prop => Object.prototype.hasOwnProperty.call(item, prop))
66
const GET_NAME_USERNAME_PROPERTY = item => { if (NAME_USERNAME_PROPERTY(item)) return item[NAME_USERNAME_PROPERTY(item)] }
77

@@ -91,6 +91,10 @@ class MergeDeep {
9191
// One of the oddities is when we compare objects, we are only interested in the properties of source
9292
// So any property in the target that is not in the source is not treated as a deletion
9393
for (const key in source) {
94+
// Skip prototype pollution vectors
95+
if (key === "__proto__" || key === "constructor") {
96+
continue;
97+
}
9498
// Logic specific for Github
9599
// API response includes urls for resources, or other ignorable fields; we can ignore them
96100
if (key.indexOf('url') >= 0 || this.ignorableFields.indexOf(key) >= 0) {

lib/plugins/diffable.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
const ErrorStash = require('./errorStash')
2323
const MergeDeep = require('../mergeDeep')
2424
const NopCommand = require('../nopcommand')
25+
const Glob = require('../glob')
2526
const ignorableFields = ['id', 'node_id', 'default', 'url']
2627
module.exports = class Diffable extends ErrorStash {
2728
constructor (nop, github, repo, entries, log, errors) {
@@ -39,7 +40,10 @@ module.exports = class Diffable extends ErrorStash {
3940
// this.log.debug(` entries ${JSON.stringify(filteredEntries)}`)
4041
filteredEntries = filteredEntries.filter(attrs => {
4142
if (Array.isArray(attrs.exclude)) {
42-
if (!attrs.exclude.includes(this.repo.repo)) {
43+
const excludeGlobs = attrs.exclude.map(exc => new Glob(exc));
44+
const excludeGlobsMatch = excludeGlobs.some(glob => !!this.repo.repo.match(glob));
45+
46+
if (!attrs.exclude.includes(this.repo.repo) && !excludeGlobsMatch) {
4347
// this.log.debug(`returning not excluded entry = ${JSON.stringify(attrs)} for repo ${this.repo.repo}`)
4448
return true
4549
} else {
@@ -53,7 +57,10 @@ module.exports = class Diffable extends ErrorStash {
5357
})
5458
filteredEntries = filteredEntries.filter(attrs => {
5559
if (Array.isArray(attrs.include)) {
56-
if (attrs.include.includes(this.repo.repo)) {
60+
const includeGlobs = attrs.include.map(inc => new Glob(inc));
61+
const includeGlobsMatch = includeGlobs.some(glob => !!this.repo.repo.match(glob));
62+
63+
if (attrs.include.includes(this.repo.repo) || includeGlobsMatch) {
5764
// this.log.debug(`returning included entry = ${JSON.stringify(attrs)} for repo ${this.repo.repo}`)
5865
return true
5966
} else {

lib/settings.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ class Settings {
2222
settings.logError(error.message)
2323
await settings.handleResults()
2424
}
25+
return settings
2526
}
2627

2728
static async syncSubOrgs(nop, context, suborg, repo, config, ref) {

0 commit comments

Comments
 (0)