Skip to content

Commit

Permalink
Merge pull request #14 from github/automated-security-fixes
Browse files Browse the repository at this point in the history
feat: Add automated security updates
  • Loading branch information
zkoppert committed Dec 16, 2023
2 parents bcc697b + cc74b54 commit 7583e32
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[![CodeQL](https://github.com/github/evergreen/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/github/evergreen/actions/workflows/github-code-scanning/codeql) [![Lint Code Base](https://github.com/github/evergreen/actions/workflows/super-linter.yaml/badge.svg)](https://github.com/github/evergreen/actions/workflows/super-linter.yaml) [![Python package](https://github.com/github/evergreen/actions/workflows/python-ci.yml/badge.svg)](https://github.com/github/evergreen/actions/workflows/python-ci.yml)

This is a GitHub Action that given an organization or specified repositories, opens an issue/PR dependabot is not enabled but could be.
This is a GitHub Action that given an organization or specified repositories, opens an issue/PR dependabot is not enabled but could be. It also enables [automated security updates](https://docs.github.com/en/code-security/dependabot/dependabot-security-updates/configuring-dependabot-security-updates#managing-dependabot-security-updates-for-your-repositories) for the repository.

This action was developed by the GitHub OSPO for our own use and developed in a way that we could open source it that it might be useful to you as well! If you want to know more about how we use it, reach out in an issue in this repository.

Expand Down
35 changes: 35 additions & 0 deletions evergreen.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import auth
import env
import github3
import requests
from dependabot_file import build_dependabot_file


Expand Down Expand Up @@ -74,6 +75,10 @@ def main():
print("\tConfiguration:\n" + dependabot_file)
continue

# Get dependabot security updates enabled if possible
if not is_dependabot_security_updates_enabled(repo.owner, repo.name, token):
enable_dependabot_security_updates(repo.owner, repo.name, token)

if follow_up_type == "issue":
count_eligible += 1
issue = repo.create_issue(title, body)
Expand All @@ -87,9 +92,39 @@ def main():
if not skip:
pull = commit_changes(title, body, repo, dependabot_file)
print("\tCreated pull request " + pull.html_url)

print("Done. " + str(count_eligible) + " repositories were eligible.")


def is_dependabot_security_updates_enabled(owner, repo, access_token):
"""Check if Dependabot security updates are enabled at the /repos/:owner/:repo/automated-security-fixes endpoint using the requests library"""
url = f"https://api.github.com/repos/{owner}/{repo}/automated-security-fixes"
headers = {
"Authorization": f"Bearer {access_token}",
"Accept": "application/vnd.github.london-preview+json",
}

response = requests.get(url, headers=headers, timeout=20)
if response.status_code == 200:
return response.json()["enabled"]
return False


def enable_dependabot_security_updates(owner, repo, access_token):
"""Enable Dependabot security updates at the /repos/:owner/:repo/automated-security-fixes endpoint using the requests library"""
url = f"https://api.github.com/repos/{owner}/{repo}/automated-security-fixes"
headers = {
"Authorization": f"Bearer {access_token}",
"Accept": "application/vnd.github.london-preview+json",
}

response = requests.put(url, headers=headers, timeout=20)
if response.status_code == 204:
print("\tDependabot security updates enabled successfully.")
else:
print("\tFailed to enable Dependabot security updates.")


def get_repos_iterator(organization, repository_list, github_connection):
"""Get the repositories from the organization or list of repositories"""
repos = []
Expand Down
180 changes: 180 additions & 0 deletions test_evergreen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
""" Test the evergreen.py module. """
import unittest
from unittest.mock import patch

from evergreen import (
enable_dependabot_security_updates,
is_dependabot_security_updates_enabled,
)


class TestDependabotSecurityUpdates(unittest.TestCase):
"""Test the Dependabot security updates functions in evergreen.py"""

def test_is_dependabot_security_updates_enabled(self):
"""
Test the is_dependabot_security_updates_enabled function.
This test checks if the is_dependabot_security_updates_enabled function correctly
detects if Dependabot security updates are enabled.
It mocks the requests.get method to simulate different scenarios.
"""
owner = "my_owner"
repo = "my_repo"
access_token = "my_access_token"

expected_url = (
f"https://api.github.com/repos/{owner}/{repo}/automated-security-fixes"
)
expected_headers = {
"Authorization": f"Bearer {access_token}",
"Accept": "application/vnd.github.london-preview+json",
}
expected_response = {"enabled": True}

with patch("requests.get") as mock_get:
mock_get.return_value.status_code = 200
mock_get.return_value.json.return_value = expected_response

result = is_dependabot_security_updates_enabled(owner, repo, access_token)

mock_get.assert_called_once_with(
expected_url, headers=expected_headers, timeout=20
)
self.assertTrue(result)

def test_is_dependabot_security_updates_disabled(self):
"""
Test the is_dependabot_security_updates_enabled function when security updates are disabled.
This test checks if the is_dependabot_security_updates_enabled function correctly
detects if Dependabot security updates are disabled.
It mocks the requests.get method to simulate different scenarios.
"""
owner = "my_owner"
repo = "my_repo"
access_token = "my_access_token"

expected_url = (
f"https://api.github.com/repos/{owner}/{repo}/automated-security-fixes"
)
expected_headers = {
"Authorization": f"Bearer {access_token}",
"Accept": "application/vnd.github.london-preview+json",
}

with patch("requests.get") as mock_get:
mock_get.return_value.status_code = 200
mock_get.return_value.json.return_value = {"enabled": False}

result = is_dependabot_security_updates_enabled(owner, repo, access_token)

mock_get.assert_called_once_with(
expected_url, headers=expected_headers, timeout=20
)
self.assertFalse(result)

def test_is_dependabot_security_updates_not_found(self):
"""
Test the is_dependabot_security_updates_enabled function when the endpoint is not found.
This test checks if the is_dependabot_security_updates_enabled function correctly
handles the case when the endpoint is not found.
It mocks the requests.get method to simulate different scenarios.
"""
owner = "my_owner"
repo = "my_repo"
access_token = "my_access_token"

expected_url = (
f"https://api.github.com/repos/{owner}/{repo}/automated-security-fixes"
)
expected_headers = {
"Authorization": f"Bearer {access_token}",
"Accept": "application/vnd.github.london-preview+json",
}

with patch("requests.get") as mock_get:
mock_get.return_value.status_code = 404

result = is_dependabot_security_updates_enabled(owner, repo, access_token)

mock_get.assert_called_once_with(
expected_url, headers=expected_headers, timeout=20
)
self.assertFalse(result)

def test_enable_dependabot_security_updates(self):
"""
Test the enable_dependabot_security_updates function.
This test checks if the enable_dependabot_security_updates function successfully enables
Dependabot security updates.
It mocks the requests.put method to simulate different scenarios.
"""
owner = "my_owner"
repo = "my_repo"
access_token = "my_access_token"

expected_url = (
f"https://api.github.com/repos/{owner}/{repo}/automated-security-fixes"
)
expected_headers = {
"Authorization": f"Bearer {access_token}",
"Accept": "application/vnd.github.london-preview+json",
}

with patch("requests.put") as mock_put:
mock_put.return_value.status_code = 204

with patch("builtins.print") as mock_print:
enable_dependabot_security_updates(owner, repo, access_token)

mock_put.assert_called_once_with(
expected_url, headers=expected_headers, timeout=20
)
mock_print.assert_called_once_with(
"\tDependabot security updates enabled successfully."
)

def test_enable_dependabot_security_updates_failed(self):
"""
Test the enable_dependabot_security_updates function when enabling fails.
This test checks if the enable_dependabot_security_updates function handles the case
when enabling Dependabot security updates fails.
It mocks the requests.put method to simulate different scenarios.
"""
owner = "my_owner"
repo = "my_repo"
access_token = "my_access_token"

expected_url = (
f"https://api.github.com/repos/{owner}/{repo}/automated-security-fixes"
)
expected_headers = {
"Authorization": f"Bearer {access_token}",
"Accept": "application/vnd.github.london-preview+json",
}

with patch("requests.put") as mock_put:
mock_put.return_value.status_code = 500

with patch("builtins.print") as mock_print:
enable_dependabot_security_updates(owner, repo, access_token)

mock_put.assert_called_once_with(
expected_url, headers=expected_headers, timeout=20
)
mock_print.assert_called_once_with(
"\tFailed to enable Dependabot security updates."
)


if __name__ == "__main__":
unittest.main()

0 comments on commit 7583e32

Please sign in to comment.