diff --git a/github/data_source_github_enterprise_custom_properties.go b/github/data_source_github_enterprise_custom_properties.go new file mode 100644 index 0000000000..39ae13c2d2 --- /dev/null +++ b/github/data_source_github_enterprise_custom_properties.go @@ -0,0 +1,79 @@ +package github + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceGithubEnterpriseCustomProperties() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceGithubEnterpriseCustomPropertiesRead, + + Schema: map[string]*schema.Schema{ + "enterprise_slug": { + Type: schema.TypeString, + Required: true, + }, + "property_name": { + Type: schema.TypeString, + Required: true, + }, + "value_type": { + Type: schema.TypeString, + Optional: true, + }, + "required": { + Type: schema.TypeBool, + Optional: true, + }, + "default_value": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "allowed_values": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "values_editable_by": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + } +} + +func dataSourceGithubEnterpriseCustomPropertiesRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*Owner).v3client + entSlug := d.Get("enterprise_slug").(string) + + propertyAttributes, _, err := client.Enterprise.GetCustomProperty(ctx, entSlug, d.Get("property_name").(string)) + if err != nil { + return diag.Errorf("error querying GitHub custom properties %s: %v", entSlug, err) + } + + // TODO: Add support for other types of default values + defaultValue, _ := propertyAttributes.DefaultValueString() + + d.SetId("enterprise-custom-properties") + _ = d.Set("enterprise_slug", entSlug) + _ = d.Set("allowed_values", propertyAttributes.AllowedValues) + _ = d.Set("default_value", defaultValue) + _ = d.Set("description", propertyAttributes.Description) + _ = d.Set("property_name", propertyAttributes.PropertyName) + _ = d.Set("required", propertyAttributes.Required) + _ = d.Set("value_type", string(propertyAttributes.ValueType)) + _ = d.Set("values_editable_by", propertyAttributes.ValuesEditableBy) + + return nil +} diff --git a/github/data_source_github_enterprise_custom_properties_test.go b/github/data_source_github_enterprise_custom_properties_test.go new file mode 100644 index 0000000000..70e3f9aec2 --- /dev/null +++ b/github/data_source_github_enterprise_custom_properties_test.go @@ -0,0 +1,58 @@ +package github + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" +) + +func TestAccGithubEnterpriseCustomPropertiesDataSource(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlpha) + propertyName := fmt.Sprintf("test-%s", randomID) + + config := fmt.Sprintf(` + resource "github_enterprise_custom_properties" "test" { + enterprise_slug = "%s" + property_name = "%s" + value_type = "string" + required = true + default_value = "terraform" + description = "A test property" + values_editable_by = "org_and_repo_actors" + } + + data "github_enterprise_custom_properties" "test" { + enterprise_slug = "%s" + property_name = github_enterprise_custom_properties.test.property_name + } + `, + testAccConf.enterpriseSlug, + propertyName, + testAccConf.enterpriseSlug, + ) + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.github_enterprise_custom_properties.test", "enterprise_slug", testAccConf.enterpriseSlug), + resource.TestCheckResourceAttr("data.github_enterprise_custom_properties.test", "property_name", propertyName), + resource.TestCheckResourceAttr("data.github_enterprise_custom_properties.test", "allowed_values.#", "0"), + resource.TestCheckResourceAttr("data.github_enterprise_custom_properties.test", "value_type", "string"), + resource.TestCheckResourceAttr("data.github_enterprise_custom_properties.test", "required", "true"), + resource.TestCheckResourceAttr("data.github_enterprise_custom_properties.test", "default_value", "terraform"), + resource.TestCheckResourceAttr("data.github_enterprise_custom_properties.test", "description", "A test property"), + resource.TestCheckResourceAttr("data.github_enterprise_custom_properties.test", "values_editable_by", "org_and_repo_actors"), + ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessEnterprise(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + }, + ) +} diff --git a/github/provider.go b/github/provider.go index 2d5d6dc33a..f7a8908e0b 100644 --- a/github/provider.go +++ b/github/provider.go @@ -218,6 +218,7 @@ func Provider() *schema.Provider { "github_actions_organization_workflow_permissions": resourceGithubActionsOrganizationWorkflowPermissions(), "github_enterprise_security_analysis_settings": resourceGithubEnterpriseSecurityAnalysisSettings(), "github_workflow_repository_permissions": resourceGithubWorkflowRepositoryPermissions(), + "github_enterprise_custom_properties": resourceGithubEnterpriseCustomProperties(), }, DataSourcesMap: map[string]*schema.Resource{ @@ -294,6 +295,7 @@ func Provider() *schema.Provider { "github_user_external_identity": dataSourceGithubUserExternalIdentity(), "github_users": dataSourceGithubUsers(), "github_enterprise": dataSourceGithubEnterprise(), + "github_enterprise_custom_properties": dataSourceGithubEnterpriseCustomProperties(), "github_repository_environment_deployment_policies": dataSourceGithubRepositoryEnvironmentDeploymentPolicies(), }, } diff --git a/github/resource_github_enterprise_custom_properties.go b/github/resource_github_enterprise_custom_properties.go new file mode 100644 index 0000000000..83db114413 --- /dev/null +++ b/github/resource_github_enterprise_custom_properties.go @@ -0,0 +1,168 @@ +package github + +import ( + "context" + + "github.com/google/go-github/v84/github" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func resourceGithubEnterpriseCustomProperties() *schema.Resource { + return &schema.Resource{ + Create: resourceGithubEnterpriseCustomPropertiesCreate, + Read: resourceGithubEnterpriseCustomPropertiesRead, + Update: resourceGithubEnterpriseCustomPropertiesUpdate, + Delete: resourceGithubEnterpriseCustomPropertiesDelete, + Importer: &schema.ResourceImporter{ + State: resourceGithubEnterpriseCustomPropertiesImport, + }, + + CustomizeDiff: customdiff.Sequence( + customdiff.ComputedIf("slug", func(_ context.Context, d *schema.ResourceDiff, meta any) bool { + return d.HasChange("name") + }), + ), + + Schema: map[string]*schema.Schema{ + "enterprise_slug": { + Type: schema.TypeString, + Required: true, + Description: "The slug of the enterprise to which the custom property belongs", + }, + "property_name": { + Type: schema.TypeString, + Required: true, + Description: "The name of the custom property", + }, + "value_type": { + Type: schema.TypeString, + Optional: true, + Description: "The type of the custom property", + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{string(github.PropertyValueTypeString), string(github.PropertyValueTypeSingleSelect), string(github.PropertyValueTypeMultiSelect), string(github.PropertyValueTypeTrueFalse), string(github.PropertyValueTypeURL)}, false)), + }, + "required": { + Type: schema.TypeBool, + Optional: true, + Description: "Whether the custom property is required", + }, + "default_value": { + Type: schema.TypeString, + Description: "The default value of the custom property", + Optional: true, + Computed: true, + }, + "description": { + Description: "The description of the custom property", + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "allowed_values": { + Description: "The allowed values of the custom property", + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "values_editable_by": { + Description: "Who can edit the values of the custom property. Can be one of 'org_actors' or 'org_and_repo_actors'. If not specified, the default is 'org_actors' (only organization owners can edit values)", + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"org_actors", "org_and_repo_actors"}, false)), + }, + }, + } +} + +func resourceGithubEnterpriseCustomPropertiesCreate(d *schema.ResourceData, meta any) error { + ctx := context.Background() + client := meta.(*Owner).v3client + entSlug := d.Get("enterprise_slug").(string) + + propertyName := d.Get("property_name").(string) + valueType := github.PropertyValueType(d.Get("value_type").(string)) + required := d.Get("required").(bool) + defaultValue := d.Get("default_value").(string) + description := d.Get("description").(string) + allowedValues := d.Get("allowed_values").([]any) + var allowedValuesString []string + for _, v := range allowedValues { + allowedValuesString = append(allowedValuesString, v.(string)) + } + + customProperty := &github.CustomProperty{ + PropertyName: &propertyName, + ValueType: valueType, + Required: &required, + DefaultValue: &defaultValue, + Description: &description, + AllowedValues: allowedValuesString, + } + + if val, ok := d.GetOk("values_editable_by"); ok { + str := val.(string) + customProperty.ValuesEditableBy = &str + } + + customProperty, _, err := client.Enterprise.CreateOrUpdateCustomProperty(ctx, entSlug, d.Get("property_name").(string), customProperty) + if err != nil { + return err + } + + d.SetId(*customProperty.PropertyName) + return resourceGithubEnterpriseCustomPropertiesRead(d, meta) +} + +func resourceGithubEnterpriseCustomPropertiesRead(d *schema.ResourceData, meta any) error { + ctx := context.Background() + client := meta.(*Owner).v3client + entSlug := d.Get("enterprise_slug").(string) + + customProperty, _, err := client.Enterprise.GetCustomProperty(ctx, entSlug, d.Get("property_name").(string)) + if err != nil { + return err + } + + // TODO: Add support for other types of default values + defaultValue, _ := customProperty.DefaultValueString() + + d.SetId(*customProperty.PropertyName) + _ = d.Set("allowed_values", customProperty.AllowedValues) + _ = d.Set("default_value", defaultValue) + _ = d.Set("description", customProperty.Description) + _ = d.Set("property_name", customProperty.PropertyName) + _ = d.Set("required", customProperty.Required) + _ = d.Set("value_type", string(customProperty.ValueType)) + _ = d.Set("values_editable_by", customProperty.ValuesEditableBy) + + return nil +} + +func resourceGithubEnterpriseCustomPropertiesUpdate(d *schema.ResourceData, meta any) error { + if err := resourceGithubEnterpriseCustomPropertiesCreate(d, meta); err != nil { + return err + } + return resourceGithubEnterpriseCustomPropertiesRead(d, meta) +} + +func resourceGithubEnterpriseCustomPropertiesDelete(d *schema.ResourceData, meta any) error { + client := meta.(*Owner).v3client + entSlug := d.Get("enterprise_slug").(string) + + _, err := client.Enterprise.RemoveCustomProperty(context.Background(), entSlug, d.Get("property_name").(string)) + if err != nil { + return err + } + + return nil +} + +func resourceGithubEnterpriseCustomPropertiesImport(d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + if err := d.Set("property_name", d.Id()); err != nil { + return nil, err + } + return []*schema.ResourceData{d}, nil +} diff --git a/github/resource_github_enterprise_custom_properties_test.go b/github/resource_github_enterprise_custom_properties_test.go new file mode 100644 index 0000000000..59970ee3fe --- /dev/null +++ b/github/resource_github_enterprise_custom_properties_test.go @@ -0,0 +1,89 @@ +package github + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccGithubEnterpriseCustomPropertiesResource(t *testing.T) { + t.Run("creates basic custom property without error", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlpha) + propertyName := fmt.Sprintf("test-%s", randomID) + + config := fmt.Sprintf(` + resource "github_enterprise_custom_properties" "test" { + enterprise_slug = "%s" + property_name = "%s" + value_type = "string" + required = true + default_value = "terraform" + description = "A test property" + values_editable_by = "org_and_repo_actors" + } + `, + testAccConf.enterpriseSlug, + propertyName, + ) + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.github_enterprise_custom_properties.test", "enterprise_slug", testAccConf.enterpriseSlug), + resource.TestCheckResourceAttr("data.github_enterprise_custom_properties.test", "property_name", propertyName), + resource.TestCheckResourceAttr("data.github_enterprise_custom_properties.test", "allowed_values.#", "0"), + resource.TestCheckResourceAttr("data.github_enterprise_custom_properties.test", "value_type", "string"), + resource.TestCheckResourceAttr("data.github_enterprise_custom_properties.test", "required", "true"), + resource.TestCheckResourceAttr("data.github_enterprise_custom_properties.test", "default_value", "terraform"), + resource.TestCheckResourceAttr("data.github_enterprise_custom_properties.test", "description", "A test property"), + resource.TestCheckResourceAttr("data.github_enterprise_custom_properties.test", "values_editable_by", "org_and_repo_actors"), + ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessEnterprise(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + }) + }) + + t.Run("creates custom property using allowed_values", func(t *testing.T) { + randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlpha) + propertyName := fmt.Sprintf("test-%s", randomID) + + config := fmt.Sprintf(` + resource "github_enterprise_custom_properties" "test" { + enterprise_slug = "%s" + property_name = "%s" + value_type = "single_select" + required = true + description = "A test property" + values_editable_by = "org_and_repo_actors" + allowed_values = [ "a", "b", "c" ] + } + `, + testAccConf.enterpriseSlug, + propertyName, + ) + + check := resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.github_enterprise_custom_properties.test", "allowed_values.#", "3"), + resource.TestCheckResourceAttr("data.github_enterprise_custom_properties.test", "value_type", "single_select"), + ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { skipUnlessEnterprise(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: config, + Check: check, + }, + }, + }) + }) +} diff --git a/website/docs/d/enterprise_custom_properties.html.markdown b/website/docs/d/enterprise_custom_properties.html.markdown new file mode 100644 index 0000000000..12f8cb82f6 --- /dev/null +++ b/website/docs/d/enterprise_custom_properties.html.markdown @@ -0,0 +1,43 @@ +--- +layout: "github" +page_title: "GitHub: github_enterprise_custom_properties" +description: |- + Get information about a GitHub enterprise custom property +--- + +# github_enterprise_custom_properties + +Use this data source to retrieve information about a GitHub enterprise custom property. + +## Example Usage + +```hcl +data "github_enterprise_custom_properties" "environment" { + enterprise_slug = "yourent" + property_name = "environment" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `enterprise_slug` - (Required) The URL slug identifying the enterprise. + +* `property_name` - (Required) The name of the custom property to retrieve. + +## Attributes Reference + +* `property_name` - The name of the custom property. + +* `value_type` - The type of the custom property. Can be one of `string`, `single_select`, `multi_select`, or `true_false`. + +* `required` - Whether the custom property is required. + +* `description` - The description of the custom property. + +* `default_value` - The default value of the custom property. + +* `allowed_values` - List of allowed values for the custom property. Only populated when `value_type` is `single_select` or `multi_select`. + +* `values_editable_by` - Who can edit the values of the custom property. Can be one of `org_actors` or `org_and_repo_actors`. \ No newline at end of file diff --git a/website/docs/r/enterprise_custom_properties.html.markdown b/website/docs/r/enterprise_custom_properties.html.markdown new file mode 100644 index 0000000000..675aa6abfd --- /dev/null +++ b/website/docs/r/enterprise_custom_properties.html.markdown @@ -0,0 +1,104 @@ +--- +layout: "github" +page_title: "GitHub: github_enterprise_custom_properties" +description: |- + Creates and manages custom properties for a GitHub enterprise +--- + +# github_enterprise_custom_properties + +This resource allows you to create and manage custom properties for a GitHub enterprise. + +Custom properties enable you to add metadata to repositories within your enterprise. You can use custom properties to add context about repositories, such as who owns them, when they expire, or compliance requirements. + +## Example Usage + +```hcl +resource "github_enterprise_custom_properties" "environment" { + enterprise_slug = "yourent" + property_name = "environment" + value_type = "single_select" + required = true + description = "The deployment environment for this repository" + default_value = "development" + allowed_values = [ + "development", + "staging", + "production" + ] +} +``` + +## Example Usage - Allow Repository Actors to Edit + +This example shows how to allow repository administrators to edit the property values: + +```hcl +resource "github_enterprise_custom_properties" "team_contact" { + enterprise_slug = "yourent" + property_name = "team_contact" + value_type = "string" + required = false + description = "Contact information for the team managing this repository" + values_editable_by = "org_and_repo_actors" +} +``` + +## Example Usage - Text Property + +```hcl +resource "github_enterprise_custom_properties" "owner" { + enterprise_slug = "yourent" + property_name = "owner" + value_type = "string" + required = true + description = "The team or individual responsible for this repository" +} +``` + +## Example Usage - Boolean Property + +```hcl +resource "github_enterprise_custom_properties" "archived" { + enterprise_slug = "yourent" + property_name = "archived" + value_type = "true_false" + required = false + description = "Whether this repository is archived" + default_value = "false" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `enterprise_slug` - (Required) The URL slug identifying the enterprise. + +* `property_name` - (Required) The name of the custom property. + +* `value_type` - (Optional) The type of the custom property. Can be one of `string`, `single_select`, `multi_select`, or `true_false`. Defaults to `string`. + +* `required` - (Optional) Whether the custom property is required. Defaults to `false`. + +* `description` - (Optional) The description of the custom property. + +* `default_value` - (Optional) The default value of the custom property. + +* `allowed_values` - (Optional) List of allowed values for the custom property. Only applicable when `value_type` is `single_select` or `multi_select`. + +* `values_editable_by` - (Optional) Who can edit the values of the custom property. Can be one of `org_actors` or `org_and_repo_actors`. When set to `org_actors` (the default), only organization owners can edit the property values on repositories. When set to `org_and_repo_actors`, both organization owners and repository administrators with the custom properties permission can edit the values. + +## Attributes Reference + +In addition to all arguments above, the following attributes are exported: + +* `property_name` - The name of the custom property. + +## Import + +Enterprise custom properties can be imported using the property name: + +``` +terraform import github_enterprise_custom_properties.environment environment +``` \ No newline at end of file