diff --git a/README.md b/README.md index bca8e30..81f6c8b 100644 --- a/README.md +++ b/README.md @@ -46,40 +46,42 @@ This action can be configured to authenticate with GitHub App Installation or Pe ##### GitHub App Installation -| field | required | default | description | -| ------------------------ | -------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `GH_APP_ID` | True | `""` | GitHub Application ID. See [documentation](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app) for more details. | -| `GH_APP_INSTALLATION_ID` | True | `""` | GitHub Application Installation ID. See [documentation](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app) for more details. | -| `GH_APP_PRIVATE_KEY` | True | `""` | GitHub Application Private Key. See [documentation](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app) for more details. | +| field | required | default | description | +| --- | --- | --- | --- | +| `GH_APP_ID` | True | `""` | GitHub Application ID. See [documentation](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app) for more details. | +| `GH_APP_INSTALLATION_ID` | True | `""` | GitHub Application Installation ID. See [documentation](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app) for more details. | +| `GH_APP_PRIVATE_KEY` | True | `""` | GitHub Application Private Key. See [documentation](https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/about-authentication-with-a-github-app) for more details. | ##### Personal Access Token (PAT) -| 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, `repo:write`, and `workflow` privileges to create a pull request. | +| 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, `repo:write`, and `workflow` privileges to create a pull request. | #### Other Configuration Options -| field | required | default | description | -| -------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `GH_ENTERPRISE_URL` | False | "" | The `GH_ENTERPRISE_URL` is used to connect to an enterprise server instance of GitHub. github.com users should not enter anything here. | -| `ORGANIZATION` | Required to have `ORGANIZATION` or `REPOSITORY` | | The name of the GitHub organization which you want this action to work from. ie. github.com/github would be `github` | -| `REPOSITORY` | Required to have `ORGANIZATION` or `REPOSITORY` | | The name of the repository and organization which you want this action to work from. ie. `github/evergreen` or a comma separated list of multiple repositories `github/evergreen,super-linter/super-linter` | -| `EXEMPT_REPOS` | False | "" | These repositories will be exempt from this action considering them for dependabot enablement. ex: If my org is set to `github` then I might want to exempt a few of the repos but get the rest by setting `EXEMPT_REPOS` to `github/evergreen,github/contributors` | -| `TYPE` | False | pull | Type refers to the type of action you want taken if this workflow determines that dependabot could be enabled. Valid values are `pull` or `issue`. | -| `TITLE` | False | "Enable Dependabot" | The title of the issue or pull request that will be created if dependabot could be enabled. | -| `BODY` | False | **Pull Request:** "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." **Issue:** "Please update the repository to include a Dependabot configuration file. This will ensure our dependencies remain updated and secure.Follow the guidelines in [creating Dependabot configuration files](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file) to set it up properly.Here's an example of the code:" | 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 | -| `UPDATE_EXISTING` | False | False | If set to true, this action will update the existing dependabot configuration file with any package ecosystems that are detected but not configured yet. If set to false, the action will only create a new dependabot configuration file if there is not an existing one. | -| `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. | -| `GROUP_DEPENDENCIES` | False | false | If set to true, dependabot configuration will group dependencies updates based on [dependency type](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#groups) (production or development, where supported) | -| `FILTER_VISIBILITY` | False | "public,private,internal" | Use this flag to filter repositories in scope by their visibility (`public`, `private`, `internal`). By default all repository are targeted. ex: to ignore public repositories set this value to `private,internal`. | -| `BATCH_SIZE` | False | None | Set this to define the maximum amount of eligible repositories for every run. This is useful if you are targeting large organizations and you don't want to flood repositories with pull requests / issues. ex: if you want to target 20 repositories per time, set this to 20. | -| `ENABLE_SECURITY_UPDATES` | False | true | If set to true, Evergreen will enable [Dependabot security updates](https://docs.github.com/en/code-security/dependabot/dependabot-security-updates/configuring-dependabot-security-updates) on target repositories. Note that the GitHub token needs to have the `administration:write` permission on every repository in scope to successfully enable security updates. | -| `EXEMPT_ECOSYSTEMS` | False | "" | A list of [package ecosystems](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem) to exempt from the generated dependabot configuration. To ignore ecosystems set this to one or more of `bundler`,`cargo`, `composer`, `pip`, `docker`, `npm`, `gomod`, `mix`, `nuget`, `github-actions` and `terraform`. ex: if you don't want Dependabot to update Dockerfiles and Github Actions you can set this to `docker,github-actions`. | -| `REPO_SPECIFIC_EXEMPTIONS` | False | "" | A list of repositories that should be exempt from specific package ecosystems similar to EXEMPT_ECOSYSTEMS but those apply to all repositories. ex: `org1/repo1:docker,github-actions;org1/repo2:pip` would set exempt_ecosystems for `org1/repo1` to be `['docker', 'github-actions']`, and for `org1/repo2` it would be `['pip']`, while for every other repository evaluated, it would be set by the env variable `EXEMPT_ECOSYSTEMS`. NOTE: If you want specific exemptions to be added on top of the already specified global exemptions, you need to add the global exemptions to each repo specific exemption. | +| field | required | default | description | +| --- | --- | --- | --- | +| `GH_ENTERPRISE_URL` | False | "" | The `GH_ENTERPRISE_URL` is used to connect to an enterprise server instance of GitHub. github.com users should not enter anything here. | +| `ORGANIZATION` | Required to have `ORGANIZATION` or `REPOSITORY` | | The name of the GitHub organization which you want this action to work from. ie. github.com/github would be `github` | +| `REPOSITORY` | Required to have `ORGANIZATION` or `REPOSITORY` | | The name of the repository and organization which you want this action to work from. ie. `github/evergreen` or a comma separated list of multiple repositories `github/evergreen,super-linter/super-linter` | +| `EXEMPT_REPOS` | False | "" | These repositories will be exempt from this action considering them for dependabot enablement. ex: If my org is set to `github` then I might want to exempt a few of the repos but get the rest by setting `EXEMPT_REPOS` to `github/evergreen,github/contributors` | +| `TYPE` | False | pull | Type refers to the type of action you want taken if this workflow determines that dependabot could be enabled. Valid values are `pull` or `issue`. | +| `TITLE` | False | "Enable Dependabot" | The title of the issue or pull request that will be created if dependabot could be enabled. | +| `BODY` | False | **Pull Request:** "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." **Issue:** "Please update the repository to include a Dependabot configuration file. This will ensure our dependencies remain updated and secure.Follow the guidelines in [creating Dependabot configuration files](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file) to set it up properly.Here's an example of the code:" | 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 | +| `UPDATE_EXISTING` | False | False | If set to true, this action will update the existing dependabot configuration file with any package ecosystems that are detected but not configured yet. If set to false, the action will only create a new dependabot configuration file if there is not an existing one. | +| `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. | +| `GROUP_DEPENDENCIES` | False | false | If set to true, dependabot configuration will group dependencies updates based on [dependency type](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#groups) (production or development, where supported) | +| `FILTER_VISIBILITY` | False | "public,private,internal" | Use this flag to filter repositories in scope by their visibility (`public`, `private`, `internal`). By default all repository are targeted. ex: to ignore public repositories set this value to `private,internal`. | +| `BATCH_SIZE`| False| None| Set this to define the maximum amount of eligible repositories for every run. This is useful if you are targeting large organizations and you don't want to flood repositories with pull requests / issues. ex: if you want to target 20 repositories per time, set this to 20. | +| `ENABLE_SECURITY_UPDATES`| False| true| If set to true, Evergreen will enable [Dependabot security updates](https://docs.github.com/en/code-security/dependabot/dependabot-security-updates/configuring-dependabot-security-updates) on target repositories. Note that the GitHub token needs to have the `administration:write` permission on every repository in scope to successfully enable security updates.| +| `EXEMPT_ECOSYSTEMS` | False | "" | A list of [package ecosystems](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file#package-ecosystem) to exempt from the generated dependabot configuration. To ignore ecosystems set this to one or more of `bundler`,`cargo`, `composer`, `pip`, `docker`, `npm`, `gomod`, `mix`, `nuget`, `github-actions` and `terraform`. ex: if you don't want Dependabot to update Dockerfiles and Github Actions you can set this to `docker,github-actions`. | +| `REPO_SPECIFIC_EXEMPTIONS` | False | "" | A list of repositories that should be exempt from specific package ecosystems similar to EXEMPT_ECOSYSTEMS but those apply to all repositories. ex: `org1/repo1:docker,github-actions;org1/repo2:pip` would set exempt_ecosystems for `org1/repo1` to be `['docker', 'github-actions']`, and for `org1/repo2` it would be `['pip']`, while for every other repository evaluated, it would be set by the env variable `EXEMPT_ECOSYSTEMS`. NOTE: If you want specific exemptions to be added on top of the already specified global exemptions, you need to add the global exemptions to each repo specific exemption. | +| `SCHEDULE` | False | 'weekly' | Schedule interval by which to check for dependency updates via Dependabot. Allowed values are 'daily', 'weekly', or 'monthly' | +| `SCHEDULE_DAY` | False | '' | Scheduled day by which to check for dependency updates via Dependabot. Allowed values are days of the week full names (i.e., 'monday') | ### Example workflows diff --git a/dependabot_file.py b/dependabot_file.py index d0b406f..3d280f0 100644 --- a/dependabot_file.py +++ b/dependabot_file.py @@ -4,7 +4,9 @@ import yaml -def make_dependabot_config(ecosystem, group_dependencies, indent) -> str: +def make_dependabot_config( + ecosystem, group_dependencies, indent, schedule, schedule_day +) -> str: """ Make the dependabot configuration for a specific package ecosystem @@ -12,15 +14,23 @@ def make_dependabot_config(ecosystem, group_dependencies, indent) -> str: 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: " " + schedule: the schedule to run dependabot ex: "daily" + schedule_day: the day of the week to run dependabot ex: "monday" if schedule is "daily" Returns: str: the dependabot configuration for the package ecosystem """ + schedule_day_line = "" + if schedule_day: + schedule_day_line += f""" +{indent}{indent}{indent}day: '{schedule_day}'""" + dependabot_config = f"""{indent}- package-ecosystem: '{ecosystem}' {indent}{indent}directory: '/' {indent}{indent}schedule: -{indent}{indent}{indent}interval: 'weekly' +{indent}{indent}{indent}interval: '{schedule}'{schedule_day_line} """ + if group_dependencies: dependabot_config += f"""{indent}{indent}groups: {indent}{indent}{indent}production-dependencies: @@ -37,6 +47,8 @@ def build_dependabot_file( exempt_ecosystems, repo_specific_exemptions, existing_config, + schedule, + schedule_day, ) -> str | None: """ Build the dependabot.yml file for a repo based on the repo contents @@ -47,6 +59,8 @@ def build_dependabot_file( exempt_ecosystems: the list of ecosystems to ignore repo_specific_exemptions: the list of ecosystems to ignore for a specific repo existing_config: the existing dependabot configuration file or None if it doesn't exist + schedule: the schedule to run dependabot ex: "daily" + schedule_day: the day of the week to run dependabot ex: "monday" if schedule is "daily" Returns: str: the dependabot.yml file for the repo @@ -126,7 +140,7 @@ def build_dependabot_file( if repo.file_contents(file): package_managers_found[manager] = True dependabot_file += make_dependabot_config( - manager, group_dependencies, indent + manager, group_dependencies, indent, schedule, schedule_day ) break except github3.exceptions.NotFoundError: @@ -139,7 +153,7 @@ def build_dependabot_file( if file[0].endswith(".tf"): package_managers_found["terraform"] = True dependabot_file += make_dependabot_config( - "terraform", group_dependencies, indent + "terraform", group_dependencies, indent, schedule, schedule_day ) break except github3.exceptions.NotFoundError: @@ -150,7 +164,11 @@ 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, indent + "github-actions", + group_dependencies, + indent, + schedule, + schedule_day, ) break except github3.exceptions.NotFoundError: diff --git a/env.py b/env.py index 05053a8..4facb77 100644 --- a/env.py +++ b/env.py @@ -90,7 +90,9 @@ def parse_repo_specific_exemptions(repo_specific_exemptions_str: str) -> dict: return exemptions_dict -def get_env_vars(test: bool = False) -> tuple[ +def get_env_vars( + test: bool = False, +) -> tuple[ str | None, list[str], int | None, @@ -113,6 +115,8 @@ def get_env_vars(test: bool = False) -> tuple[ list[str], bool | None, dict, + str, + str, ]: """ Get the environment variables for use in the action. @@ -142,6 +146,8 @@ def get_env_vars(test: bool = False) -> tuple[ 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 repo_specific_exemptions (dict): A dictionary of per repository ecosystem exemptions + schedule (str): The schedule to run the action on + schedule_day (str): The day of the week to run the action on if schedule is daily """ if not test: @@ -288,6 +294,36 @@ def get_env_vars(test: bool = False) -> tuple[ repo_specific_exemptions_str ) + schedule = os.getenv("SCHEDULE", "").strip().lower() + if schedule and schedule not in ["daily", "weekly", "monthly"]: + raise ValueError( + "SCHEDULE environment variable not 'daily', 'weekly', or 'monthly'" + ) + if not schedule: + schedule = "weekly" + schedule_day = os.getenv("SCHEDULE_DAY", "").strip().lower() + if schedule != "weekly" and schedule_day: + raise ValueError( + "SCHEDULE_DAY environment variable not needed when SCHEDULE is not 'weekly'" + ) + if ( + schedule == "weekly" + and schedule_day + and schedule_day + not in [ + "monday", + "tuesday", + "wednesday", + "thursday", + "friday", + "saturday", + "sunday", + ] + ): + raise ValueError( + "SCHEDULE_DAY environment variable not 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', or 'sunday'" + ) + return ( organization, repositories_list, @@ -311,4 +347,6 @@ def get_env_vars(test: bool = False) -> tuple[ exempt_ecosystems_list, update_existing, repo_specific_exemptions, + schedule, + schedule_day, ) diff --git a/evergreen.py b/evergreen.py index f5b5e6a..ba64f49 100644 --- a/evergreen.py +++ b/evergreen.py @@ -37,6 +37,8 @@ def main(): # pragma: no cover exempt_ecosystems, update_existing, repo_specific_exemptions, + schedule, + schedule_day, ) = env.get_env_vars() # Auth to GitHub.com or GHE @@ -110,6 +112,8 @@ def main(): # pragma: no cover exempt_ecosystems, repo_specific_exemptions, existing_config, + schedule, + schedule_day, ) if dependabot_file is None: diff --git a/test_dependabot_file.py b/test_dependabot_file.py index 9c4e427..fc078fe 100644 --- a/test_dependabot_file.py +++ b/test_dependabot_file.py @@ -20,9 +20,30 @@ 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, [], {}, None) + result = build_dependabot_file(repo, False, [], {}, None, "", "") self.assertEqual(result, None) + def test_build_dependabot_file_with_schedule_day(self): + """Test that the dependabot.yml file is built correctly with weekly schedule day""" + repo = MagicMock() + filename_list = ["Gemfile", "Gemfile.lock"] + + for filename in filename_list: + repo.file_contents.side_effect = lambda f, filename=filename: f == filename + expected_result = """--- +version: 2 +updates: + - package-ecosystem: 'bundler' + directory: '/' + schedule: + interval: 'weekly' + day: 'tuesday' +""" + result = build_dependabot_file( + repo, False, [], {}, None, "weekly", "tuesday" + ) + self.assertEqual(result, expected_result) + def test_build_dependabot_file_with_bundler(self): """Test that the dependabot.yml file is built correctly with bundler""" repo = MagicMock() @@ -38,7 +59,7 @@ def test_build_dependabot_file_with_bundler(self): schedule: interval: 'weekly' """ - result = build_dependabot_file(repo, False, [], {}, None) + result = build_dependabot_file(repo, False, [], {}, None, "weekly", "") self.assertEqual(result, expected_result) def test_build_dependabot_file_with_existing_config_bundler_no_update(self): @@ -51,7 +72,9 @@ def test_build_dependabot_file_with_existing_config_bundler_no_update(self): existing_config = MagicMock() existing_config.decoded = b'---\nversion: 2\nupdates:\n - package-ecosystem: "bundler"\n\ directory: "/"\n schedule:\n interval: "weekly"\n commit-message:\n prefix: "chore(deps)"\n' - result = build_dependabot_file(repo, False, [], {}, existing_config) + result = build_dependabot_file( + repo, False, [], {}, existing_config, "weekly", "" + ) self.assertEqual(result, expected_result) def test_build_dependabot_file_with_2_space_indent_existing_config_bundler_with_update( @@ -80,7 +103,9 @@ def test_build_dependabot_file_with_2_space_indent_existing_config_bundler_with_ 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) + result = build_dependabot_file( + repo, False, [], {}, existing_config, "weekly", "" + ) self.assertEqual(result, expected_result) def test_build_dependabot_file_with_weird_space_indent_existing_config_bundler_with_update( @@ -95,7 +120,9 @@ def test_build_dependabot_file_with_weird_space_indent_existing_config_bundler_w 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) + result = build_dependabot_file( + repo, False, [], {}, existing_config, "weekly", "" + ) self.assertEqual(result, None) def test_build_dependabot_file_with_npm(self): @@ -113,7 +140,7 @@ def test_build_dependabot_file_with_npm(self): schedule: interval: 'weekly' """ - result = build_dependabot_file(repo, False, [], {}, None) + result = build_dependabot_file(repo, False, [], {}, None, "weekly", "") self.assertEqual(result, expected_result) def test_build_dependabot_file_with_pip(self): @@ -137,7 +164,7 @@ def test_build_dependabot_file_with_pip(self): schedule: interval: 'weekly' """ - result = build_dependabot_file(repo, False, [], {}, None) + result = build_dependabot_file(repo, False, [], {}, None, "weekly", "") self.assertEqual(result, expected_result) def test_build_dependabot_file_with_cargo(self): @@ -158,7 +185,7 @@ def test_build_dependabot_file_with_cargo(self): schedule: interval: 'weekly' """ - result = build_dependabot_file(repo, False, [], {}, None) + result = build_dependabot_file(repo, False, [], {}, None, "weekly", "") self.assertEqual(result, expected_result) def test_build_dependabot_file_with_gomod(self): @@ -174,7 +201,7 @@ def test_build_dependabot_file_with_gomod(self): schedule: interval: 'weekly' """ - result = build_dependabot_file(repo, False, [], {}, None) + result = build_dependabot_file(repo, False, [], {}, None, "weekly", "") self.assertEqual(result, expected_result) def test_build_dependabot_file_with_composer(self): @@ -195,7 +222,7 @@ def test_build_dependabot_file_with_composer(self): schedule: interval: 'weekly' """ - result = build_dependabot_file(repo, False, [], {}, None) + result = build_dependabot_file(repo, False, [], {}, None, "weekly", "") self.assertEqual(result, expected_result) def test_build_dependabot_file_with_hex(self): @@ -216,7 +243,7 @@ def test_build_dependabot_file_with_hex(self): schedule: interval: 'weekly' """ - result = build_dependabot_file(repo, False, [], {}, None) + result = build_dependabot_file(repo, False, [], {}, None, "weekly", "") self.assertEqual(result, expected_result) def test_build_dependabot_file_with_nuget(self): @@ -232,7 +259,7 @@ def test_build_dependabot_file_with_nuget(self): schedule: interval: 'weekly' """ - result = build_dependabot_file(repo, False, [], {}, None) + result = build_dependabot_file(repo, False, [], {}, None, "weekly", "") self.assertEqual(result, expected_result) def test_build_dependabot_file_with_docker(self): @@ -248,7 +275,7 @@ def test_build_dependabot_file_with_docker(self): schedule: interval: 'weekly' """ - result = build_dependabot_file(repo, False, [], {}, None) + result = build_dependabot_file(repo, False, [], {}, None, "weekly", "") self.assertEqual(result, expected_result) def test_build_dependabot_file_with_terraform_with_files(self): @@ -269,7 +296,7 @@ def test_build_dependabot_file_with_terraform_with_files(self): schedule: interval: 'weekly' """ - result = build_dependabot_file(repo, False, [], {}, None) + result = build_dependabot_file(repo, False, [], {}, None, "weekly", "") self.assertEqual(result, expected_result) def test_build_dependabot_file_with_terraform_without_files(self): @@ -281,7 +308,7 @@ def test_build_dependabot_file_with_terraform_without_files(self): # Test absence of Terraform files repo.directory_contents.side_effect = lambda path: [] if path == "/" else [] - result = build_dependabot_file(repo, False, [], {}, None) + result = build_dependabot_file(repo, False, [], {}, None, "weekly", "") self.assertIsNone(result) # Test empty repository @@ -290,7 +317,7 @@ def test_build_dependabot_file_with_terraform_without_files(self): repo.directory_contents.side_effect = github3.exceptions.NotFoundError( resp=response ) - result = build_dependabot_file(repo, False, [], {}, None) + result = build_dependabot_file(repo, False, [], {}, None, "weekly", "") self.assertIsNone(result) def test_build_dependabot_file_with_github_actions(self): @@ -311,7 +338,7 @@ def test_build_dependabot_file_with_github_actions(self): schedule: interval: 'weekly' """ - result = build_dependabot_file(repo, False, [], None, None) + result = build_dependabot_file(repo, False, [], None, None, "weekly", "") self.assertEqual(result, expected_result) def test_build_dependabot_file_with_github_actions_without_files(self): @@ -324,7 +351,7 @@ def test_build_dependabot_file_with_github_actions_without_files(self): resp=response ) - result = build_dependabot_file(repo, False, [], None, None) + result = build_dependabot_file(repo, False, [], None, None, "weekly", "") self.assertEqual(result, None) def test_build_dependabot_file_with_groups(self): @@ -345,7 +372,7 @@ def test_build_dependabot_file_with_groups(self): development-dependencies: dependency-type: 'development' """ - result = build_dependabot_file(repo, True, [], {}, None) + result = build_dependabot_file(repo, True, [], {}, None, "weekly", "") self.assertEqual(result, expected_result) def test_build_dependabot_file_with_exempt_ecosystems(self): @@ -353,7 +380,7 @@ def test_build_dependabot_file_with_exempt_ecosystems(self): repo = MagicMock() repo.file_contents.side_effect = lambda filename: filename == "Dockerfile" - result = build_dependabot_file(repo, False, ["docker"], {}, None) + result = build_dependabot_file(repo, False, ["docker"], {}, None, "weekly", "") self.assertEqual(result, None) def test_build_dependabot_file_with_repo_specific_exempt_ecosystems(self): @@ -362,7 +389,9 @@ def test_build_dependabot_file_with_repo_specific_exempt_ecosystems(self): repo.full_name = "test/test" repo.file_contents.side_effect = lambda filename: filename == "Dockerfile" - result = build_dependabot_file(repo, False, [], {"test/test": ["docker"]}, None) + result = build_dependabot_file( + repo, False, [], {"test/test": ["docker"]}, None, "weekly", "" + ) self.assertEqual(result, None) def test_add_existing_ecosystem_to_exempt_list(self): @@ -401,7 +430,13 @@ def test_build_dependabot_file_for_multiple_repos_with_few_existing_config(self) directory: "/"\n schedule:\n interval: "weekly"\n commit-message:\n prefix: "chore(deps)"\n' exempt_ecosystems = [] result = build_dependabot_file( - existing_config_repo, False, exempt_ecosystems, {}, existing_config + existing_config_repo, + False, + exempt_ecosystems, + {}, + existing_config, + "weekly", + "", ) self.assertEqual(result, None) @@ -420,7 +455,7 @@ def test_build_dependabot_file_for_multiple_repos_with_few_existing_config(self) interval: 'weekly' """ result = build_dependabot_file( - no_existing_config_repo, False, exempt_ecosystems, {}, None + no_existing_config_repo, False, exempt_ecosystems, {}, None, "weekly", "" ) self.assertEqual(result, expected_result) @@ -440,7 +475,9 @@ def test_check_multiple_repos_with_no_dependabot_config(self): interval: 'weekly' """ exempt_ecosystems = [] - result = build_dependabot_file(mock_repo_1, False, exempt_ecosystems, {}, None) + result = build_dependabot_file( + mock_repo_1, False, exempt_ecosystems, {}, None, "weekly", "" + ) self.assertEqual(result, expected_result) no_existing_config_repo = MagicMock() @@ -458,7 +495,7 @@ def test_check_multiple_repos_with_no_dependabot_config(self): interval: 'weekly' """ result = build_dependabot_file( - no_existing_config_repo, False, exempt_ecosystems, {}, None + no_existing_config_repo, False, exempt_ecosystems, {}, None, "weekly", "" ) self.assertEqual(result, expected_result) diff --git a/test_env.py b/test_env.py index 5a918b1..a238b16 100644 --- a/test_env.py +++ b/test_env.py @@ -29,6 +29,8 @@ def setUp(self): "TYPE", "UPDATE_EXISTING", "REPO_SPECIFIC_EXEMPTIONS", + "SCHEDULE", + "SCHEDULE_DAY", ] for key in env_keys: if key in os.environ: @@ -75,6 +77,8 @@ def test_get_env_vars_with_org(self): [], # exempt_ecosystems False, # update_existing {}, # repo_specific_exemptions + "weekly", # schedule + "", # schedule_day ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -124,6 +128,8 @@ def test_get_env_vars_with_org_and_repo_specific_exemptions(self): "repo1": ["gomod"], "repo2": ["docker", "gomod"], }, # repo_specific_exemptions + "weekly", # schedule + "", # schedule_day ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -230,6 +236,8 @@ def test_get_env_vars_with_repos(self): "org1/repo1": ["docker"], "org2/repo2": ["gomod"], }, # repo_specific_exemptions + "weekly", # schedule + "", # schedule_day ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -269,6 +277,8 @@ def test_get_env_vars_optional_values(self): [], # exempt_ecosystems False, # update_existing {}, # repo_specific_exemptions + "weekly", # schedule + "", # schedule_day ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -309,6 +319,8 @@ def test_get_env_vars_with_update_existing(self): [], # exempt_ecosystems True, # update_existing {}, # repo_specific_exemptions + "weekly", # schedule + "", # schedule_day ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -363,6 +375,8 @@ def test_get_env_vars_auth_with_github_app_installation(self): [], # exempt_ecosystems False, # update_existing {}, # repo_specific_exemptions + "weekly", # schedule + "", # schedule_day ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -425,6 +439,8 @@ def test_get_env_vars_with_repos_no_dry_run(self): [], # exempt_ecosystems False, # update_existing {}, # repo_specific_exemptions + "weekly", # schedule + "", # schedule_day ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -465,6 +481,8 @@ def test_get_env_vars_with_repos_disabled_security_updates(self): [], # exempt_ecosystems False, # update_existing {}, # repo_specific_exemptions + "weekly", # schedule + "", # schedule_day ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -506,6 +524,8 @@ def test_get_env_vars_with_repos_filter_visibility_multiple_values(self): [], # exempt_ecosystems False, # update_existing {}, # repo_specific_exemptions + "weekly", # schedule + "", # schedule_day ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -547,6 +567,8 @@ def test_get_env_vars_with_repos_filter_visibility_single_value(self): [], # exempt_ecosystems False, # update_existing {}, # repo_specific_exemptions + "weekly", # schedule + "", # schedule_day ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -618,6 +640,8 @@ def test_get_env_vars_with_repos_filter_visibility_no_duplicates(self): [], # exempt_ecosystems False, # update_existing {}, # repo_specific_exemptions + "weekly", # schedule + "", # schedule_day ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -660,6 +684,8 @@ def test_get_env_vars_with_repos_exempt_ecosystems(self): ["gomod", "docker"], # exempt_ecosystems False, # update_existing {}, # repo_specific_exemptions + "weekly", # schedule + "", # schedule_day ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -701,6 +727,8 @@ def test_get_env_vars_with_no_batch_size(self): [], # exempt_ecosystems False, # update_existing {}, # repo_specific_exemptions + "weekly", # schedule + "", # schedule_day ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -743,6 +771,8 @@ def test_get_env_vars_with_batch_size(self): [], # exempt_ecosystems False, # update_existing {}, # repo_specific_exemptions + "weekly", # schedule + "", # schedule_day ) result = get_env_vars(True) self.assertEqual(result, expected_result) @@ -789,7 +819,7 @@ def test_get_env_vars_with_invalid_batch_size_str(self): clear=True, ) def test_get_env_vars_with_badly_formatted_created_after_date(self): - """Test that""" + """Test that badly formatted CREATED_AFTER_DATE throws exception""" with self.assertRaises(ValueError) as context_manager: get_env_vars(True) the_exception = context_manager.exception @@ -798,6 +828,45 @@ def test_get_env_vars_with_badly_formatted_created_after_date(self): "CREATED_AFTER_DATE '20200101' environment variable not in YYYY-MM-DD", ) + @patch.dict( + os.environ, + { + "ORGANIZATION": "my_organization", + "GH_TOKEN": "my_token", + "SCHEDULE": "annually", + }, + clear=True, + ) + def test_get_env_vars_with_bad_schedule_choice(self): + """Test that bad schedule choice throws exception""" + with self.assertRaises(ValueError) as context_manager: + get_env_vars(True) + the_exception = context_manager.exception + self.assertEqual( + str(the_exception), + "SCHEDULE environment variable not 'daily', 'weekly', or 'monthly'", + ) + + @patch.dict( + os.environ, + { + "ORGANIZATION": "my_organization", + "GH_TOKEN": "my_token", + "SCHEDULE": "weekly", + "SCHEDULE_DAY": "thorsday", + }, + clear=True, + ) + def test_get_env_vars_with_bad_schedule_day_choice(self): + """Test that bad schedule day choice throws exception""" + with self.assertRaises(ValueError) as context_manager: + get_env_vars(True) + the_exception = context_manager.exception + self.assertEqual( + str(the_exception), + "SCHEDULE_DAY environment variable not 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', or 'sunday'", + ) + if __name__ == "__main__": unittest.main()