Skip to content

Commit 07baaa4

Browse files
Add documentation and updates to code
Signed-off-by: Lukasz Gryglicki <[email protected]>
1 parent 5fcf079 commit 07baaa4

File tree

4 files changed

+154
-29
lines changed

4 files changed

+154
-29
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: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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+
This property is a Map attribute that contains mapping from repository pattern to bot username and email pattern.
8+
9+
Each pattern is a string and can be one of three possible types:
10+
- `"name"` - exact match for repository name, GitHub username, or email address.
11+
- `"re:regexp"` - regular expression match for repository name, GitHub username, or email address.
12+
- `"*"` - matches all.
13+
14+
So the format is like `"repository_pattern": "github_username_pattern;email_pattern"`.
15+
16+
There can be multiple entries under one Github Organization DynamoDB entry.
17+
18+
Example:
19+
```
20+
{
21+
(...)
22+
"organization_name": {
23+
"S": "linuxfoundation"
24+
},
25+
"skip_cla": {
26+
"M": {
27+
"*": {
28+
"S": "copilot-swe-agent[bot];*"
29+
},
30+
"repo1": {
31+
"S": "re:vee?rendra;*"
32+
}
33+
}
34+
},
35+
(...)
36+
}
37+
```
38+
39+
Algorithm to match pattern is as follows:
40+
- 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.
41+
- 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.
42+
- If no match is found, we check for `*` entry. If it exists, we use that entry and stop searching.
43+
- If no match is found, we don't skip CLA check.
44+
- Now when we have the entry, it is in the following format: `github_username_pattern;email_pattern`.
45+
- We check both GitHub username and email address against the patterns. Algorith is the same - username and email patterns can be either direct match or `re:regexp` or `*`.
46+
- If both username and email match the patterns, we skip CLA check. If username or email is not set but the pattern is `*` it means hit.
47+
- So setting pattern to `username_pattern;*` means that we only check for username match and assume all emails are valid.
48+
- 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 checked first.
49+
50+
51+
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:
52+
- `` MODE=mode ./utils/skip_cla_entry.sh 'org-name' 'repo-pattern' 'github-username-pattern' 'email-pattern' ``.
53+
- `` MODE=add-key ./utils/skip_cla_entry.sh 'sun-test-org' '*' 'copilot-swe-agent[bot]' '*' ``.
54+
55+
`MODE` can be one of:
56+
- `put-item`: Overwrites/adds the entire `skip_cla` property. Needs all 4 arguments org, repo, username and email.
57+
- `add-key`: Adds or updates a key/value inside the `skip_cla` map (preserves other keys). Needs all 4 args.
58+
- `delete-key`: Removes a key from the `skip_cla` map. Needs 2 arguments: org and repo.
59+
- `delete-item`: Deletes the entire `skip_cla` item. Needs 1 argument: org.
60+
61+
62+
You can also use AWS CLI to update the `skip_cla` property. Here is an example command:
63+
64+
To add a new `skip_cla` entry:
65+
66+
```
67+
aws --profile "lfproduct-prod" --region "us-east-1" dynamodb update-item \
68+
--table-name "cla-prod-github-orgs" \
69+
--key '{"organization_name": {"S": "linuxfoundation"}}' \
70+
--update-expression 'SET skip_cla = :val' \
71+
--expression-attribute-values '{":val": {"M": {"re:^easycla":{"S":"copilot-swe-agent[bot];*"}}}}'
72+
```
73+
74+
To add a new key to an existing `skip_cla` entry (or replace the existing key):
75+
76+
```
77+
aws --profile "lfproduct-prod" --region "us-east-1" dynamodb update-item \
78+
--table-name "cla-prod-github-orgs" \
79+
--key '{"organization_name": {"S": "linuxfoundation"}}' \
80+
--update-expression "SET skip_cla.#repo = :val" \
81+
--expression-attribute-names '{"#repo": "re:^easycla"}' \
82+
--expression-attribute-values '{":val": {"S": "copilot-swe-agent[bot];*"}}'
83+
```
84+
85+
To delete a key from an existing `skip_cla` entry:
86+
87+
```
88+
aws --profile "lfproduct-prod" --region "us-east-1" dynamodb update-item \
89+
--table-name "cla-prod-github-orgs" \
90+
--key '{"organization_name": {"S": "linuxfoundation"}}' \
91+
--update-expression "REMOVE skip_cla.#repo" \
92+
--expression-attribute-names '{"#repo": "re:^easycla"}'
93+
```
94+
95+
To delete the entire `skip_cla` entry:
96+
97+
```
98+
aws --profile "lfproduct-prod" --region "us-east-1" dynamodb update-item \
99+
--table-name "cla-prod-github-orgs" \
100+
--key '{"organization_name": {"S": "linuxfoundation"}}' \
101+
--update-expression "REMOVE skip_cla"
102+
```
103+
104+
To see given organization's entry: `./utils/scan.sh github-orgs organization_name sun-test-org`.
105+
106+
Or using AWS CLI:
107+
108+
```
109+
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'
110+
```
111+

