-
Notifications
You must be signed in to change notification settings - Fork 4
GCSS-1135: Add support for GitHub environment with deployment policy #28
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
base: main
Are you sure you want to change the base?
GCSS-1135: Add support for GitHub environment with deployment policy #28
Conversation
|
|
||
| Configure GitHub deployment environments with protection rules and reviewers. | ||
|
|
||
| **Import Control**: Set `feature_github_environment: true` in `import-config.yaml` to import environments. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be in another document, something like IMPORTER_DEVELOPERS_GUIDE.md or in the LOCAL_DEVELOPMENT_SETUP.md (if it's not already there).
The DEVELOPERS_GUIDE.md should focus only on the fields allowed to be set in a repo yaml config file
| ### Environment Fields | ||
|
|
||
| - **`environment`**: *(required, string)* Environment name | ||
| - **`wait_timer`**: *(optional, int)* Delay in seconds (max 43200) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't this a value in minutes not seconds? Please verify
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
correct, it's in minutes (max is 30 days or 43200 minutes) - will update it
verifyuing on my org for demo1 repo and its actual config
are you ok with below edit?
| - **`wait_timer`**: *(optional, int)* Delay in seconds (max 43200) | |
| - **`wait_timer`**: *(optional, int)* Delay in minutes (max 43200 or 30 days) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, looks good
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this file is necessary. We have an example configuration file in the config template repo: https://github.com/G-Research/github-terraformer-configuration-template/blob/main/repos/repository.yaml.example
Consider moving information to the example
|
General remark: the name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't be a part of this PR. Please move it to another PR that focuses on documentation. This one should be only about environments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't be a part of this PR. Please move it to another PR that focuses on documentation. This one should be only about environments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't be a part of this PR. Please move it to another PR that focuses on documentation. This one should be only about environments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't be a part of this PR. Please move it to another PR that focuses on documentation. This one should be only about environments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't be a part of this PR. Please move it to another PR that focuses on documentation. This one should be only about environments. Also, i don't get the purpose of this file. Maybe we go through this on a call?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Module modules/terraform-github-repository is vendored. That means it shouldn't be changed. All changes and features are added to the root module
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Module modules/terraform-github-repository is vendored. That means it shouldn't be changed. All changes and features are added to the root module
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Module modules/terraform-github-repository is vendored. That means it shouldn't be changed. All changes and features are added to the root module
| # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
| # Environments Configuration | ||
| # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
|
||
| environments = try(each.value.environments, []) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once environments are moved from the child module to the root module, this should be removed.
| import { | ||
| for_each = local.generated_environments_map | ||
|
|
||
| to = module.repository[each.value.repository].github_repository_environment.environment[each.value.environment.environment] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will change once environments are moved to root module
| type Environment struct { | ||
| Environment string `yaml:"environment"` | ||
| WaitTimer *int `yaml:"wait_timer,omitempty"` | ||
| CanAdminsBypass *bool `yaml:"can_admins_bypass,omitempty"` | ||
| PreventSelfReview *bool `yaml:"prevent_self_review,omitempty"` // Extracted from ProtectionRules in API response | ||
| Reviewers *EnvironmentReviewers `yaml:"reviewers,omitempty"` | ||
| DeploymentPolicy *DeploymentPolicy `yaml:"deployment_policy,omitempty"` | ||
| } | ||
|
|
||
| type EnvironmentReviewers struct { | ||
| Teams []string `yaml:"teams,omitempty"` // Team slugs (e.g., "platform-team") | ||
| Users []string `yaml:"users,omitempty"` // GitHub usernames (e.g., "octocat") | ||
| } | ||
|
|
||
| // DeploymentPolicy represents the cleaner structure for deployment policies | ||
| type DeploymentPolicy struct { | ||
| PolicyType string `yaml:"policy_type"` // "protected_branches" or "selected_branches_and_tags" | ||
| BranchPatterns []string `yaml:"branch_patterns,omitempty"` // e.g., ["release/*", "main"] - only for selected_branches_and_tags | ||
| TagPatterns []string `yaml:"tag_patterns,omitempty"` // e.g., ["v*"] - only for selected_branches_and_tags | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please move this to environments.go. And remove comments
| // Log enabled features | ||
| if cfg != nil && cfg.Features != nil { | ||
| for featureName, enabled := range cfg.Features { | ||
| if enabled { | ||
| fmt.Printf("Feature enabled: %s\n", featureName) | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not needed, please remove
| - **`users`**: *(string[])* GitHub usernames (max 6 total) | ||
| - **`teams`**: *(string[])* Team slugs (max 6 total) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can be max 6 in total, not 6 per list
| // Feature flags | ||
| // All feature flags follow the pattern: feature_<feature_name> | ||
| // Add new features here and they'll automatically work with the config system | ||
| FeatureGithubEnvironment = "feature_github_environment" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| FeatureGithubEnvironment = "feature_github_environment" | |
| FeatureGithubEnvironments = "feature_github_environments" |
| IgnoredRepos []string `yaml:"ignored_repos,omitempty"` | ||
| SelectedRepos []string `yaml:"selected_repos,omitempty"` | ||
| PageSize *int `yaml:"page_size,omitempty"` | ||
| Features map[string]bool `yaml:",inline"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This line means that any field added to the root of the import-config.yaml file that has a boolean value will act as a feature flag, eg:
feature_github_environment: true
some_ficticious_feature: true
Both will be captured. We want a strict configuration file that can be (de)serialized explicitly. Please change the Config struct accordingly
| // Feature flags | ||
| // All feature flags follow the pattern: feature_<feature_name> | ||
| // Add new features here and they'll automatically work with the config system |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not correct information. Please remove it. It's also affected by the comment https://github.com/G-Research/github-terraformer/pull/28/files#r2549525571
| // Example future features: | ||
| // FeatureGithubWebhooks = "feature_github_webhooks" | ||
| // FeatureGithubSecrets = "feature_github_secrets" | ||
| // FeatureGithubTopics = "feature_github_topics" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove comments. Also, topics are already supported
| enableEnvironments := false | ||
| if cfg != nil && cfg.Features != nil { | ||
| if enabled, exists := cfg.Features[FeatureGithubEnvironment]; exists { | ||
| enableEnvironments = enabled | ||
| } | ||
| } | ||
|
|
||
| if enableEnvironments { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the three ifs can be reduced. you added a function that is not used cfg.IsFeatureEnabled that you can use to reduce the number of ifs. once that is done, you won't need enableEnvironments any more
| if err != nil { | ||
| if res != nil && res.StatusCode == http.StatusNotFound { | ||
| fmt.Printf("environments not found (this is normal for repositories without environments): %v\n", err) | ||
| } else { | ||
| fmt.Printf("failed to get environments: %v\n", err) | ||
| } | ||
| break | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be changed so that we fail the import if getting the environments fail. Repo can not and shouldn't be partially imported, otherwise we will get drift in terraform. So it's "all or nothing" approach
| if err != nil { | ||
| fmt.Printf("Warning: failed to get full details for environment %s: %v\n", *env.Name, err) | ||
| // Use basic info if detailed fetch fails | ||
| allEnvironments = append(allEnvironments, env) | ||
| } else { | ||
| // Use full environment data with all details | ||
| allEnvironments = append(allEnvironments, fullEnv) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar as above, this should be changed so that we fail the import if getting the environments fail. Repo can not and shouldn't be partially imported, otherwise we will get drift in terraform. So it's "all or nothing" approach
| if err != nil { | ||
| fmt.Printf("Warning: failed to get organization info: %v\n", err) | ||
| return nil | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be changed to a more go idiomatic approach, where you capture the error, log it, and escalate it above to the called function. Check the rest of the code to see how it's done, it's used everywhere. function resolveEnvironments will then return ([]Environment, error)
| continue | ||
| } | ||
|
|
||
| // The Reviewer field is an interface{} - try different type casts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep this one it's useful
| // Fallback: try map[string]interface{} for older API versions | ||
| if reviewerData, ok := reqReviewer.Reviewer.(map[string]interface{}); ok { | ||
| if login, ok := reviewerData["login"].(string); ok { | ||
| protectionReviewers.Users = append(protectionReviewers.Users, login) | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i don't understand this. please explain
|
I will remove comments that say "remove comments", and leave only those that say "keep this comment" so that the number of asked changes here are reduced, but it also means that comments should be removed |
| CanAdminsBypass: env.CanAdminsBypass, | ||
| } | ||
|
|
||
| // Extract PreventSelfReview, WaitTimer, and Reviewers from ProtectionRules |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep this comment line, remove second and third
| // Handle reviewers at top level (fallback if not in ProtectionRules) | ||
| // API may return array of EnvReviewers with Type and ID | ||
| // We resolve IDs to human-readable names (usernames and team slugs) | ||
| // Note: This is usually empty as reviewers are typically in ProtectionRules | ||
| if env.Reviewers != nil && len(env.Reviewers) > 0 && environment.Reviewers == nil { | ||
| reviewers := &EnvironmentReviewers{} | ||
|
|
||
| // Separate reviewers by type and resolve IDs to names | ||
| for _, reviewer := range env.Reviewers { | ||
| if reviewer.Type != nil && reviewer.ID != nil { | ||
| switch *reviewer.Type { | ||
| case "Team": | ||
| // Resolve team ID to team slug | ||
| team, _, err := client.Teams.GetTeamByID(context.Background(), org.GetID(), *reviewer.ID) | ||
| if err != nil { | ||
| fmt.Printf("Warning: failed to resolve team ID %d: %v\n", *reviewer.ID, err) | ||
| continue | ||
| } | ||
| if team.Slug != nil { | ||
| reviewers.Teams = append(reviewers.Teams, *team.Slug) | ||
| } | ||
| case "User": | ||
| // Resolve user ID to username | ||
| user, _, err := client.Users.GetByID(context.Background(), *reviewer.ID) | ||
| if err != nil { | ||
| fmt.Printf("Warning: failed to resolve user ID %d: %v\n", *reviewer.ID, err) | ||
| continue | ||
| } | ||
| if user.Login != nil { | ||
| reviewers.Users = append(reviewers.Users, *user.Login) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if len(reviewers.Teams) > 0 || len(reviewers.Users) > 0 { | ||
| environment.Reviewers = reviewers | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove, unless you found a case where this is set. if there is one, then this block and the other on top can be merged, and reduced
| } else if env.DeploymentBranchPolicy.CustomBranchPolicies != nil && *env.DeploymentBranchPolicy.CustomBranchPolicies { | ||
| // Custom branch/tag patterns | ||
| deploymentPolicy.PolicyType = "selected_branches_and_tags" | ||
|
|
||
| // Fetch deployment branch policies from GitHub API | ||
| branchPatterns, tagPatterns := fetchDeploymentPolicies(client, owner, repo, env.GetName()) | ||
|
|
||
| if len(branchPatterns) > 0 { | ||
| deploymentPolicy.BranchPatterns = branchPatterns | ||
| } | ||
| if len(tagPatterns) > 0 { | ||
| deploymentPolicy.TagPatterns = tagPatterns | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs an else statement that should raise an error, because it means someone tampered with the json file and introduced something we don't know exists, or Github made a change, and we should be aware if any of the two happened. If so, throw an error, and fail the import.
| if len(branchPatterns) > 0 { | ||
| deploymentPolicy.BranchPatterns = branchPatterns | ||
| } | ||
| if len(tagPatterns) > 0 { | ||
| deploymentPolicy.TagPatterns = tagPatterns | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two should work together and check if length of both arrays in total is bigger than 6. If yes, throw an error, and fail the import.
pavlovic-ivan
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please take a look at comments and change accordingly
Co-authored-by: Ivan Pavlovic <[email protected]>
Co-authored-by: Ivan Pavlovic <[email protected]>
Co-authored-by: Ivan Pavlovic <[email protected]>
Co-authored-by: Ivan Pavlovic <[email protected]>
Co-authored-by: Ivan Pavlovic <[email protected]>
Add GitHub Environment Support with Improved Deployment Policy Structure
This PR adds support for GitHub repository environments and introduces a cleaner, more intuitive deployment policy structure throughout the codebase.
Key Changes
🚀 New Feature: GitHub Environments
🎛️ Feature Flag System
📝 Environment Configuration Structure
🔧 Implementation Details
Importer Changes:
Terraform Changes:
📚 Documentation
How to Use
🔄 Migration
Testing
Important Notes
This PR adds GitHub environment support as a new, feature-flagged capability that can be gradually rolled out without affecting existing workflows.
References