Skip to content

Commit

Permalink
Merge pull request #150 from github/maintain-existing-filenames
Browse files Browse the repository at this point in the history
fix: maintain dependabot config filename for existing configs
  • Loading branch information
zkoppert committed May 30, 2024
2 parents 7618880 + a981676 commit b350eab
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 40 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ This action can be configured to authenticate with GitHub App Installation or Pe

| field | required | default | description |
|-------------------------------|----------|---------|-------------|
| `GH_TOKEN` | True | `""` | The GitHub Token used to scan the repository. Must have read access to all repository you are interested in scanning. |
| `GH_TOKEN` | True | `""` | The GitHub Token used to scan the repository. Must have read access to all repository you are interested in scanning, `repo:write`, and `workflow` privileges to create a pull request. |

#### Other Configuration Options

Expand Down
40 changes: 27 additions & 13 deletions dependabot_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,29 @@
import yaml


def make_dependabot_config(ecosystem, group_dependencies) -> str:
def make_dependabot_config(ecosystem, group_dependencies, indent) -> str:
"""
Make the dependabot configuration for a specific package ecosystem
Args:
ecosystem: the package ecosystem to make the dependabot configuration for
group_dependencies: whether to group dependencies in the dependabot.yml file
indent: the number of spaces to indent the dependabot configuration ex: " "
Returns:
str: the dependabot configuration for the package ecosystem
"""
dependabot_config = f""" - package-ecosystem: '{ecosystem}'
directory: '/'
schedule:
interval: 'weekly'
dependabot_config = f"""{indent}- package-ecosystem: '{ecosystem}'
{indent}{indent}directory: '/'
{indent}{indent}schedule:
{indent}{indent}{indent}interval: 'weekly'
"""
if group_dependencies:
dependabot_config += """ groups:
production-dependencies:
dependency-type: 'production'
development-dependencies:
dependency-type: 'development'
dependabot_config += f"""{indent}{indent}groups:
{indent}{indent}{indent}production-dependencies:
{indent}{indent}{indent}{indent}dependency-type: 'production'
{indent}{indent}{indent}development-dependencies:
{indent}{indent}{indent}{indent}dependency-type: 'development'
"""
return dependabot_config

Expand Down Expand Up @@ -58,10 +59,23 @@ def build_dependabot_file(
"terraform": False,
"github-actions": False,
}
DEFAULT_INDENT = 2 # pylint: disable=invalid-name

