Skip to content

Commit

Permalink
Merge pull request #36 from chrheg/feature-add-to-project
Browse files Browse the repository at this point in the history
feat: add-project-support
  • Loading branch information
zkoppert authored Jan 30, 2024
2 parents 83274c2 + a7c4271 commit dcd15d3
Show file tree
Hide file tree
Showing 5 changed files with 391 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ Below are the allowed configuration options:
| `BODY` | False | "Dependabot could be enabled for this repository. Please enable it by merging this pull request so that we can keep our dependencies up to date and secure." | The body of the issue or pull request that will be created if dependabot could be enabled. |
| `COMMIT_MESSAGE` | False | "Create dependabot.yaml" | The commit message for the pull request that will be created if dependabot could be enabled. |
| `CREATED_AFTER_DATE` | False | none | If a value is set, this action will only consider repositories created on or after this date for dependabot enablement. This is useful if you want to only consider newly created repositories. If I set up this action to run weekly and I only want to scan for repos created in the last week that need dependabot enabled, then I would set `CREATED_AFTER_DATE` to 7 days ago. That way only repositories created after 7 days ago will be considered for dependabot enablement. If not set or set to nothing, all repositories will be scanned and a duplicate issue/pull request may occur. Ex: 2023-12-31 for Dec. 31st 2023 |
| `PROJECT_ID` | False | "" | If set, this will assign the issue or pull request to the project with the given ID. ( The project ID on GitHub can be located by navigating to the respective project and observing the URL's end.) **The `ORGANIZATION` variable is required** |
| `DRY_RUN` | False | false | If set to true, this action will not create any issues or pull requests. It will only log the repositories that could have dependabot enabled. This is useful for testing. |

### Example workflows
Expand Down
17 changes: 16 additions & 1 deletion env.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,18 @@

def get_env_vars() -> (
tuple[
str | None, list[str], str, str, list[str], str, str, str, str | None, bool, str
str | None,
list[str],
str,
str,
list[str],
str,
str,
str,
str | None,
bool,
str,
str | None,
]
):
"""
Expand Down Expand Up @@ -118,6 +129,9 @@ def get_env_vars() -> (
else:
dry_run_bool = False

project_id = os.getenv("PROJECT_ID")
if project_id and not project_id.isnumeric():
raise ValueError("PROJECT_ID environment variable is not numeric")
return (
organization,
repositories_list,
Expand All @@ -130,4 +144,5 @@ def get_env_vars() -> (
created_after_date,
dry_run_bool,
commit_message,
project_id,
)
124 changes: 123 additions & 1 deletion evergreen.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,21 @@ def main(): # pragma: no cover
created_after_date,
dry_run,
commit_message,
project_id,
) = env.get_env_vars()

# Auth to GitHub.com or GHE
github_connection = auth.auth_to_github(token, ghe)

# If Project ID is set lookup the global project ID
if project_id:
# Check Organization is set as it is required for linking to a project
if not organization:
raise ValueError(
"ORGANIZATION environment variable was not set. Please set it"
)
project_id = get_global_project_id(token, organization, project_id)

# Get the repositories from the organization or list of repositories
repos = get_repos_iterator(organization, repository_list, github_connection)

Expand Down Expand Up @@ -87,6 +97,12 @@ def main(): # pragma: no cover
count_eligible += 1
issue = repo.create_issue(title, body)
print("\tCreated issue " + issue.html_url)
if project_id:
issue_id = get_global_issue_id(
token, organization, repo.name, issue.number
)
link_item_to_project(token, project_id, issue_id)
print("\tLinked issue to project " + project_id)
else:
count_eligible += 1
# Try to detect if the repo already has an open pull request for dependabot
Expand All @@ -99,6 +115,13 @@ def main(): # pragma: no cover
title, body, repo, dependabot_file, commit_message
)
print("\tCreated pull request " + pull.html_url)
if project_id:
pr_id = get_global_pr_id(
token, organization, repo.name, pull.number
)
response = link_item_to_project(token, project_id, pr_id)
if response:
print("\tLinked pull request to project " + project_id)
except github3.exceptions.NotFoundError:
print("\tFailed to create pull request. Check write permissions.")
continue
Expand Down Expand Up @@ -138,7 +161,7 @@ def enable_dependabot_security_updates(owner, repo, access_token):
def get_repos_iterator(organization, repository_list, github_connection):
"""Get the repositories from the organization or list of repositories"""
repos = []
if organization:
if organization and not repository_list:
repos = github_connection.organization(organization).repositories()
else:
# Get the repositories from the repository_list
Expand Down Expand Up @@ -195,5 +218,104 @@ def commit_changes(title, body, repo, dependabot_file, message):
return pull


def get_global_project_id(token, organization, number):
"""Fetches the project ID from GitHub's GraphQL API."""
url = "https://api.github.com/graphql"
headers = {"Authorization": f"Bearer {token}"}
data = {
"query": f'query{{organization(login: "{organization}") {{projectV2(number: {number}){{id}}}}}}'
}

try:
response = requests.post(url, headers=headers, json=data, timeout=20)
response.raise_for_status()
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
return None

try:
return response.json()["data"]["organization"]["projectV2"]["id"]
except KeyError as e:
print(f"Failed to parse response: {e}")
return None


def get_global_issue_id(token, organization, repository, issue_number):
"""Fetches the issue ID from GitHub's GraphQL API"""
url = "https://api.github.com/graphql"
headers = {"Authorization": f"Bearer {token}"}
data = {
"query": f"""
query {{
repository(owner: "{organization}", name: "{repository}") {{
issue(number: {issue_number}) {{
id
}}
}}
}}
"""
}

try:
response = requests.post(url, headers=headers, json=data, timeout=20)
response.raise_for_status()
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
return None

try:
return response.json()["data"]["repository"]["issue"]["id"]
except KeyError as e:
print(f"Failed to parse response: {e}")
return None


def get_global_pr_id(token, organization, repository, pr_number):
"""Fetches the pull request ID from GitHub's GraphQL API"""
url = "https://api.github.com/graphql"
headers = {"Authorization": f"Bearer {token}"}
data = {
"query": f"""
query {{
repository(owner: "{organization}", name: "{repository}") {{
pullRequest(number: {pr_number}) {{
id
}}
}}
}}
"""
}

try:
response = requests.post(url, headers=headers, json=data, timeout=20)
response.raise_for_status()
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
return None

try:
return response.json()["data"]["repository"]["pullRequest"]["id"]
except KeyError as e:
print(f"Failed to parse response: {e}")
return None


def link_item_to_project(token, project_id, item_id):
"""Links an item (issue or pull request) to a project in GitHub."""
url = "https://api.github.com/graphql"
headers = {"Authorization": f"Bearer {token}"}
data = {
"query": f'mutation {{addProjectV2ItemById(input: {{projectId: "{project_id}", contentId: "{item_id}"}}) {{item {{id}}}}}}'
}

try:
response = requests.post(url, headers=headers, json=data, timeout=20)
response.raise_for_status()
return response
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
return None


if __name__ == "__main__":
main() # pragma: no cover
6 changes: 6 additions & 0 deletions test_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class TestEnv(unittest.TestCase):
"BODY": "Dependabot custom body",
"CREATED_AFTER_DATE": "2023-01-01",
"COMMIT_MESSAGE": "Create dependabot configuration",
"PROJECT_ID": "123",
},
)
def test_get_env_vars_with_org(self):
Expand All @@ -36,6 +37,7 @@ def test_get_env_vars_with_org(self):
"2023-01-01",
False,
"Create dependabot configuration",
"123",
)
result = get_env_vars()
self.assertEqual(result, expected_result)
Expand All @@ -52,6 +54,7 @@ def test_get_env_vars_with_org(self):
"CREATED_AFTER_DATE": "2023-01-01",
"DRY_RUN": "true",
"COMMIT_MESSAGE": "Create dependabot configuration",
"PROJECT_ID": "123",
},
clear=True,
)
Expand All @@ -69,6 +72,7 @@ def test_get_env_vars_with_repos(self):
"2023-01-01",
True,
"Create dependabot configuration",
"123",
)
result = get_env_vars()
self.assertEqual(result, expected_result)
Expand Down Expand Up @@ -96,6 +100,7 @@ def test_get_env_vars_optional_values(self):
None,
False,
"Create dependabot.yaml",
None,
)
result = get_env_vars()
self.assertEqual(result, expected_result)
Expand Down Expand Up @@ -143,6 +148,7 @@ def test_get_env_vars_with_repos_no_dry_run(self):
None,
False,
"Create dependabot.yaml",
None,
)
result = get_env_vars()
self.assertEqual(result, expected_result)
Expand Down
Loading

0 comments on commit dcd15d3

Please sign in to comment.