Skip to content

Commit

Permalink
feat: Update existing configs with missing package-ecosystems
Browse files Browse the repository at this point in the history
Signed-off-by: Zack Koppert <[email protected]>
  • Loading branch information
zkoppert committed May 25, 2024
1 parent 1f06d0e commit 5853222
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 27 deletions.
26 changes: 24 additions & 2 deletions dependabot_file.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""This module contains the function to build the dependabot.yml file for a repo"""

import github3
import yaml


def make_dependabot_config(ecosystem, group_dependencies) -> str:
Expand Down Expand Up @@ -29,14 +30,17 @@ def make_dependabot_config(ecosystem, group_dependencies) -> str:
return dependabot_config


def build_dependabot_file(repo, group_dependencies, exempt_ecosystems) -> str | None:
def build_dependabot_file(
repo, group_dependencies, exempt_ecosystems, existing_config
) -> str | None:
"""
Build the dependabot.yml file for a repo based on the repo contents
Args:
repo: the repository to build the dependabot.yml file for
group_dependencies: whether to group dependencies in the dependabot.yml file
exempt_ecosystems: the list of ecosystems to ignore
existing_config: the existing dependabot configuration file or None if it doesn't exist
Returns:
str: the dependabot.yml file for the repo
Expand All @@ -51,10 +55,16 @@ def build_dependabot_file(repo, group_dependencies, exempt_ecosystems) -> str |
hex_found = False
nuget_found = False
docker_found = False
dependabot_file = """---
if existing_config:
dependabot_file = existing_config.decoded.decode("utf-8")
else:
dependabot_file = """---
version: 2
updates:
"""

add_existing_ecosystem_to_exempt_list(exempt_ecosystems, existing_config)

try:
if (
repo.file_contents("Gemfile")
Expand Down Expand Up @@ -305,3 +315,15 @@ def build_dependabot_file(repo, group_dependencies, exempt_ecosystems) -> str |
if compatible_package_manager_found:
return dependabot_file
return None


def add_existing_ecosystem_to_exempt_list(exempt_ecosystems, existing_config):
"""
Add the existing package ecosystems found in the dependabot.yml
to the exempt list so we don't get duplicate entries and maintain configuration settings
"""
if existing_config:
existing_config_obj = yaml.safe_load(existing_config.decoded)
if existing_config_obj:
for entry in existing_config_obj.get("updates", []):
exempt_ecosystems.append(entry["package-ecosystem"])
7 changes: 7 additions & 0 deletions env.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ def get_env_vars(test: bool = False) -> tuple[
int | None,
bool | None,
list[str],
bool | None,
]:
"""
Get the environment variables for use in the action.
Expand Down Expand Up @@ -91,7 +92,9 @@ def get_env_vars(test: bool = False) -> tuple[
batch_size (int): The max number of repositories in scope
enable_security_updates (bool): Whether to enable security updates in target repositories
exempt_ecosystems_list (list[str]): A list of package ecosystems to exempt from the action
update_existing (bool): Whether to update existing dependabot configuration files
"""

if not test:
# Load from .env file if it exists and not testing
dotenv_path = join(dirname(__file__), ".env")
Expand Down Expand Up @@ -228,6 +231,9 @@ def get_env_vars(test: bool = False) -> tuple[
project_id = os.getenv("PROJECT_ID")
if project_id and not project_id.isnumeric():
raise ValueError("PROJECT_ID environment variable is not numeric")

update_existing = get_bool_env_var("UPDATE_EXISTING")

return (
organization,
repositories_list,
Expand All @@ -249,4 +255,5 @@ def get_env_vars(test: bool = False) -> tuple[
batch_size,
enable_security_updates_bool,
exempt_ecosystems_list,
update_existing,
)
37 changes: 24 additions & 13 deletions evergreen.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def main(): # pragma: no cover
batch_size,
enable_security_updates,
exempt_ecosystems,
update_existing,
) = env.get_env_vars()

# Auth to GitHub.com or GHE
Expand All @@ -47,7 +48,7 @@ def main(): # pragma: no cover
gh_app_id, gh_app_private_key, gh_app_installation_id
)

# If Project ID is set lookup the global project ID
# 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:
Expand All @@ -61,6 +62,7 @@ def main(): # pragma: no cover

# Iterate through the repositories and open an issue/PR if dependabot is not enabled
count_eligible = 0
existing_config = None
for repo in repos:
# if batch_size is defined, ensure we break if we exceed the number of eligible repos
if batch_size and count_eligible >= batch_size:
Expand All @@ -78,19 +80,27 @@ def main(): # pragma: no cover
print("Skipping " + repo.full_name + " (visibility-filtered)")
continue
try:
if repo.file_contents(".github/dependabot.yml").size > 0:
print(
"Skipping " + repo.full_name + " (dependabot file already exists)"
)
continue
existing_config = repo.file_contents(".github/dependabot.yml")
if existing_config.size > 0:
if not update_existing:
print(
"Skipping "
+ repo.full_name
+ " (dependabot file already exists)"
)
continue
except github3.exceptions.NotFoundError:
pass
try:
if repo.file_contents(".github/dependabot.yaml").size > 0:
print(
"Skipping " + repo.full_name + " (dependabot file already exists)"
)
continue
existing_config = repo.file_contents(".github/dependabot.yaml")
if existing_config.size > 0:
if not update_existing:
print(
"Skipping "
+ repo.full_name
+ " (dependabot file already exists)"
)
continue
except github3.exceptions.NotFoundError:
pass

Expand All @@ -103,10 +113,11 @@ def main(): # pragma: no cover
print("Checking " + repo.full_name + " for compatible package managers")
# Try to detect package managers and build a dependabot file
dependabot_file = build_dependabot_file(
repo, group_dependencies, exempt_ecosystems
repo, group_dependencies, exempt_ecosystems, existing_config
)

if dependabot_file is None:
print("\tNo compatible package manager found")
print("\tNo (new) compatible package manager found")
continue

# If dry_run is set, just print the dependabot file
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
github3.py==4.0.1
requests==2.31.0
python-dotenv==1.0.1
PyYAML==6.0.1
24 changes: 12 additions & 12 deletions test_dependabot_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ def test_not_found_error(self):
response.status_code = 404
repo.file_contents.side_effect = github3.exceptions.NotFoundError(resp=response)

result = build_dependabot_file(repo, False, [])
result = build_dependabot_file(repo, False, [], None)
self.assertEqual(result, None)

def test_build_dependabot_file_with_bundler(self):
Expand All @@ -37,7 +37,7 @@ def test_build_dependabot_file_with_bundler(self):
schedule:
interval: 'weekly'
"""
result = build_dependabot_file(repo, False, [])
result = build_dependabot_file(repo, False, [], None)
self.assertEqual(result, expected_result)

def test_build_dependabot_file_with_npm(self):
Expand All @@ -55,7 +55,7 @@ def test_build_dependabot_file_with_npm(self):
schedule:
interval: 'weekly'
"""
result = build_dependabot_file(repo, False, [])
result = build_dependabot_file(repo, False, [], None)
self.assertEqual(result, expected_result)

def test_build_dependabot_file_with_pip(self):
Expand All @@ -79,7 +79,7 @@ def test_build_dependabot_file_with_pip(self):
schedule:
interval: 'weekly'
"""
result = build_dependabot_file(repo, False, [])
result = build_dependabot_file(repo, False, [], None)
self.assertEqual(result, expected_result)

def test_build_dependabot_file_with_cargo(self):
Expand All @@ -100,7 +100,7 @@ def test_build_dependabot_file_with_cargo(self):
schedule:
interval: 'weekly'
"""
result = build_dependabot_file(repo, False, [])
result = build_dependabot_file(repo, False, [], None)
self.assertEqual(result, expected_result)

def test_build_dependabot_file_with_gomod(self):
Expand All @@ -116,7 +116,7 @@ def test_build_dependabot_file_with_gomod(self):
schedule:
interval: 'weekly'
"""
result = build_dependabot_file(repo, False, [])
result = build_dependabot_file(repo, False, [], None)
self.assertEqual(result, expected_result)

def test_build_dependabot_file_with_composer(self):
Expand All @@ -137,7 +137,7 @@ def test_build_dependabot_file_with_composer(self):
schedule:
interval: 'weekly'
"""
result = build_dependabot_file(repo, False, [])
result = build_dependabot_file(repo, False, [], None)
self.assertEqual(result, expected_result)

def test_build_dependabot_file_with_hex(self):
Expand All @@ -158,7 +158,7 @@ def test_build_dependabot_file_with_hex(self):
schedule:
interval: 'weekly'
"""
result = build_dependabot_file(repo, False, [])
result = build_dependabot_file(repo, False, [], None)
self.assertEqual(result, expected_result)

def test_build_dependabot_file_with_nuget(self):
Expand All @@ -174,7 +174,7 @@ def test_build_dependabot_file_with_nuget(self):
schedule:
interval: 'weekly'
"""
result = build_dependabot_file(repo, False, [])
result = build_dependabot_file(repo, False, [], None)
self.assertEqual(result, expected_result)

def test_build_dependabot_file_with_docker(self):
Expand All @@ -190,7 +190,7 @@ def test_build_dependabot_file_with_docker(self):
schedule:
interval: 'weekly'
"""
result = build_dependabot_file(repo, False, [])
result = build_dependabot_file(repo, False, [], None)
self.assertEqual(result, expected_result)

def test_build_dependabot_file_with_groups(self):
Expand All @@ -211,15 +211,15 @@ def test_build_dependabot_file_with_groups(self):
development-dependencies:
dependency-type: 'development'
"""
result = build_dependabot_file(repo, True, [])
result = build_dependabot_file(repo, True, [], None)
self.assertEqual(result, expected_result)

def test_build_dependabot_file_with_exempt_ecosystems(self):
"""Test that the dependabot.yml file is built correctly with exempted ecosystems"""
repo = MagicMock()
repo.file_contents.side_effect = lambda filename: filename == "Dockerfile"

result = build_dependabot_file(repo, False, ["docker"])
result = build_dependabot_file(repo, False, ["docker"], None)
self.assertEqual(result, None)


Expand Down
Loading

0 comments on commit 5853222

Please sign in to comment.