diff --git a/github/provider.go b/github/provider.go index 8f44c9509..ae0d330c0 100644 --- a/github/provider.go +++ b/github/provider.go @@ -157,6 +157,7 @@ func Provider() *schema.Provider { "github_issue_label": resourceGithubIssueLabel(), "github_issue_labels": resourceGithubIssueLabels(), "github_membership": resourceGithubMembership(), + "github_organization_code_security_configuration": resourceGithubOrganizationCodeSecurityConfiguration(), "github_organization_block": resourceOrganizationBlock(), "github_organization_custom_role": resourceGithubOrganizationCustomRole(), "github_organization_project": resourceGithubOrganizationProject(), diff --git a/github/resource_github_organization_code_security_configuration.go b/github/resource_github_organization_code_security_configuration.go new file mode 100644 index 000000000..5b41c619f --- /dev/null +++ b/github/resource_github_organization_code_security_configuration.go @@ -0,0 +1,268 @@ +package github + +import ( + "context" + "fmt" + "strconv" + + "github.com/google/go-github/v66/github" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceGithubOrganizationCodeSecurityConfiguration() *schema.Resource { + return &schema.Resource{ + Create: resourceGithubOrganizationCodeSecurityConfigurationCreate, + Read: resourceGithubOrganizationCodeSecurityConfigurationRead, + Update: resourceGithubOrganizationCodeSecurityConfigurationUpdate, + Delete: resourceGithubOrganizationCodeSecurityConfigurationDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "The name of the code security configuration. Must be unique within the organization.", + }, + "description": { + Type: schema.TypeString, + Optional: true, + Default: "", + Description: "A description of the code security configuration.", + }, + "advanced_security": { + Type: schema.TypeString, + Optional: true, + Default: "disabled", + Description: "The enablement status of GitHub Advanced Security features. enabled will enable both Code Security or Secret Protection features. Can be one of: `enabled`, `disabled`, `code_security` and `secret_protection`.", + ValidateDiagFunc: validateValueFunc([]string{"enabled", "disabled", "code_security", "secret_protection"}), + }, + "dependency_graph": { + Type: schema.TypeString, + Optional: true, + Default: "enabled", + Description: "The enablement status of Dependency Graph. Can be one of: `enabled` or `disabled`", + ValidateDiagFunc: validateValueFunc([]string{"enabled", "disabled"}), + }, + "dependency_graph_autosubmit_action": { + Type: schema.TypeString, + Optional: true, + Default: "disabled", + Description: "The enablement status of Automatic dependency submission. Can be one of: `enabled` or `disabled`", + ValidateDiagFunc: validateValueFunc([]string{"enabled", "disabled"}), + }, + "dependency_graph_autosubmit_action_options": { + Type: schema.TypeList, + MaxItems: 1, + Optional: true, + Description: "The options for the Automatic dependency submission action.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "labeled_runners": { + Type: schema.TypeBool, + Default: false, + Optional: true, + Description: "Whether to use runners labeled with 'dependency-submission' or standard GitHub runners.", + }, + }, + }, + }, + "dependabot_alerts": { + Type: schema.TypeString, + Optional: true, + Default: "disabled", + Description: "The enablement status of Dependabot alerts. Can be one of: `enabled` or `disabled`", + ValidateDiagFunc: validateValueFunc([]string{"enabled", "disabled"}), + }, + "dependabot_security_updates": { + Type: schema.TypeString, + Optional: true, + Default: "disabled", + Description: "The enablement status of Dependabot security updates. Can be one of: `enabled` or `disabled`", + ValidateDiagFunc: validateValueFunc([]string{"enabled", "disabled"}), + }, + "code_scanning_default_setup": { + Type: schema.TypeString, + Optional: true, + Default: "disabled", + Description: "The enablement status of code scanning default setup. Can be one of: `enabled` or `disabled`", + ValidateDiagFunc: validateValueFunc([]string{"enabled", "disabled"}), + }, + "secret_scanning": { + Type: schema.TypeString, + Optional: true, + Default: "disabled", + Description: "The enablement status of secret scanning. Can be one of: `enabled` or `disabled`", + ValidateDiagFunc: validateValueFunc([]string{"enabled", "disabled"}), + }, + "secret_scanning_push_protection": { + Type: schema.TypeString, + Optional: true, + Default: "disabled", + Description: "The enablement status of secret scanning push protection. Can be one of: `enabled` or `disabled`", + ValidateDiagFunc: validateValueFunc([]string{"enabled", "disabled"}), + }, + "secret_scanning_validity_checks": { + Type: schema.TypeString, + Optional: true, + Default: "disabled", + Description: "The enablement status of secret scanning validity checks. Can be one of: `enabled` or `disabled`", + ValidateDiagFunc: validateValueFunc([]string{"enabled", "disabled"}), + }, + "secret_scanning_non_provider_patterns": { + Type: schema.TypeString, + Optional: true, + Default: "disabled", + Description: "The enablement status of secret scanning non-provider patterns. Can be one of: `enabled` or `disabled`", + ValidateDiagFunc: validateValueFunc([]string{"enabled", "disabled"}), + }, + "private_vulnerability_reporting": { + Type: schema.TypeString, + Optional: true, + Default: "disabled", + Description: "The enablement status of private vulnerability reporting. Can be one of: `enabled` or `disabled`", + ValidateDiagFunc: validateValueFunc([]string{"enabled", "disabled"}), + }, + "enforcement": { + Type: schema.TypeString, + Optional: true, + Default: "enforced", + Description: "The enforcement status for a security configuration. Can be one of: `enforced` or `unenforced`", + ValidateDiagFunc: validateValueFunc([]string{"enforced", "unenforced"}), + }, + }, + } +} + +func resourceGithubOrganizationCodeSecurityConfigurationCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Owner).v3client + orgName := meta.(*Owner).name + ctx := context.Background() + + if err := checkOrganization(meta); err != nil { + return err + } + + options := &github.CodeSecurityConfiguration{ + Name: github.String(d.Get("name").(string)), + Description: github.String(d.Get("description").(string)), + AdvancedSecurity: github.String(d.Get("advanced_security").(string)), + DependencyGraph: github.String(d.Get("dependency_graph").(string)), + DependencyGraphAutosubmitAction: github.String(d.Get("dependency_graph_autosubmit_action").(string)), + DependabotAlerts: github.String(d.Get("dependabot_alerts").(string)), + DependabotSecurityUpdates: github.String(d.Get("dependabot_security_updates").(string)), + CodeScanningDefaultSetup: github.String(d.Get("code_scanning_default_setup").(string)), + SecretScanningPushProtection: github.String(d.Get("secret_scanning_push_protection").(string)), + SecretScanningValidityChecks: github.String(d.Get("secret_scanning_validity_checks").(string)), + SecretScanningNonProviderPatterns: github.String(d.Get("secret_scanning_non_provider_patterns").(string)), + PrivateVulnerabilityReporting: github.String(d.Get("private_vulnerability_reporting").(string)), + SecretScanning: github.String(d.Get("secret_scanning").(string)), + Enforcement: github.String(d.Get("enforcement").(string)), + } + + config, _, err := client.Organizations.CreateCodeSecurityConfiguration(ctx, orgName, options) + if err != nil { + return err + } + d.SetId(fmt.Sprint(config.GetID())) + return resourceGithubOrganizationCodeSecurityConfigurationRead(d, meta) +} + +func resourceGithubOrganizationCodeSecurityConfigurationRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Owner).v3client + orgName := meta.(*Owner).name + ctx := context.Background() + if err := checkOrganization(meta); err != nil { + return err + } + configID, err := strconv.ParseInt(d.Id(), 10, 64) + if err != nil { + return unconvertibleIdErr(d.Id(), err) + } + + config, _, err := client.Organizations.GetCodeSecurityConfiguration(ctx, orgName, configID) + if err != nil { + return err + } + + d.Set("name", config.GetName()) + d.Set("description", config.GetDescription()) + d.Set("advanced_security", config.GetAdvancedSecurity()) + d.Set("dependency_graph", config.GetDependencyGraph()) + d.Set("dependency_graph_autosubmit_action", config.GetDependencyGraphAutosubmitAction()) + d.Set("dependabot_alerts", config.GetDependabotAlerts()) + d.Set("dependabot_security_updates", config.GetDependabotSecurityUpdates()) + d.Set("code_scanning_default_setup", config.GetCodeScanningDefaultSetup()) + d.Set("secret_scanning_push_protection", config.GetSecretScanningPushProtection()) + d.Set("secret_scanning_validity_checks", config.GetSecretScanningValidityChecks()) + d.Set("secret_scanning_non_provider_patterns", config.GetSecretScanningNonProviderPatterns()) + d.Set("private_vulnerability_reporting", config.GetPrivateVulnerabilityReporting()) + d.Set("secret_scanning", config.GetSecretScanning()) + d.Set("enforcement", config.GetEnforcement()) + + return nil +} +func resourceGithubOrganizationCodeSecurityConfigurationUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Owner).v3client + orgName := meta.(*Owner).name + ctx := context.Background() + if err := checkOrganization(meta); err != nil { + return err + } + configID, err := strconv.ParseInt(d.Id(), 10, 64) + if err != nil { + return unconvertibleIdErr(d.Id(), err) + } + + options := resourceGithubOrganizationCodeSecurityConfigurationObject(d) + + config, _, err := client.Organizations.UpdateCodeSecurityConfiguration(ctx, orgName, configID, options) + if err != nil { + return err + } + d.SetId(fmt.Sprint(config.GetID())) + return resourceGithubOrganizationCodeSecurityConfigurationRead(d, meta) +} +func resourceGithubOrganizationCodeSecurityConfigurationDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*Owner).v3client + orgName := meta.(*Owner).name + ctx := context.Background() + if err := checkOrganization(meta); err != nil { + return err + } + configID, err := strconv.ParseInt(d.Id(), 10, 64) + if err != nil { + return unconvertibleIdErr(d.Id(), err) + } + _, err = client.Organizations.DeleteCodeSecurityConfiguration(ctx, orgName, configID) + return err +} + +func resourceGithubOrganizationCodeSecurityConfigurationObject(d *schema.ResourceData) *github.CodeSecurityConfiguration { + config := &github.CodeSecurityConfiguration{ + Name: github.String(d.Get("name").(string)), + Description: github.String(d.Get("description").(string)), + AdvancedSecurity: github.String(d.Get("advanced_security").(string)), + DependencyGraph: github.String(d.Get("dependency_graph").(string)), + DependencyGraphAutosubmitAction: github.String(d.Get("dependency_graph_autosubmit_action").(string)), + DependabotAlerts: github.String(d.Get("dependabot_alerts").(string)), + DependabotSecurityUpdates: github.String(d.Get("dependabot_security_updates").(string)), + CodeScanningDefaultSetup: github.String(d.Get("code_scanning_default_setup").(string)), + SecretScanningPushProtection: github.String(d.Get("secret_scanning_push_protection").(string)), + SecretScanningValidityChecks: github.String(d.Get("secret_scanning_validity_checks").(string)), + SecretScanningNonProviderPatterns: github.String(d.Get("secret_scanning_non_provider_patterns").(string)), + PrivateVulnerabilityReporting: github.String(d.Get("private_vulnerability_reporting").(string)), + SecretScanning: github.String(d.Get("secret_scanning").(string)), + Enforcement: github.String(d.Get("enforcement").(string)), + } + + if options := d.Get("dependency_graph_autosubmit_action_options"); options != nil && len(options.([]interface{})) > 0 { + graphOptions := options.([]interface{})[0].(map[string]interface{}) + config.DependencyGraphAutosubmitActionOptions = &github.DependencyGraphAutosubmitActionOptions{ + LabeledRunners: github.Bool(graphOptions["labeled_runners"].(bool)), + } + } + + return config +} diff --git a/github/resource_github_organization_code_security_configuration_test.go b/github/resource_github_organization_code_security_configuration_test.go new file mode 100644 index 000000000..3d3b015bd --- /dev/null +++ b/github/resource_github_organization_code_security_configuration_test.go @@ -0,0 +1,298 @@ +package github + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestGithubOrganizationCodeSecurityConfiguration(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum) + + t.Run("creates code security configuration without error", func(t *testing.T) { + config := fmt.Sprintf(` + resource "github_organization_code_security_configuration" "test" { + name = "tf-acc-test-%s" + description = "Test code security configuration description" + advanced_security = "enabled" + dependency_graph = "enabled" + dependency_graph_autosubmit_action_options { + labeled_runners = true + } + dependabot_alerts = "enabled" + dependabot_security_updates = "enabled" + secret_scanning = "enabled" + secret_scanning_push_protection = "enabled" + secret_scanning_validity_checks = "enabled" + secret_scanning_non_provider_patterns = "enabled" + private_vulnerability_reporting = "enabled" + enforcement = "unenforced" + } + `, randomID) + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet( + "github_organization_code_security_configuration.test", "name", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "name", + fmt.Sprintf(`tf-acc-test-%s`, randomID), + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "description", + "Test code security configuration description", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "advanced_security", + "enabled", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "dependency_graph", + "enabled", + ), + resource.TestCheckTypeSetElemNestedAttrs( + "github_organization_code_security_configuration.test", "dependency_graph_autosubmit_action_options.*", + map[string]string{ + "labeled_runners": "true", + }, + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "dependabot_alerts", + "enabled", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "dependabot_security_updates", + "enabled", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "secret_scanning", + "enabled", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "secret_scanning_push_protection", + "enabled", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "secret_scanning_validity_checks", + "enabled", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "secret_scanning_non_provider_patterns", + "enabled", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "private_vulnerability_reporting", + "enabled", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "enforcement", + "unenforced", + ), + ) + + 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) + }) + }) + + t.Run("updates code security configuration without error", func(t *testing.T) { + + configs := map[string]string{ + "before": fmt.Sprintf(` + resource "github_organization_code_security_configuration" "test" { + name = "tf-acc-test-%s" + description = "Test code security configuration description" + enforcement = "unenforced" + } + `, randomID), + "after": fmt.Sprintf(` + resource "github_organization_code_security_configuration" "test" { + name = "tf-acc-test-%s" + description = "Test code security configuration description" + enforcement = "enforced" + } + `, randomID), + } + + checks := map[string]resource.TestCheckFunc{ + "before": resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet( + "github_organization_code_security_configuration.test", "name", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "name", + fmt.Sprintf(`tf-acc-test-%s`, randomID), + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "description", + "Test code security configuration description", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "enforcement", + "unenforced", + ), + ), + "after": resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet( + "github_organization_code_security_configuration.test", "name", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "name", + fmt.Sprintf(`tf-acc-test-%s`, randomID), + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "description", + "Test code security configuration description", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "enforcement", + "enforced", + ), + )} + + testCase := func(t *testing.T, mode string) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessMode(t, mode) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: configs["before"], + Check: checks["before"], + }, + { + Config: configs["after"], + Check: checks["after"], + }, + }, + }) + } + + 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) + }) + }) + + t.Run("imports code security configuration without error", func(t *testing.T) { + + config := fmt.Sprintf(` + resource "github_organization_code_security_configuration" "test" { + name = "tf-acc-test-%s" + description = "Test code security configuration description" + } + `, randomID) + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet( + "github_organization_code_security_configuration.test", "name", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "name", + fmt.Sprintf(`tf-acc-test-%s`, randomID), + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "description", + "Test code security configuration description", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "advanced_security", + "disabled", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "dependency_graph", + "enabled", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "dependabot_alerts", + "disabled", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "dependabot_security_updates", + "disabled", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "secret_scanning", + "disabled", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "secret_scanning_push_protection", + "disabled", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "secret_scanning_validity_checks", + "disabled", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "secret_scanning_non_provider_patterns", + "disabled", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "private_vulnerability_reporting", + "disabled", + ), + resource.TestCheckResourceAttr( + "github_organization_code_security_configuration.test", "enforcement", + "enforced", + ), + ) + + 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, + }, + { + ResourceName: "github_organization_code_security_configuration.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) + } + + 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) + }) + }) +}