cla-backend/cla/models/github_models.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -961,6 +961,9 @@ def is_actor_skipped(self, actor, config):
961961
return False
962962

963963
def strip_org(self, repo_full):
964+
"""
965+
Removes the organization part from the repository name.
966+
"""
964967
if '/' in repo_full:
965968
return repo_full.split('/', 1)[1]
966969
return repo_full
@@ -982,7 +985,7 @@ def skip_whitelisted_bots(self, org_model, org_repo, actors_missing_cla) -> Tupl
982985
"*": "<username_pattern>;<email_pattern>"
983986
}
984987
where:
985-
- repo-name is the exact repository name (e.g., "my-org/my-repo")
988+
- repo-name is the exact repository name under given org (e.g., "my-repo" not "my-org/my-repo")
986989
- re:repo-regexp is a regex pattern to match repository names
987990
- * is a wildcard that applies to all repositories
988991
- <username_pattern> is a GitHub username pattern (exact match or regex prefixed by re: or match all '*')
@@ -994,41 +997,41 @@ def skip_whitelisted_bots(self, org_model, org_repo, actors_missing_cla) -> Tupl
994997
repo = self.strip_org(org_repo)
995998
skip_cla = org_model.get_skip_cla()
996999
if skip_cla is None:
997-
cla.log.debug("skip_cla is not set, skipping whitelisted bots check")
1000+
cla.log.debug("skip_cla is not set on '%s', skipping whitelisted bots check", org_repo)
9981001
return actors_missing_cla, []
9991002

10001003
if hasattr(skip_cla, "as_dict"):
10011004
skip_cla = skip_cla.as_dict()
10021005
config = ''
10031006
# 1. Exact match
10041007
if repo in skip_cla:
1005-
cla.log.debug("skip_cla config found for repo %s: %s (exact hit)", repo, skip_cla[repo])
1008+
cla.log.debug("skip_cla config found for repo %s: %s (exact hit)", org_repo, skip_cla[repo])
10061009
config = skip_cla[repo]
10071010

10081011
# 2. Regex pattern (if no exact hit)
10091012
if config == '':
1010-
cla.log.debug("No skip_cla config found for repo %s, checking regex patterns", repo)
1013+
cla.log.debug("No skip_cla config found for repo %s, checking regex patterns", org_repo)
10111014
for k, v in skip_cla.items():
10121015
if not isinstance(k, str) or not k.startswith("re:"):
10131016
continue
10141017
pattern = k[3:]
10151018
try:
10161019
if re.search(pattern, repo):
10171020
config = v
1018-
cla.log.debug("Found skip_cla config for repo %s: %s via regex pattern: %s", repo, config, pattern)
1021+
cla.log.debug("Found skip_cla config for repo %s: %s via regex pattern: %s", org_repo, config, pattern)
10191022
break
10201023
except re.error as e:
1021-
cla.log.warning("Invalid regex in skip_cla: %s (%s)", k, e)
1024+
cla.log.warning("Invalid regex in skip_cla: %s (%s) for repo: %s", k, e, org_repo)
10221025
continue
10231026

10241027
# 3. Wildcard fallback
10251028
if config == '' and '*' in skip_cla:
1026-
cla.log.debug("No skip_cla config found for repo %s, using wildcard config", repo)
1029+
cla.log.debug("No skip_cla config found for repo %s, using wildcard config", org_repo)
10271030
config = skip_cla['*']
10281031

10291032
# 4. No match
10301033
if config == '':
1031-
cla.log.debug("No skip_cla config found for repo %s, skipping whitelisted bots check", repo)
1034+
cla.log.debug("No skip_cla config found for repo %s, skipping whitelisted bots check", org_repo)
10321035
return actors_missing_cla, []
10331036

10341037
out_actors_missing_cla = []

