Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Same repository that is part of multiple suborgs #277

Open
Chris-V opened this issue Sep 6, 2022 · 16 comments · May be fixed by #664
Open

Same repository that is part of multiple suborgs #277

Chris-V opened this issue Sep 6, 2022 · 16 comments · May be fixed by #664
Labels
enhancement New feature or request

Comments

@Chris-V
Copy link
Contributor

Chris-V commented Sep 6, 2022

Prerequisites:

  • Is the functionality available in the GitHub UI? If so, please provide a link to information about the feature.
    n/a

New Feature

What I am looking for exactly is a way to add required_status_checks from multiple base files to a repository. We extensively use monorepos and reusable workflows. Having a way to not repeat ourselves too much with the repo settings would facilitate maintenance quite a bit.

So I tried adding a single repo to multiple suborgs but the required_status_checks are not merged and only the checks from one suborg are synced. Every other checks are removed from the branch protections. Using suborgs for that purpose may not be appropriate though. What if a suborg requires 1 approval and the other one asks for 2 approvals? How to decide which one should be applied first?

I think a way to extend other config files and apply the extensions in the order they are specified could solve the issue. Many linter configs work this way. ie:

.github/repos/example.yml

"_extend":
  - webapp-react-frontend
  - webapp-bff

branches:   # Note that these are already merged to "root" and "one of the suborgs" settings
  - name: default
    protection:
      required_status_checks:
        contexts:
          - "Something very specific to this repo"

.github/templates/webapp-react-frontend.yml

branches:
  - name: default
    protection:
      required_status_checks:
        contexts:
          - "React / ESLint"

.github/templates/webapp-bff.yml

branches:
  - name: default
    protection:
      required_status_checks:
        contexts:
          - "BFF / Compile"
@Chris-V Chris-V added the enhancement New feature or request label Sep 6, 2022
@svg153
Copy link
Contributor

svg153 commented Sep 6, 2022

Hi @Chris-V.

I experienced something similar in the #247, but @markjm fixes (I haven't tested it yet) in #262

Probably it's not working for sub-orgs?
Are you using the new version 2.0.0?

:)

@decyjphr
Copy link
Collaborator

decyjphr commented Sep 6, 2022

I think what you are looking for is to have multiple sub-org configs be applied to a single repo and merge the configs of multiple sub-orgs to that repo config.

@Chris-V
Copy link
Contributor Author

Chris-V commented Sep 6, 2022

Hi @Chris-V.

I experienced something similar in the #247, but @markjm fixes (I haven't tested it yet) in #262

Probably it's not working for sub-orgs? Are you using the new version 2.0.0?

:)

Yes, I'm testing against 2.0.0. Everything is merged properly except all of the suborgs. We are essentially limited to a single suborg per repo.

I think what you are looking for is to have multiple sub-org configs be applied to a single repo and merge the configs of multiple sub-orgs to that repo config.

In a way, but my understanding is that it may not be clear upfront in which order suborg configs will be applied. Hence my suggestion of an explicit "extends" keyword. Although for our usage (merge required_status_checks), a merge of the suborgs would be enough.

@martinm82
Copy link
Contributor

What do you think about such an organization of the settings: #370?

@esteluk
Copy link

esteluk commented Jan 2, 2023

I think there's definitely value, but it's somewhat orthogonal to the main use case I'd see from this particular issue. eg. for at my org we'd have both discipline-specific and business-suborganisation specific configuration that it would be nice to be able to intelligently merge together.

@anderssonjohan
Copy link
Contributor

anderssonjohan commented Feb 2, 2023

A little heads up on merging required_status_checks. I could not apply my checks because the GitHub API rejected the payload defined as:

contexts: []
checks: [ { ... }, { ... } ]

To my surprise it turned out that I was having a suborg file with suborgteams. The suborgteams feature turns out to be like an alias for all the repositories defined on the given team. With multiple uses of suborgteams for the same team it's a bit unclear what config is used. It seems to apply the last config (in alphabetical order).

In my case I had contexts: [] in that config with suborgteams and the schema for the endpoint in the GitHub API accepts either contexts or checks (contexts are deprecated btw). So these two can't be merged and used simultaneously, ie., you have to use contexts (with name and appid) consistently in all safe-settings config files.

