Skip to content

Commit 0d4d7a4

Browse files
Merge pull request #4729 from linuxfoundation/unicron-4701-allow-bots-to-skip-cla-prod
Add support for skipping CLA requirement for bots
2 parents 4e8bab2 + 02bdd45 commit 0d4d7a4

File tree

16 files changed

+864
-24
lines changed

16 files changed

+864
-24
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ The following diagram explains the EasyCLA architecture.
6161

6262
![CLA Architecture](.gitbook/assets/easycla-architecture-overview.png)
6363

64+
## Bot Whitelisting
65+
66+
For whitelisting bots please see the [Whitelisting Bots](WHITELISTING_BOTS.md) documentation.
67+
6468
## EasyCLA Release Process
6569

6670
The following diagram illustrates the EasyCLA release process:

WHITELISTING_BOTS.md

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
## Whitelisting Bots
2+
3+
You can allow specific bot users to automatically pass the CLA check.
4+
5+
This can be done on the GitHub organization level by setting the `skip_cla` property on `cla-{stage}-github-orgs` DynamoDB table.
6+
7+
Replace `{stage}` with either `dev` or `prod`.
8+
9+
This property is a Map attribute that contains mapping from repository pattern to bot username (GitHub login), email and name pattern.
10+
11+
Example `username/login` is `lukaszgryglicki` (like any `username/login` that can be accessed via `https://github.com/username`).
12+
13+
Example name is `"Lukasz Gryglicki"`.
14+
15+
Email pattern and name pattern are optional and `*` is assumed for them if not specified.
16+
17+
Each pattern is a string and can be one of three possible types (and are checked tin this order):
18+
- `"name"` - exact match for repository name, GitHub login/username, email address, GitHub name.
19+
- `"re:regexp"` - regular expression match for repository name, GitHub username, or email address.
20+
- `"*"` - matches all.
21+
22+
So the format is like `"repository_pattern": "github_username_pattern;email_pattern;name_pattern"`. `;` is used as a separator.
23+
24+
You can also specify multiple patterns so different set is used for multiple users - in such case configuration must start with `[`, end with `]` and be `||` separated.
25+
26+
For example: `"[copilot-swe-agent[bot];*;*||re:(?i)^l(ukasz)?gryglicki$;*;re:Gryglicki]"`.
27+
28+
Full format is like `"repository_pattern": "[github_username_pattern;email_pattern;name_pattern||..]"`.
29+
30+
Other complex example: `"re:(?i)^repo\d*$": "[veerendra||re:(?i)^l(ukasz)?gryglicki$;[email protected]||*;*;Lukasz Gryglicki]"`.
31+
32+
This matches one of:
33+
- GitHub username/login `veerendra` no matter the email and name.
34+
- GitHub username/login like lgryglicki, LukaszGryglicki and similar with email [email protected], name doesn't matter.
35+
- GitHub name "Lukasz Gryglicki" email and username/login doesn't matter.
36+
37+
There can be multiple entries under one Github Organization DynamoDB entry.
38+
39+
Example:
40+
```
41+
{
42+
(...)
43+
"organization_name": {
44+
"S": "linuxfoundation"
45+
},
46+
"skip_cla": {
47+
"M": {
48+
"*": {
49+
"S": "copilot-swe-agent[bot];re:^\\d+\\+Copilot@users\\.noreply\\.github\\.com$;*"
50+
},
51+
"re:(?i)^repo[0-9]+$": {
52+
"S": "re:vee?rendra;*;*"
53+
}
54+
}
55+
},
56+
(...)
57+
}
58+
```
59+
60+
Algorithm to match pattern is as follows:
61+
- First we check repository name for exact match. Repository name is without the organization name, so for `https://github.com/linuxfoundation/easycla` it is just `easycla`. If we find an entry in `skip_cla` for `easycla` that entry is used and we stop searching.
62+
- If no exact match is found, we check for regular expression match. Only keys starting with `re:` are considered. If we find a match, we use that entry and stop searching.
63+
- If no match is found, we check for `*` entry. If it exists, we use that entry and stop searching.
64+
- If no match is found, we don't skip CLA check.
65+
- Now when we have the entry, it is in the following format: `github_username_pattern;email_pattern;name_pattern` or `"[github_username_pattern;email_pattern;name_pattern||...]" (array)`.
66+
- We check GitHub username/login, email address and name against the patterns. Algorithm is the same - username, email and name patterns can be either direct match or `re:regexp` or `*`.
67+
- If username, email and name match the patterns, we skip CLA check. If username or email or name is not set but the pattern is `*` it means hit.
68+
- So setting pattern to `username_pattern;*;*` or `username_pattern` (which is equivalent) means that we only check for username match and assume all emails and names are valid.
69+
- Any actor that matches any of the entries in the array will be skipped (logical OR).
70+
- If we set `repo_pattern` to `*` it means that this configuration applies to all repositories in the organization. If there are also specific repository patterns, they will be used instead of `*` (fallback for all).
71+
72+
73+
There is a script that allows you to update the `skip_cla` property in the DynamoDB table. It is located in `utils/skip_cla_entry.sh`. You can run it like this:
74+
- `` MODE=mode ./utils/skip_cla_entry.sh 'org-name' 'repo-pattern' 'github-username-pattern;email-pattern;name_pattern' ``.
75+
- `` MODE=add-key ./utils/skip_cla_entry.sh 'sun-test-org' '*' 'copilot-swe-agent[bot];*;*' ``.
76+
- Complex example: `` MODE=add-key ./utils/skip_cla_entry.sh 'sun-test-org' 're:(?i)^repo[0-9]+$' '[re:(?i)^l(ukasz)?gryglicki$;re:(?i)^l(ukasz)?gryglicki@;*||copilot-swe-agent[bot]]' ``.
77+
78+
`MODE` can be one of:
79+
- `put-item`: Overwrites/adds the entire `skip_cla` property. Needs all 3 arguments org, repo, and pattern.
80+
- `add-key`: Adds or updates a key/value inside the `skip_cla` map (preserves other keys). Needs all 3 args.
81+
- `delete-key`: Removes a key from the `skip_cla` map. Needs 2 arguments: org and repo.
82+
- `delete-item`: Deletes the entire `skip_cla` from the item. Needs 1 argument: org.
83+
84+
85+
You can also use AWS CLI to update the `skip_cla` property. Here is an example command:
86+
87+
To add a new `skip_cla` entry:
88+
89+
```
90+
aws --profile "lfproduct-prod" --region "us-east-1" dynamodb update-item \
91+
--table-name "cla-prod-github-orgs" \
92+
--key '{"organization_name": {"S": "linuxfoundation"}}' \
93+
--update-expression 'SET skip_cla = :val' \
94+
--expression-attribute-values '{":val": {"M": {"re:^easycla":{"S":"copilot-swe-agent[bot];*;*"}}}}'
95+
```
96+
97+
To add a new key to an existing `skip_cla` entry (or replace the existing key):
98+
99+
```
100+
aws --profile "lfproduct-prod" --region "us-east-1" dynamodb update-item \
101+
--table-name "cla-prod-github-orgs" \
102+
--key '{"organization_name": {"S": "linuxfoundation"}}' \
103+
--update-expression "SET skip_cla.#repo = :val" \
104+
--expression-attribute-names '{"#repo": "re:^easycla"}' \
105+
--expression-attribute-values '{":val": {"S": "copilot-swe-agent[bot]"}}'
106+
```
107+
108+
To delete a key from an existing `skip_cla` entry:
109+
110+
```
111+
aws --profile "lfproduct-prod" --region "us-east-1" dynamodb update-item \
112+
--table-name "cla-prod-github-orgs" \
113+
--key '{"organization_name": {"S": "linuxfoundation"}}' \
114+
--update-expression "REMOVE skip_cla.#repo" \
115+
--expression-attribute-names '{"#repo": "re:^easycla"}'
116+
```
117+
118+
To delete the entire `skip_cla` entry:
119+
120+
```
121+
aws --profile "lfproduct-prod" --region "us-east-1" dynamodb update-item \
122+
--table-name "cla-prod-github-orgs" \
123+
--key '{"organization_name": {"S": "linuxfoundation"}}' \
124+
--update-expression "REMOVE skip_cla"
125+
```
126+
127+
To see given organization's entry: `./utils/scan.sh github-orgs organization_name sun-test-org`.
128+
129+
Or using AWS CLI:
130+
131+
```
132+
aws --profile "lfproduct-prod" dynamodb scan --table-name "cla-prod-github-orgs" --filter-expression "contains(organization_name,:v)" --expression-attribute-values "{\":v\":{\"S\":\"linuxfoundation\"}}" --max-items 100 | jq -r '.Items'
133+
```
134+
135+
To check for log entries related to skipping CLA check, you can use the following command: `` STAGE=dev DTFROM='1 hour ago' DTTO='1 second ago' ./utils/search_aws_log_group.sh 'cla-backend-dev-githubactivity' 'skip_cla' ``.
136+
137+
# Example setup on prod
138+
139+
To add first `skip_cla` value for an organization:
140+
```
141+
aws --profile lfproduct-prod --region us-east-1 dynamodb update-item --table-name "cla-prod-github-orgs" --key '{"organization_name": {"S": "open-telemetry"}}' --update-expression 'SET skip_cla = :val' --expression-attribute-values '{":val": {"M": {"otel-arrow":{"S":"copilot-swe-agent[bot];re:^\\d+\\+Copilot@users\\.noreply\\.github\\.com$;*"}}}}'
142+
aws --profile lfproduct-prod --region us-east-1 dynamodb update-item --table-name "cla-prod-github-orgs" --key '{"organization_name": {"S": "openfga"}}' --update-expression 'SET skip_cla = :val' --expression-attribute-values '{":val": {"M": {"vscode-ext":{"S":"copilot-swe-agent[bot];re:^\\d+\\+Copilot@users\\.noreply\\.github\\.com$;*"}}}}'
143+
```
144+
145+
To add additional repositories entries without overwriting the existing `skip_cla` value:
146+
```
147+
aws --profile lfproduct-prod --region us-east-1 dynamodb update-item --table-name "cla-prod-github-orgs" --key '{"organization_name": {"S": "open-telemetry"}}' --update-expression 'SET skip_cla.#repo = :val' --expression-attribute-names '{"#repo": "*"}' --expression-attribute-values '{":val": {"S": "copilot-swe-agent[bot];re:^\\d+\\+Copilot@users\\.noreply\\.github\\.com$;*"}}'
148+
aws --profile lfproduct-prod --region us-east-1 dynamodb update-item --table-name "cla-prod-github-orgs" --key '{"organization_name": {"S": "openfga"}}' --update-expression 'SET skip_cla.#repo = :val' --expression-attribute-names '{"#repo": "*"}' --expression-attribute-values '{":val": {"S": "copilot-swe-agent[bot];re:^\\d+\\+Copilot@users\\.noreply\\.github\\.com$;*"}}'
149+
```
150+
151+
To delete a specific repo entry from `skip_cla`:
152+
```
153+
aws --profile "lfproduct-prod" --region "us-east-1" dynamodb update-item --table-name "cla-prod-github-orgs" --key '{"organization_name": {"S": "open-telemetry"}}' --update-expression 'REMOVE skip_cla.#repo' --expression-attribute-names '{"#repo": "*"}'
154+
aws --profile "lfproduct-prod" --region "us-east-1" dynamodb update-item --table-name "cla-prod-github-orgs" --key '{"organization_name": {"S": "openfga"}}' --update-expression 'REMOVE skip_cla.#repo' --expression-attribute-names '{"#repo": "*"}'
155+
```
156+
157+
To delete the entire `skip_cla` attribute:
158+
```
159+
aws --profile "lfproduct-prod" --region "us-east-1" dynamodb update-item --table-name "cla-prod-github-orgs" --key '{"organization_name": {"S": "open-telemetry"}}' --update-expression 'REMOVE skip_cla'
160+
aws --profile "lfproduct-prod" --region "us-east-1" dynamodb update-item --table-name "cla-prod-github-orgs" --key '{"organization_name": {"S": "openfga"}}' --update-expression 'REMOVE skip_cla'
161+
```
162+
163+
To check values:
164+
```
165+
aws --profile "lfproduct-prod" dynamodb scan --table-name "cla-prod-github-orgs" --filter-expression "contains(organization_name,:v)" --expression-attribute-values "{\":v\":{\"S\":\"open-telemetry\"}}" --max-items 100 | jq -r '.Items'
166+
aws --profile "lfproduct-prod" dynamodb scan --table-name "cla-prod-github-orgs" --filter-expression "contains(organization_name,:v)" --expression-attribute-values "{\":v\":{\"S\":\"openfga\"}}" --max-items 100 | jq -r '.Items'
167+
aws --profile "lfproduct-prod" dynamodb scan --table-name "cla-prod-github-orgs" --filter-expression "contains(organization_name,:v)" --expression-attribute-values "{\":v\":{\"S\":\"open-telemetry\"}}" --max-items 100 | jq -r '.Items[0].skip_cla.M["otel-arrow"]["S"]'
168+
aws --profile "lfproduct-prod" dynamodb scan --table-name "cla-prod-github-orgs" --filter-expression "contains(organization_name,:v)" --expression-attribute-values "{\":v\":{\"S\":\"openfga\"}}" --max-items 100 | jq -r '.Items[0].skip_cla.M["vscode-ext"]["S"]'
169+
```
170+

