diff --git a/github/resource_github_repository.go b/github/resource_github_repository.go index 5cedfb9046..2188e66afa 100644 --- a/github/resource_github_repository.go +++ b/github/resource_github_repository.go @@ -54,6 +54,58 @@ func resourceGithubRepository() *schema.Resource { Computed: true, // is affected by "private" ValidateFunc: validation.StringInSlice([]string{"public", "private", "internal"}, false), }, + "security_and_analysis": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Description: "Security and analysis settings for the repository. To use this parameter you must have admin permissions for the repository or be an owner or security manager for the organization that owns the repository.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "advanced_security": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "status": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"enabled", "disabled"}, false), + }, + }, + }, + }, + "secret_scanning": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "status": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"enabled", "disabled"}, false), + }, + }, + }, + }, + "secret_scanning_push_protection": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "status": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{"enabled", "disabled"}, false), + }, + }, + }, + }, + }, + }, + }, "has_issues": { Type: schema.TypeBool, Optional: true, @@ -417,6 +469,14 @@ func resourceGithubRepositoryCreate(d *schema.ResourceData, meta interface{}) er } } + securityAndAnalysis := expandSecurityAndAnalysis(d.Get("security_and_analysis").([]interface{})) + if securityAndAnalysis != nil { + _, _, err := client.Repositories.Edit(ctx, owner, repoName, securityAndAnalysis) + if err != nil { + return err + } + } + return resourceGithubRepositoryUpdate(d, meta) } @@ -512,6 +572,12 @@ func resourceGithubRepositoryRead(d *schema.ResourceData, meta interface{}) erro d.Set("vulnerability_alerts", vulnerabilityAlerts) } + securityAndAnalysis, _, err := client.Repositories.Edit(ctx, owner, repoName, &github.Repository{}) + if err != nil { + return fmt.Errorf("Error reading repository security and analysis settings: %v", err) + } + d.Set("security_and_analysis", flattenSecurityAndAnalysis(securityAndAnalysis.GetSecurityAndAnalysis())) + return nil } @@ -563,6 +629,29 @@ func resourceGithubRepositoryUpdate(d *schema.ResourceData, meta interface{}) er } } + if d.HasChange("security_and_analysis") && !d.IsNewResource() { + opts := expandSecurityAndAnalysis(d.Get("security_and_analysis").([]interface{})) + if opts != nil { + _, _, err := client.Repositories.Edit(ctx, owner, repoName, opts) + if err != nil { + return err + } + } else { // disable security and analysis + _, _, err := client.Repositories.Edit(ctx, owner, repoName, &github.Repository{ + SecurityAndAnalysis: &github.SecurityAndAnalysis{ + AdvancedSecurity: &github.AdvancedSecurity{ + Status: github.String("disabled")}, + SecretScanning: &github.SecretScanning{ + Status: github.String("disabled")}, + SecretScanningPushProtection: &github.SecretScanningPushProtection{ + Status: github.String("disabled")}}, + }) + if err != nil { + return err + } + } + } + if d.HasChange("topics") { topics := repoReq.Topics _, _, err = client.Repositories.ReplaceAllTopics(ctx, owner, *repo.Name, topics) @@ -715,3 +804,51 @@ func flattenPages(pages *github.Pages) []interface{} { return []interface{}{pagesMap} } + +func flattenSecurityAndAnalysis(securityAndAnalysis *github.SecurityAndAnalysis) []interface{} { + if securityAndAnalysis == nil { + return []interface{}{} + } + + advancedSecurityMap := make(map[string]interface{}) + advancedSecurityMap["status"] = securityAndAnalysis.GetAdvancedSecurity().GetStatus() + + secretScanningMap := make(map[string]interface{}) + secretScanningMap["status"] = securityAndAnalysis.GetSecretScanning().GetStatus() + + secretScanningPushProtectionMap := make(map[string]interface{}) + secretScanningPushProtectionMap["status"] = securityAndAnalysis.GetSecretScanningPushProtection().GetStatus() + + securityAndAnalysisMap := make(map[string]interface{}) + securityAndAnalysisMap["advanced_security"] = []interface{}{advancedSecurityMap} + securityAndAnalysisMap["secret_scanning"] = []interface{}{secretScanningMap} + securityAndAnalysisMap["secret_scanning_push_protection"] = []interface{}{secretScanningPushProtectionMap} + + return []interface{}{securityAndAnalysisMap} +} + +func expandSecurityAndAnalysis(input []interface{}) *github.Repository { + if len(input) == 0 || input[0] == nil { + return nil + } + + securityAndAnalysis := input[0].(map[string]interface{}) + update := &github.SecurityAndAnalysis{} + + advancedSecurity := securityAndAnalysis["advanced_security"].([]interface{})[0].(map[string]interface{}) + update.AdvancedSecurity = &github.AdvancedSecurity{ + Status: github.String(advancedSecurity["status"].(string)), + } + + secretScanning := securityAndAnalysis["secret_scanning"].([]interface{})[0].(map[string]interface{}) + update.SecretScanning = &github.SecretScanning{ + Status: github.String(secretScanning["status"].(string)), + } + + secretScanningPushProtection := securityAndAnalysis["secret_scanning_push_protection"].([]interface{})[0].(map[string]interface{}) + update.SecretScanningPushProtection = &github.SecretScanningPushProtection{ + Status: github.String(secretScanningPushProtection["status"].(string)), + } + + return &github.Repository{SecurityAndAnalysis: update} +} diff --git a/github/resource_github_repository_test.go b/github/resource_github_repository_test.go index 3d9144e258..4528e681f5 100644 --- a/github/resource_github_repository_test.go +++ b/github/resource_github_repository_test.go @@ -809,6 +809,67 @@ func TestAccGithubRepositoryPages(t *testing.T) { } +func TestAccGithubRepositorySecurity(t *testing.T) { + + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + + t.Run("manages the security feature for a repository", func(t *testing.T) { + + config := fmt.Sprintf(` + resource "github_repository" "test" { + name = "tf-acc-%s" + description = "A repository created by Terraform to test security features" + visibility = "internal" + security_and_analysis { + advanced_security { + status = "enabled" + } + secret_scanning { + status = "enabled" + } + secret_scanning_push_protection { + status = "enabled" + } + } + `, randomID) + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "github_repository.test", "security_and_analysis.0.advanced_security.0.status", + "enabled", + ), + resource.TestCheckResourceAttr( + "github_repository.test", "security_and_analysis.0.secret_scanning.0.status", + "enabled", + ), + ) + testCase := func(t *testing.T, mode string) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, mode) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + }) + } + + t.Run("with an anonymous account", func(t *testing.T) { + t.Skip("anonymous account not supported for this operation") + }) + + t.Run("with an individual account", func(t *testing.T) { + t.Skip("individual account not supported for this operation") + }) + + t.Run("with an organization account", func(t *testing.T) { + testCase(t, organization) + }) + }) +} + func TestAccGithubRepositoryVisibility(t *testing.T) { randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) diff --git a/website/docs/r/repository.html.markdown b/website/docs/r/repository.html.markdown index b816fcf5f0..51980679b8 100644 --- a/website/docs/r/repository.html.markdown +++ b/website/docs/r/repository.html.markdown @@ -106,6 +106,8 @@ initial repository creation and create the target branch inside of the repositor * `pages` - (Optional) The repository's GitHub Pages configuration. See [GitHub Pages Configuration](#github-pages-configuration) below for details. +* `security_and_analysis` - (Optional) The repository's [security and analysis](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/enabling-features-for-your-repository/managing-security-and-analysis-settings-for-your-repository) configuration. See [Security and Analysis Configuration](#security-and-analysis-configuration) below for details. + * `topics` - (Optional) The list of topics of the repository. * `template` - (Optional) Use a template repository to create this resource. See [Template Repositories](#template-repositories) below for details. @@ -132,6 +134,30 @@ The `source` block supports the following: * `path` - (Optional) The repository directory from which the site publishes (Default: `/`). +### Security and Analysis Configuration + +The `security_and_analysis` block supports the following: + +* `advanced_security` - (Required) The advanced security configuration for the repository. See [Advanced Security Configuration](#advanced-security-configuration) below for details. + +* `secret_scanning` - (Required) The secret scanning configuration for the repository. See [Secret Scanning Configuration](#secret-scanning-configuration) below for details. + +* `secret_scanning_push_protection` - (Required) The secret scanning push protection configuration for the repository. See [Secret Scanning Push Protection Configuration](#secret-scanning-push-protection-configuration) below for details. + +#### Advanced Security Configuration #### + +The `advanced_security` block supports the following: + +* `status` - (Required) Set to `enabled` to enable advanced security features on the repository. Can be `enabled` or `disabled`. + +#### Secret Scanning Configuration #### + +* `status` - (Required) Set to `enabled` to enable secret scanning on the repository. Can be `enabled` or `disabled`. + +#### Secret Scanning Push Protection Configuration #### + +* `status` - (Required) Set to `enabled` to enable secret scanning push protection on the repository. Can be `enabled` or `disabled`. + ### Template Repositories `template` supports the following arguments: