Skip to content

Commit

Permalink
add support for chevron templates in required_file blueprints (#334)
Browse files Browse the repository at this point in the history
  • Loading branch information
netomi authored Nov 19, 2024
1 parent 167858d commit dfd99e6
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 13 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

### Added

- Added support for templates in `required-file` blueprints. ([#322](https://github.com/eclipse-csi/otterdog/issues/322))
- Added support for a `post-add-objects` hook in the default configuration that gets executed after resources have been added. ([#318](https://github.com/eclipse-csi/otterdog/issues/318))
- Added new blueprint `pin_workflow` to used GitHub actions in workflows.
- Added new blueprint `pin_workflow` to pin used GitHub actions in workflows.
- Added new blueprint `required_file` to create files in repositories.
- Added a new operation `list-advisories` to list GitHub Security Advisories for organizations.

### Changed
Expand Down
31 changes: 30 additions & 1 deletion docs/reference/blueprints/required-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,34 @@ The pattern is expected to be in [python regular expression format](https://docs
| content | mandatory | string | the content of the file |
| strict | optional | boolean | if `false` (default), the file will be only checked for existence, otherwise the content will be compared |

### Templating

It is possible to use [Mustache](https://mustache.github.io/) logic-less templates within the content of a blueprint.

To use a context variable, simply enclose its name in curly braces:

```yaml
This is an example to use mustache template for repo: {{repo_name}}
```
In this example, `{{repo_name}}` will be replaced with the actual name of the repository being processed. For more complex examples please refer
to the [mustache documentation](https://mustache.github.io/mustache.5.html).


#### Template Context

The following context is injected during template evaluation when a specific repository is being processed:

| Variable | Type | Description |
|---------------|--------|-------------------------------------------------------------------------------------------------------------------------------|
| project_name | string | the project name of the associated GitHub organization, e.g. `technology.csi` |
| github_id | string | the name of the associated GitHub organization |
| repo_name | string | the name of the repository being processed |
| org | dict | the [organization settings](../organization/settings.md) for the associated GitHub organization |
| repo | dict | the [repository settings](../organization/repository/index.md) for the repository being processed |
| repo_url | string | the url of the repository being processed, e.g. `https://github.com/eclipse-csi/otterdog` |
| blueprint_url | string | the url of the associated blueprint, e.g. `https://github.com/eclipse-csi/.eclipsefdn/blob/main/otterdog/blueprints/test.yml` |

## Example

In this example a workflow file `.github/workflows/dependabot-auto-merge.yml` should be present in a set of repositories matching the configured content.
Expand All @@ -48,6 +76,7 @@ config:
files:
- path: .github/workflows/dependabot-auto-merge.yml
content: |
# This is a templated file from {{blueprint_url}} for {{repo_name}}
name: Dependabot auto-merge
on: pull_request_target
Expand All @@ -58,7 +87,7 @@ config:
permissions:
contents: write
pull-requests: write
uses: adoptium/.github/.github/workflows/dependabot-auto-merge.yml@main
uses: {{github_id}}/.github/.github/workflows/dependabot-auto-merge.yml@main
# ensure that changes to the template are propagated
strict: true
```
12 changes: 9 additions & 3 deletions otterdog/webapp/blueprints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

if TYPE_CHECKING:
from otterdog.models.repository import Repository
from otterdog.webapp.db.models import BlueprintModel
from otterdog.webapp.db.models import BlueprintModel, ConfigurationModel

BLUEPRINT_PATH = "otterdog/blueprints"

Expand Down Expand Up @@ -78,14 +78,20 @@ async def evaluate(self, installation_id: int, github_id: str, recheck: bool = F
continue

self.logger.debug(f"checking blueprint with id '{self.id}' in repo '{github_id}/{repo.name}'")
await self.evaluate_repo(installation_id, github_id, repo.name)
await self.evaluate_repo(installation_id, github_id, repo.name, config_data)
else:
# if a recheck is needed, cleanup status of non-matching repos if they exist
if recheck:
await cleanup_blueprint_status_of_repo(github_id, repo.name, self.id)

@abstractmethod
async def evaluate_repo(self, installation_id: int, github_id: str, repo_name: str) -> None: ...
async def evaluate_repo(
self,
installation_id: int,
github_id: str,
repo_name: str,
config: ConfigurationModel | None = None,
) -> None: ...


def read_blueprint(path: str, content: dict[str, Any]) -> Blueprint:
Expand Down
9 changes: 8 additions & 1 deletion otterdog/webapp/blueprints/pin_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

if TYPE_CHECKING:
from otterdog.models.repository import Repository
from otterdog.webapp.db.models import ConfigurationModel


class PinWorkflowBlueprint(Blueprint):
Expand All @@ -31,7 +32,13 @@ def _matches(self, repo: Repository) -> bool:
else:
return self.repo_selector.matches(repo)

async def evaluate_repo(self, installation_id: int, github_id: str, repo_name: str) -> None:
async def evaluate_repo(
self,
installation_id: int,
github_id: str,
repo_name: str,
config: ConfigurationModel | None = None,
) -> None:
from otterdog.webapp.tasks.blueprints.pin_workflow import PinWorkflowTask

current_app.add_background_task(
Expand Down
14 changes: 13 additions & 1 deletion otterdog/webapp/blueprints/required_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
from quart import current_app

from otterdog.webapp.blueprints import Blueprint, BlueprintType, RepoSelector
from otterdog.webapp.db.service import get_configuration_by_github_id

if TYPE_CHECKING:
from otterdog.models.repository import Repository
from otterdog.webapp.db.models import ConfigurationModel


class RequiredFile(BaseModel):
Expand All @@ -39,14 +41,24 @@ def _matches(self, repo: Repository) -> bool:
else:
return self.repo_selector.matches(repo)

async def evaluate_repo(self, installation_id: int, github_id: str, repo_name: str) -> None:
async def evaluate_repo(
self,
installation_id: int,
github_id: str,
repo_name: str,
config: ConfigurationModel | None = None,
) -> None:
from otterdog.webapp.tasks.blueprints.check_files import CheckFilesTask

config_model = await get_configuration_by_github_id(github_id) if config is None else config
assert config_model is not None

current_app.add_background_task(
CheckFilesTask(
installation_id,
github_id,
repo_name,
self,
config_model,
)
)
35 changes: 30 additions & 5 deletions otterdog/webapp/tasks/blueprints/check_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
# *******************************************************************************

from dataclasses import dataclass
from functools import cached_property

from otterdog.models.github_organization import GitHubOrganization
from otterdog.webapp.blueprints.required_file import RequiredFile, RequiredFileBlueprint
from otterdog.webapp.db.models import ConfigurationModel
from otterdog.webapp.tasks.blueprints import BlueprintTask, CheckResult


Expand All @@ -18,6 +21,11 @@ class CheckFilesTask(BlueprintTask):
org_id: str
repo_name: str
blueprint: RequiredFileBlueprint
config_model: ConfigurationModel

@cached_property
def github_organization_configuration(self) -> GitHubOrganization:
return GitHubOrganization.from_model_data(self.config_model.config)

async def _execute(self) -> CheckResult:
self.logger.info(
Expand All @@ -39,15 +47,17 @@ async def _execute(self) -> CheckResult:

files_needing_update = []
for file in self.blueprint.files:
file_content = self._render_content(file)

try:
content = await rest_api.content.get_content(self.org_id, self.repo_name, file.path)
if file.strict is False or file.content == content:
if file.strict is False or file_content == content:
continue
except RuntimeError:
# file does not exist, so let's create it
pass

files_needing_update.append(file)
files_needing_update.append((file, file_content))

if len(files_needing_update) > 0:
self.logger.debug(
Expand All @@ -57,9 +67,24 @@ async def _execute(self) -> CheckResult:

return result

def _render_content(self, file: RequiredFile) -> str:
import chevron

context = {
"project_name": self.config_model.project_name,
"github_id": self.config_model.github_id,
"repo_name": self.repo_name,
"org": self.github_organization_configuration.settings,
"repo": self.github_organization_configuration.get_repository(self.repo_name),
"repo_url": f"https://github.com/{self.org_id}/{self.repo_name}",
"blueprint_url": self.blueprint.path,
}

return chevron.render(file.content, context)

async def _process_files(
self,
files_needing_update: list[RequiredFile],
files_needing_update: list[tuple[RequiredFile, str]],
result: CheckResult,
) -> None:
result.remediation_needed = True
Expand All @@ -70,12 +95,12 @@ async def _process_files(
await self._create_branch_if_needed(default_branch)

# update content in the branch if necessary
for file in files_needing_update:
for file, content in files_needing_update:
await rest_api.content.update_content(
self.org_id,
self.repo_name,
file.path,
file.content,
content,
self.branch_name,
f"Updating file {file.path}",
)
Expand Down
2 changes: 1 addition & 1 deletion otterdog/webapp/webhook/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@
)
from otterdog.webapp.policies import create_policy_from_model, is_policy_path
from otterdog.webapp.tasks.apply_changes import ApplyChangesTask
from otterdog.webapp.tasks.blueprints.update_blueprint_status import UpdateBlueprintStatusTask
from otterdog.webapp.tasks.check_sync import CheckConfigurationInSyncTask
from otterdog.webapp.tasks.delete_branch import DeleteBranchTask
from otterdog.webapp.tasks.fetch_blueprints import FetchBlueprintsTask
from otterdog.webapp.tasks.fetch_config import FetchConfigTask
from otterdog.webapp.tasks.fetch_policies import FetchPoliciesTask
from otterdog.webapp.tasks.help_comment import HelpCommentTask
from otterdog.webapp.tasks.retrieve_team_membership import RetrieveTeamMembershipTask
from otterdog.webapp.tasks.update_blueprint_status import UpdateBlueprintStatusTask
from otterdog.webapp.tasks.update_pull_request import UpdatePullRequestTask
from otterdog.webapp.tasks.validate_pull_request import ValidatePullRequestTask
from otterdog.webapp.utils import refresh_global_blueprints, refresh_global_policies, refresh_otterdog_config
Expand Down

0 comments on commit dfd99e6

Please sign in to comment.