cla-backend-go/events/event_data.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,23 @@ type CorporateSignatureSignedEventData struct {
457457
SignatoryName string
458458
}
459459

460+
// BypassCLAEventData event data model
461+
type BypassCLAEventData struct {
462+
Repo string
463+
Config string
464+
Actor string
465+
}
466+
467+
func (ed *BypassCLAEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) {
468+
data := fmt.Sprintf("repo='%s', config='%s', actor='%s'", ed.Repo, ed.Config, ed.Actor)
469+
return data, true
470+
}
471+
472+
func (ed *BypassCLAEventData) GetEventSummaryString(args *LogEventArgs) (string, bool) {
473+
data := fmt.Sprintf("repo='%s', config='%s', actor='%s'", ed.Repo, ed.Config, ed.Actor)
474+
return data, true
475+
}
476+
460477
func (ed *CorporateSignatureSignedEventData) GetEventDetailsString(args *LogEventArgs) (string, bool) {
461478
data := fmt.Sprintf("The signature was signed for the project %s and company %s by %s", args.ProjectName, ed.CompanyName, ed.SignatoryName)
462479
if args.UserName != "" {

cla-backend-go/events/event_types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,6 @@ const (
9999

100100
IndividualSignatureSigned = "individual.signature.signed"
101101
CorporateSignatureSigned = "corporate.signature.signed"
102+
103+
BypassCLA = "Bypass CLA"
102104
)

0 commit comments

Comments
 (0)