Support non-Enterprise GitHub organizations
Summary
git-hubby currently requires GitHub Enterprise Cloud for full organization management. Several reconciled features depend on Enterprise-only APIs and will fail with 403 or 404 errors when applied to organizations on Free, Pro, or Team plans. This issue tracks the work needed to make the operator compatible with all GitHub plan tiers.
Background
The following features are Enterprise Cloud-only and currently always reconciled:
| Feature |
API Endpoint |
Plan Requirement |
| Organization rulesets |
PUT /orgs/{org}/rulesets |
Enterprise Cloud |
| Code security configurations |
POST /orgs/{org}/code-security/configurations |
Enterprise Cloud (GHAS) |
| IDP group sync for teams |
PATCH /teams/{team_id}/team-sync/group-mappings |
Enterprise Cloud |
All other features (org settings, repositories, repo rulesets, webhooks, teams, custom properties, actions settings) work on all plans.
Proposed Approach
Add a plan field to OrganizationSpec that declares the GitHub plan tier. Reconcilers conditionally skip Enterprise-only tasks based on this field. This follows the operator's philosophy of explicit, declarative configuration.
Alternative considered
Graceful degradation (detect 403/404 and skip) was considered but rejected because:
403 is ambiguous — could be a permissions issue vs. a plan limitation
- Wastes API calls attempting unsupported operations
- Fails silently, making debugging harder
Required Changes
1. CRD: Add plan field to OrganizationSpec
File: api/v1alpha1/organization_types.go
Add a Plan field to OrganizationSpec:
// Plan indicates the GitHub plan tier for this organization.
// Determines which Enterprise-only features are reconciled.
// +kubebuilder:validation:Enum=enterprise;team;free
// +kubebuilder:default=enterprise
// +optional
Plan string `json:"plan,omitempty"`
Default is enterprise to maintain backward compatibility.
2. Organization reconciler: Conditional reconciliation groups
File: internal/reconciler/orgrec/reconciler.go
Make RequiredReconciliations() conditional based on plan:
- All plans: org settings, custom properties, actions settings
- Enterprise only: organization rulesets, code security configurations
The RequiredReconciliations() method should build the group dynamically instead of returning a static slice.
3. Team reconciler: Conditional IDP sync
File: internal/reconciler/teamrec/reconciler.go
Skip IDP group sync reconciliation when the team's organizations are not on the Enterprise plan. Since teams can span multiple organizations, this requires checking the plan of each referenced organization.
4. Conditions: Dynamic required conditions
File: internal/conditions/conditions.go
The SetReadyCondition() aggregation currently hardcodes all condition types as required. When Enterprise-only features are skipped, their conditions won't be set, so the aggregation must only consider conditions for enabled features.
Options:
- Accept the list of required condition types as a parameter
- Have reconcilers declare which conditions are relevant (already implicit in
RequiredReconciliations())
5. Validation webhook: Reject invalid plan/feature combinations
File: internal/webhook/v1alpha1/organization_webhook.go
Add validation that rejects Enterprise-only spec fields when plan != "enterprise":
- Reject non-empty
spec.rulesets on free or team plans
- Reject non-empty
spec.codeSecurityConfigurations on free or team plans
This gives users early feedback rather than runtime failures.
6. Status: Surface skipped features
Consider adding a condition or event indicating which features were skipped due to plan tier, so users understand why certain features are not being reconciled.
7. Regenerate and test
- Run
make manifests generate to regenerate CRDs and deepcopy methods
- Update Helm chart CRDs
- Add unit tests for conditional reconciliation groups
- Add unit tests for webhook validation of plan/feature combinations
- Add tests for the
Ready condition aggregation with subsets of conditions
- Update documentation (README, CONTRIBUTING.md, copilot-instructions)
Acceptance Criteria
Notes
- Repository rulesets work on Team+ plans (and Free for public repos). A future enhancement could add more granular plan-based feature gating at the repository level, but this is out of scope for this issue.
- The
plan field is purely informational to the operator — it does not query GitHub to verify the actual plan. Users are responsible for setting it correctly.
This issue was drafted with the help of GitHub Copilot.
Support non-Enterprise GitHub organizations
Summary
git-hubby currently requires GitHub Enterprise Cloud for full organization management. Several reconciled features depend on Enterprise-only APIs and will fail with
403or404errors when applied to organizations on Free, Pro, or Team plans. This issue tracks the work needed to make the operator compatible with all GitHub plan tiers.Background
The following features are Enterprise Cloud-only and currently always reconciled:
PUT /orgs/{org}/rulesetsPOST /orgs/{org}/code-security/configurationsPATCH /teams/{team_id}/team-sync/group-mappingsAll other features (org settings, repositories, repo rulesets, webhooks, teams, custom properties, actions settings) work on all plans.
Proposed Approach
Add a
planfield toOrganizationSpecthat declares the GitHub plan tier. Reconcilers conditionally skip Enterprise-only tasks based on this field. This follows the operator's philosophy of explicit, declarative configuration.Alternative considered
Graceful degradation (detect
403/404and skip) was considered but rejected because:403is ambiguous — could be a permissions issue vs. a plan limitationRequired Changes
1. CRD: Add
planfield to OrganizationSpecFile:
api/v1alpha1/organization_types.goAdd a
Planfield toOrganizationSpec:Default is
enterpriseto maintain backward compatibility.2. Organization reconciler: Conditional reconciliation groups
File:
internal/reconciler/orgrec/reconciler.goMake
RequiredReconciliations()conditional based onplan:The
RequiredReconciliations()method should build the group dynamically instead of returning a static slice.3. Team reconciler: Conditional IDP sync
File:
internal/reconciler/teamrec/reconciler.goSkip IDP group sync reconciliation when the team's organizations are not on the Enterprise plan. Since teams can span multiple organizations, this requires checking the plan of each referenced organization.
4. Conditions: Dynamic required conditions
File:
internal/conditions/conditions.goThe
SetReadyCondition()aggregation currently hardcodes all condition types as required. When Enterprise-only features are skipped, their conditions won't be set, so the aggregation must only consider conditions for enabled features.Options:
RequiredReconciliations())5. Validation webhook: Reject invalid plan/feature combinations
File:
internal/webhook/v1alpha1/organization_webhook.goAdd validation that rejects Enterprise-only spec fields when
plan != "enterprise":spec.rulesetsonfreeorteamplansspec.codeSecurityConfigurationsonfreeorteamplansThis gives users early feedback rather than runtime failures.
6. Status: Surface skipped features
Consider adding a condition or event indicating which features were skipped due to plan tier, so users understand why certain features are not being reconciled.
7. Regenerate and test
make manifests generateto regenerate CRDs and deepcopy methodsReadycondition aggregation with subsets of conditionsAcceptance Criteria
OrganizationCRD has aplanfield with valuesenterprise,team,free(default:enterprise)Readycondition correctly aggregates only the conditions for enabled featuresplanis unset orenterprise(backward compatible)Notes
planfield is purely informational to the operator — it does not query GitHub to verify the actual plan. Users are responsible for setting it correctly.This issue was drafted with the help of GitHub Copilot.