utils/skip_cla_entry.sh

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#!/bin/bash
22
# MODE=mode ./utils/skip_cla_entry.sh sun-test-org '*' 'copilot-swe-agent[bot]' '*'
3-
# put-item Overwrites the entire item (skip_cla and all other attributes if needed)
3+
# put-item Overwrites/adds the entire `skip_cla` entry.
44
# add-key Adds or updates a key/value inside the skip_cla map (preserves other keys)
55
# delete-key Removes a key from the skip_cla map
6-
# delete-item Deletes the entire DynamoDB item (removes the whole row)
6+
# delete-item Deletes the entire `skip_cla`entry.
77
#
88
# MODE=add-key ./utils/skip_cla_entry.sh sun-test-org 'repo1' 're:vee?rendra' '*'
99
# ./utils/scan.sh github-orgs organization_name sun-test-org
@@ -27,44 +27,44 @@ case "$MODE" in
2727
echo "Usage: $0 <organization_name> <repo or *> <bot username> <email regexp>"
2828
exit 1
2929
fi
30-
aws --profile "lfproduct-${STAGE}" --region "${REGION}" dynamodb update-item \
31-
--table-name "cla-${STAGE}-github-orgs" \
32-
--key "{\"organization_name\": {\"S\": \"${1}\"}}" \
30+
CMD="aws --profile \"lfproduct-${STAGE}\" --region \"${REGION}\" dynamodb update-item \
31+
--table-name \"cla-${STAGE}-github-orgs\" \
32+
--key '{\"organization_name\": {\"S\": \"${1}\"}}' \
3333
--update-expression 'SET skip_cla = :val' \
34-
--expression-attribute-values "{\":val\": {\"M\": {\"${2}\":{\"S\":\"${3};${4}\"}}}}"
34+
--expression-attribute-values '{\":val\": {\"M\": {\"${2}\":{\"S\":\"${3};${4}\"}}}}'"
3535
;;
3636
add-key)
3737
if ( [ -z "${1}" ] || [ -z "${2}" ] || [ -z "${3}" ] || [ -z "${4}" ] ); then
3838
echo "Usage: $0 <organization_name> <repo or *> <bot username> <email regexp>"
3939
exit 1
4040
fi
41-
aws --profile "lfproduct-${STAGE}" --region "${REGION}" dynamodb update-item \
42-
--table-name "cla-${STAGE}-github-orgs" \
43-
--key "{\"organization_name\": {\"S\": \"${1}\"}}" \
44-
--update-expression "SET skip_cla.#repo = :val" \
45-
--expression-attribute-names "{\"#repo\": \"${2}\"}" \
46-
--expression-attribute-values "{\":val\": {\"S\": \"${3};${4}\"}}"
41+
CMD="aws --profile \"lfproduct-${STAGE}\" --region \"${REGION}\" dynamodb update-item \
42+
--table-name \"cla-${STAGE}-github-orgs\" \
43+
--key '{\"organization_name\": {\"S\": \"${1}\"}}' \
44+
--update-expression 'SET skip_cla.#repo = :val' \
45+
--expression-attribute-names '{\"#repo\": \"${2}\"}' \
46+
--expression-attribute-values '{\":val\": {\"S\": \"${3};${4}\"}}'"
4747
;;
4848
delete-key)
4949
if ( [ -z "${1}" ] || [ -z "${2}" ] ); then
5050
echo "Usage: $0 <organization_name> <repo or *>"
5151
exit 1
5252
fi
53-
aws --profile "lfproduct-${STAGE}" --region "${REGION}" dynamodb update-item \
54-
--table-name "cla-${STAGE}-github-orgs" \
55-
--key "{\"organization_name\": {\"S\": \"${1}\"}}" \
56-
--update-expression "REMOVE skip_cla.#repo" \
57-
--expression-attribute-names "{\"#repo\": \"${2}\"}"
53+
CMD="aws --profile \"lfproduct-${STAGE}\" --region \"${REGION}\" dynamodb update-item \
54+
--table-name \"cla-${STAGE}-github-orgs\" \
55+
--key '{\"organization_name\": {\"S\": \"${1}\"}}' \
56+
--update-expression 'REMOVE skip_cla.#repo' \
57+
--expression-attribute-names '{\"#repo\": \"${2}\"}'"
5858
;;
5959
delete-item)
6060
if [ -z "${1}" ]; then
6161
echo "Usage: $0 <organization_name>"
6262
exit 1
6363
fi
64-
aws --profile "lfproduct-${STAGE}" --region "${REGION}" dynamodb update-item \
65-
--table-name "cla-${STAGE}-github-orgs" \
66-
--key "{\"organization_name\": {\"S\": \"${1}\"}}" \
67-
--update-expression "REMOVE skip_cla"
64+
CMD="aws --profile \"lfproduct-${STAGE}\" --region \"${REGION}\" dynamodb update-item \
65+
--table-name \"cla-${STAGE}-github-orgs\" \
66+
--key '{\"organization_name\": {\"S\": \"${1}\"}}' \
67+
--update-expression 'REMOVE skip_cla'"
6868
;;
6969
*)
7070
echo "$0: Unknown MODE: $MODE"
@@ -73,3 +73,10 @@ case "$MODE" in
7373
;;
7474
esac
7575

76+
if [ ! -z "$DEBUG" ]
77+
then
78+
echo "$CMD"
79+
fi
80+
81+
eval $CMD
82+

0 commit comments

Comments
 (0)