Organization-wide policies¶
This page is intended for administrators who maintain the default configuration and the otterdog setup for several organizations. For the configuration of a single organization, see the Configuration Guides.
Otterdog offers several mechanisms to establish common rules across organizations. They differ in whether an organization can deviate from them:
| Mechanism | Effect | Deviation by an organization |
|---|---|---|
| Default configuration | provides the inherited value of every property | override the property in the configuration |
| Validation scripts | reject configurations that do not comply | not possible, unless the script allows it |
| Blueprints | open pull requests that add missing configuration or files | close the pull request, which dismisses the blueprint |
| Policies | enforce a behavior, e.g. cancel workflow jobs on certain runners | only with a policy of the same type on organization level |
The default configuration as baseline¶
The default configuration, referenced by base_template in otterdog.json, provides the inherited value of every
property and defines which settings and resources otterdog manages, see
Inherited and explicit values.
- Keep the default configuration in its own repository, protect it like code and publish released versions, e.g.
as tags, which
base_templatereferences. - Before releasing a new version, run
planfor the affected organizations: every changed default applies to all resources that do not override it. - Adding a setting or a resource type to the default configuration makes otterdog manage it for every
organization, e.g. defining
team_permissionsfor repositories makes the team permissions of all repositories authoritative.
Enforce settings with validation scripts¶
The directory of the default configuration, i.e. the root of the repository referenced by base_template, can
contain Python scripts that otterdog executes during validation:
| Script | Executed for | Available variables |
|---|---|---|
validate-org-settings.py |
the organization settings | self: the organization settings, context: the validation context |
validate-team.py |
every team | self: the team, context: the validation context |
context.property_equals(self, '<key>', <value>) reports an error if a property does not have the required value,
context.add_failure(...) reports any other failure. For example, the following validate-org-settings.py requires
that members get no access to repositories by default and that two-factor authentication is required:
context.property_equals(self, "default_repository_permission", "none")
context.property_equals(self, "two_factor_requirement", True)
Similarly, scripts can run when apply creates resources:
| Script | Executed | Available variables |
|---|---|---|
pre-add-object-hook.py |
before each resource is created | model_object: the resource, org_config, self: the operation |
post-add-objects-hook.py |
after all resources have been created | patches: the applied additions, org_config, self: the operation |
Warning
These scripts are executed as Python code by otterdog, with the permissions and credentials of the process that
runs it. Anybody who can change the default configuration can therefore run code in this context. Restrict and
review changes to the repository of the default configuration accordingly, and reference a tag or a commit SHA
in base_template: a branch is pulled on every run, so every push to it takes effect immediately, and otterdog
logs a warning for it. See also the threat model.
Teams managed by another tool¶
When teams are created and maintained by another tool, e.g. a synchronization with an identity provider, exclude them from otterdog with regular expressions that are matched against the team slugs:
Excluded teams are not imported, and defining them in the configuration of an organization is reported as error,
unless they are defined by the default configuration. Their permissions on repositories are ignored as well, unless
a repository lists the team in team_permissions, see
Teams and access.
The repository that holds the configuration of each organization is set with config_repo in the same github
section, .otterdog by default. It is used by the fetch-config and push-config operations.
Exceptions¶
Common rules need exceptions from time to time. Make them visible and reviewable:
- document the reason of an override in the configuration of the organization with a comment
- require a review by the team that owns the rule for such changes, e.g. with a
CODEOWNERSfile in the configuration repository - if a validation script enforces the rule, keep the list of exceptions in the script, so that it is reviewed centrally
orgs.newOrg('my-project', 'my-org') {
settings+: {
// Exception to the organization-wide default: the project publishes its documentation
// from private repositories, which requires read access for all members.
default_repository_permission: "read",
},
}
Cost-related changes¶
Changes that may incur costs, e.g. a larger GitHub Actions cache, can require a review by a designated team, see Cost policy.
Environments managed by another tool¶
Repository environments that are created and maintained by another tool can be excluded from otterdog with regular expressions that are matched against the environment names:
Each pattern is matched independently from the start of the name, use ^name$ to match a single environment exactly.
Excluded environments, including their secrets and variables, are ignored when reading the live state of a repository, so they are not reported as differences and otterdog never deletes them.
Managing an excluded environment¶
To manage an excluded environment, declare it in the configuration of the repository:
orgs.newOrg('my-project', 'my-org') {
_repositories+:: [
orgs.newRepo('my-repo') {
environments+: [
orgs.newEnvironment('external-prod') {
deployment_branch_policy: "selected",
branch_policies+: [
"main",
],
},
],
},
],
}
The same applies to environments created automatically by GitHub, e.g. copilot, which GitHub creates for the
Copilot coding agent. To manage its settings, declare it explicitly:
orgs.newOrg('my-project', 'my-org') {
_repositories+:: [
orgs.newRepo('my-repo') {
environments+: [
// created automatically by GitHub, only declared to manage its settings
orgs.newEnvironment('copilot') {
deployment_branch_policy: "all",
},
],
},
],
}
A declared environment is managed like any other, even if its name matches a pattern: it is compared with the live state, changed if it differs and created if it does not exist. If the declaration is removed again, the environment is ignored again and remains on GitHub unmanaged.
Note
Otterdog never deletes an environment that matches an exclusion pattern, not even after its declaration has been removed from the configuration. If such an environment is no longer needed, delete it by other means, e.g. in the repository settings on GitHub or via the GitHub API.
Note
The import operation has no configuration to compare against and therefore always skips excluded environments.
When importing into an existing configuration, add the declarations of excluded environments you want to manage
again afterwards.