An option to #262 could be to allow inclusion of partial configurations. Something similar to this: https://stackoverflow.com/a/9577670/2447296

By using that a user could define behaviors in separate files such as in this example:

  • Org has many teams
  • Each team has suborg baseline
  • Each team works with frontend and backend apps
  • Each tech stack (frontend, backend, etc.) of the same kind has the same checks baseline

Example using include for python project checks:

Org-level checks baseline for all Python projects:
.github/behaviors/python_project_checks.yml

- context: Block Autosquash Commits
  app_id: 15368
- context: Commitlint
  app_id: 15368
- context: Black, isort, Ruff, and Pytest
  app_id: 15368
- context: SonarCloud Code Analysis
  app_id: 12526

Team-level baseline:
.github/suborgs/team1.yml

teams:
  - name: team1-developers
    permission: admin
  - name: all-developers
    permission: push

branches:
  - name: default
    protection:
      required_pull_request_reviews:
        required_approving_review_count: 1
        dismiss_stale_reviews: true
      required_linear_history: true
      allow_deletions: false
      block_creations: true
      required_status_checks:
        # Required. Require branches to be up to date before merging.
        strict: true
        checks: []
      enforce_admins: false

.github/repos/team1-python-service123.yml

branches:
  - name: default
    protection:
      required_pull_request_reviews: {}
      required_status_checks:
        checks:
          - !include behaviors/python_project_checks.yml

@grossag
Copy link

grossag commented Jul 15, 2024

Is it safe to say that the same repository being part of multiple suborgs is undefined behavior right now? I don't see any documented ordering by which safe-settings will determine which suborg to apply to a repository that matches multiple suborgs.

If it really is undefined behavior, it would be nice if the PR check bot would fail a status check in this case.

@decyjphr
Copy link
Collaborator

decyjphr commented Jul 25, 2024

@grossag I would say, yes, it is an undefined behavior since, when you look at