if existing_config:
dependabot_file = existing_config.decoded.decode("utf-8")
ecosystem_line = next(
line
for line in dependabot_file.splitlines()
if "- package-ecosystem:" in line
)
indent = " " * (len(ecosystem_line) - len(ecosystem_line.lstrip()))
if len(indent) < DEFAULT_INDENT:
print(
f"Invalid dependabot.yml file. No indentation found. Skipping {repo.full_name}"
)
return None
else:
indent = " " * DEFAULT_INDENT
dependabot_file = """---
version: 2
updates:
Expand Down Expand Up @@ -99,7 +113,7 @@ def build_dependabot_file(
if repo.file_contents(file):
package_managers_found[manager] = True
dependabot_file += make_dependabot_config(
manager, group_dependencies
manager, group_dependencies, indent
)
break
except github3.exceptions.NotFoundError:
Expand All @@ -112,7 +126,7 @@ def build_dependabot_file(
if file[0].endswith(".tf"):
package_managers_found["terraform"] = True
dependabot_file += make_dependabot_config(
"terraform", group_dependencies
"terraform", group_dependencies, indent
)
break
except github3.exceptions.NotFoundError:
Expand All @@ -123,7 +137,7 @@ def build_dependabot_file(
if file[0].endswith(".yml") or file[0].endswith(".yaml"):
package_managers_found["github-actions"] = True
dependabot_file += make_dependabot_config(
"github-actions", group_dependencies
"github-actions", group_dependencies, indent
)
break
except github3.exceptions.NotFoundError:
Expand Down
2 changes: 1 addition & 1 deletion env.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ def get_env_vars(test: bool = False) -> tuple[
if len(commit_message) > 65536:
raise ValueError("COMMIT_MESSAGE environment variable is too long")
else:
commit_message = "Create dependabot.yaml"
commit_message = "Create/Update dependabot.yaml"

created_after_date = os.getenv("CREATED_AFTER_DATE", "")
is_match = re.match(r"\d{4}-\d{2}-\d{2}", created_after_date)
Expand Down
43 changes: 34 additions & 9 deletions evergreen.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,11 @@ def main(): # pragma: no cover
continue
existing_config = None
filename_list = [".github/dependabot.yml", ".github/dependabot.yaml"]
dependabot_filename_to_use = None
for filename in filename_list:
existing_config = check_existing_config(repo, filename, update_existing)
if existing_config:
dependabot_filename_to_use = filename
break

if created_after_date and is_repo_created_date_before(
Expand Down Expand Up @@ -130,7 +132,9 @@ def main(): # pragma: no cover
body_issue = (
body
+ "\n\n```yaml\n"
+ "# .github/dependabot.yml\n"
+ "# "
+ dependabot_filename_to_use
+ "\n"
+ dependabot_file
+ "\n```"
)
Expand All @@ -151,7 +155,13 @@ def main(): # pragma: no cover
count_eligible += 1
try:
pull = commit_changes(
title, body, repo, dependabot_file, commit_message
title,
body,
repo,
dependabot_file,
commit_message,
dependabot_filename_to_use,
existing_config,
)
print("\tCreated pull request " + pull.html_url)
if project_id:
Expand Down Expand Up @@ -273,20 +283,35 @@ def check_pending_issues_for_duplicates(title, repo) -> bool:
return skip


def commit_changes(title, body, repo, dependabot_file, message):
def commit_changes(
title,
body,
repo,
dependabot_file,
message,
dependabot_filename=".github/dependabot.yml",
existing_config=None,
):
"""Commit the changes to the repo and open a pull reques and return the pull request object"""
default_branch = repo.default_branch
# Get latest commit sha from default branch
default_branch_commit = repo.ref("heads/" + default_branch).object.sha
front_matter = "refs/heads/"
branch_name = "dependabot-" + str(uuid.uuid4())
repo.create_ref(front_matter + branch_name, default_branch_commit)
repo.create_file(
path=".github/dependabot.yaml",
message=message,
content=dependabot_file.encode(), # Convert to bytes object
branch=branch_name,
)
if existing_config:
repo.file_contents(dependabot_filename).update(
message=message,
content=dependabot_file.encode(), # Convert to bytes object
branch=branch_name,
)
else:
repo.create_file(
path=dependabot_filename,
message=message,
content=dependabot_file.encode(), # Convert to bytes object
branch=branch_name,
)

pull = repo.create_pull(
title=title, body=body, head=branch_name, base=repo.default_branch
Expand Down
22 changes: 20 additions & 2 deletions test_dependabot_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,15 @@ def test_build_dependabot_file_with_existing_config_bundler_no_update(self):
result = build_dependabot_file(repo, False, [], existing_config)
self.assertEqual(result, expected_result)

def test_build_dependabot_file_with_existing_config_bundler_with_update(self):
def test_build_dependabot_file_with_2_space_indent_existing_config_bundler_with_update(
self,
):
"""Test that the dependabot.yml file is built correctly with bundler"""
repo = MagicMock()
repo.file_contents.side_effect = lambda f, filename="Gemfile": f == filename

# expected_result maintains existing ecosystem with custom configuration and adds new ecosystem
# expected_result maintains existing ecosystem with custom configuration
# and adds new ecosystem
expected_result = """---
version: 2
updates:
Expand All @@ -80,6 +83,21 @@ def test_build_dependabot_file_with_existing_config_bundler_with_update(self):
result = build_dependabot_file(repo, False, [], existing_config)
self.assertEqual(result, expected_result)

def test_build_dependabot_file_with_weird_space_indent_existing_config_bundler_with_update(
self,
):
"""Test that the dependabot.yml file is built correctly with bundler"""
repo = MagicMock()
repo.file_contents.side_effect = lambda f, filename="Gemfile": f == filename

# expected_result maintains existing ecosystem with custom configuration
# and adds new ecosystem
existing_config = MagicMock()
existing_config.decoded = b'---\nversion: 2\nupdates:\n- package-ecosystem: "pip"\n directory: "/"\n\
schedule:\n interval: "weekly"\n commit-message:\n prefix: "chore(deps)"\n'
result = build_dependabot_file(repo, False, [], existing_config)
self.assertEqual(result, None)

def test_build_dependabot_file_with_npm(self):
"""Test that the dependabot.yml file is built correctly with npm"""
repo = MagicMock()
Expand Down
22 changes: 11 additions & 11 deletions test_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def test_get_env_vars_optional_values(self):
we can keep our dependencies up to date and secure.",
"",
False,
"Create dependabot.yaml",
"Create/Update dependabot.yaml",
None,
False,
["internal", "private", "public"],
Expand Down Expand Up @@ -187,7 +187,7 @@ def test_get_env_vars_with_update_existing(self):
we can keep our dependencies up to date and secure.",
"",
False,
"Create dependabot.yaml",
"Create/Update dependabot.yaml",
None,
False,
["internal", "private", "public"],
Expand Down Expand Up @@ -240,7 +240,7 @@ def test_get_env_vars_auth_with_github_app_installation(self):
"secure.",
"",
False,
"Create dependabot.yaml",
"Create/Update dependabot.yaml",
None,
False,
["internal", "private", "public"],
Expand Down Expand Up @@ -301,7 +301,7 @@ def test_get_env_vars_with_repos_no_dry_run(self):
we can keep our dependencies up to date and secure.",
"",
False,
"Create dependabot.yaml",
"Create/Update dependabot.yaml",
None,
False,
["internal", "private", "public"],
Expand Down Expand Up @@ -340,7 +340,7 @@ def test_get_env_vars_with_repos_disabled_security_updates(self):
we can keep our dependencies up to date and secure.",
"",
False,
"Create dependabot.yaml",
"Create/Update dependabot.yaml",
None,
False,
["internal", "private", "public"],
Expand Down Expand Up @@ -380,7 +380,7 @@ def test_get_env_vars_with_repos_filter_visibility_multiple_values(self):
we can keep our dependencies up to date and secure.",
"",
False,
"Create dependabot.yaml",
"Create/Update dependabot.yaml",
None,
False,
["internal", "private"],
Expand Down Expand Up @@ -420,7 +420,7 @@ def test_get_env_vars_with_repos_filter_visibility_single_value(self):
we can keep our dependencies up to date and secure.",
"",
False,
"Create dependabot.yaml",
"Create/Update dependabot.yaml",
None,
False,
["public"],
Expand Down Expand Up @@ -490,7 +490,7 @@ def test_get_env_vars_with_repos_filter_visibility_no_duplicates(self):
we can keep our dependencies up to date and secure.",
"",
False,
"Create dependabot.yaml",
"Create/Update dependabot.yaml",
None,
False,
["private", "public"],
Expand Down Expand Up @@ -531,7 +531,7 @@ def test_get_env_vars_with_repos_exempt_ecosystems(self):
we can keep our dependencies up to date and secure.",
"",
False,
"Create dependabot.yaml",
"Create/Update dependabot.yaml",
None,
False,
["private", "public"],
Expand Down Expand Up @@ -571,7 +571,7 @@ def test_get_env_vars_with_no_batch_size(self):
we can keep our dependencies up to date and secure.",
"",
False,
"Create dependabot.yaml",
"Create/Update dependabot.yaml",
None,
False,
["private", "public"],
Expand Down Expand Up @@ -612,7 +612,7 @@ def test_get_env_vars_with_batch_size(self):
we can keep our dependencies up to date and secure.",
"",
False,
"Create dependabot.yaml",
"Create/Update dependabot.yaml",
None,
False,
["private", "public"],
Expand Down
14 changes: 11 additions & 3 deletions test_evergreen.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,20 +205,28 @@ def test_commit_changes(self, mock_uuid):
mock_repo.create_ref.return_value = True
mock_repo.create_file.return_value = True
mock_repo.create_pull.return_value = "MockPullRequest"
dependabot_file_name = ".github/dependabot.yml"

title = "Test Title"
body = "Test Body"
dependabot_file = 'dependencies:\n - package_manager: "python"\n directory: "/"\n update_schedule: "live"'
branch_name = "dependabot-12345678-1234-5678-1234-567812345678"
commit_message = "Create dependabot.yaml"
result = commit_changes(title, body, mock_repo, dependabot_file, commit_message)
commit_message = "Create " + dependabot_file_name
result = commit_changes(
title,
body,
mock_repo,
dependabot_file,
commit_message,
dependabot_file_name,
)

# Assert that the methods were called with the correct arguments
mock_repo.create_ref.assert_called_once_with(
f"refs/heads/{branch_name}", "abc123"
)
mock_repo.create_file.assert_called_once_with(
path=".github/dependabot.yaml",
path=dependabot_file_name,
message=commit_message,
content=dependabot_file.encode(),
branch=branch_name,
Expand Down

0 comments on commit b350eab

Please sign in to comment.