async getSubOrgConfigs () {
, the suborg config associated with a repo depends on the order the suborg files are processed and the last suborg config that applies to a repo is the one that is left assigned as the suborg config for the repo.

I think adding a PR check fail would be an easy solution since we don't have to figure out the rules for precedence, etc.

I'll look into that.

@grossag
Copy link

grossag commented Jul 26, 2024

@grossag I would say, yes, it is an undefined behavior since, when you look at

async getSubOrgConfigs () {

, the suborg config associated with a repo depends on the order the suborg files are processed and the last suborg config that applies to a repo is the one that is left assigned as the suborg config for the repo.
I think adding a PR check fail would be an easy solution since we don't have to figure out the rules for precedence, etc.

I'll look into that.

Thanks @decyjphr . It makes sense that it would be easy to check whether a repository matches multiple suborgs via name pattern. But I am not sure about the more advanced cases.

My one point of confusion is this: as far as I can tell, suborg matching is implemented the following way:

  1. Suborgs are matched directly by name here -
    const subOrgConfig = this.getSubOrgConfig(repo.repo)
  2. Suborgs are matched by custom property or team using plugins by calling into this plugins and invoking the sync() function -
    return new Plugin(this.nop, this.github, repo, config, this.log, this.errors).sync()
    calls which calls into plugins such as https://github.com/github/safe-settings/blob/f6c8d436457f91308a84e79754b32ec79aa68314/lib/plugins/teams.js and https://github.com/github/safe-settings/blob/f6c8d436457f91308a84e79754b32ec79aa68314/lib/plugins/custom_properties.js

If that is correct, I wasn't able to figure out a way to catch the more advanced suborg matching cases where, for example:

  1. One suborg matches via custom property
  2. Another suborg matches via team
  3. Another suborg matches via name wildcard pattern
    and one repository matched multiple of those patterns. Do you think that is fixable?

@thebollywoodguy
Copy link

@decyjphr your expert comments will certainly help in here!!

@decyjphr
Copy link
Collaborator

decyjphr commented Aug 1, 2024

  • Suborgs are matched directly by name here -

      [safe-settings/lib/settings.js](https://github.com/github/safe-settings/blob/f6c8d436457f91308a84e79754b32ec79aa68314/lib/settings.js#L297)
    
    
         Line 297
      in
      [f6c8d43](/github/safe-settings/commit/f6c8d436457f91308a84e79754b32ec79aa68314)
    
    
    
    
    
        
          
           const subOrgConfig = this.getSubOrgConfig(repo.repo)
    

@grossag

if (data.suborgrepos) {

If you look at the lines shown above, we build a dictionary of suborg configs with the repo name as the key. The repo names are obtained using the logic associated with suborgrepos or suborgteams or suborgproperties. So it is possible that a repo could show up in any of these matching scenarios and it would be associated with the matched suborg config.

It could match one or many of these criteria in a suborg config file and then that config will be associated to that repo. And, sometimes, subsequently it could be matched by the criteria in a different suborg config and it would be associated to that suborg config. I feel this is somewhat similar to how a CSS setting works. (Maybe we can use a page from that book and use a !important rule that will override any previous rules. )

For now, I can add logic in the code to check it is in a dryrun mode and if a repo name already exists in the dictionary to throw a PR check error.

I hope this answers your question.

@grossag
Copy link

grossag commented Aug 1, 2024

  • Suborgs are matched directly by name here -
      [safe-settings/lib/settings.js](https://github.com/github/safe-settings/blob/f6c8d436457f91308a84e79754b32ec79aa68314/lib/settings.js#L297)
    
    
         Line 297
      in
      [f6c8d43](/github/safe-settings/commit/f6c8d436457f91308a84e79754b32ec79aa68314)
    
    
    
    
    
        
          
           const subOrgConfig = this.getSubOrgConfig(repo.repo)
    

@grossag

if (data.suborgrepos) {

If you look at the lines shown above, we build a dictionary of suborg configs with the repo name as the key. The repo names are obtained using the logic associated with suborgrepos or suborgteams or suborgproperties. So it is possible that a repo could show up in any of these matching scenarios and it would be associated with the matched suborg config.

It could match one or many of these criteria in a suborg config file and then that config will be associated to that repo. And, sometimes, subsequently it could be matched by the criteria in a different suborg config and it would be associated to that suborg config. I feel this is somewhat similar to how a CSS setting works. (Maybe we can use a page from that book and use a !important rule that will override any previous rules. )

For now, I can add logic in the code to check it is in a dryrun mode and if a repo name already exists in the dictionary to throw a PR check error.

I hope this answers your question.

Thank you! I hadn't realized that async getSubOrgConfigs() implementation is where the main matching is implemented and has information about all 3 matching formats (data.suborgrepos, data.suborgteams, and data.suborgproperties), which would allow settings.js to definitively determine whether a repository matches two suborgs, regardless of the matching pattern.

That solution sounds great, thank you! That would help us be able to deploy safe-settings.

@decyjphr decyjphr linked a pull request Aug 8, 2024 that will close this issue
3 tasks
@scottcarter87
Copy link

I am testing the release candidate that was provided to me with the fix for this problem and it's definitely working much better than before. I tested this in an org that has 9 total repos (including the repo which contains the safe-settings configurations). When I raise a PR that deliberatly introduces a suborb collision as I would expect the validation check now fails.

That being said the comment that the probot app leaves on the PR seems a little odd. There are 10 line items in the comment despite there only being 9 repos in the org. Furthermore all 10 line items say the same things for every column. I would actually expect 8 line items since the repo containing the safe-settings configs is exempt from safe-settings by default. The following screenshot shows what I am seeing with the 2.1.12-rc.1 build.

Screenshot 2024-09-04 at 9 53 05 PM

@decyjphr
Copy link
Collaborator

@scottcarter87 I fixed this in 2.1.14-rc.1. Let me know if that works for you.

@scottcarter87
Copy link

@scottcarter87 I fixed this in 2.1.14-rc.1. Let me know if that works for you.

Thank you! Downloading the build now and giving it a try.

@scottcarter87
Copy link

@decyjphr Looks much better, thanks!

Screenshot 2024-09-17 at 9 42 55 AM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants