Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 82 additions & 10 deletions github/resource_github_organization_ruleset.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func resourceGithubOrganizationRuleset() *schema.Resource {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Description: "Parameters for an organization ruleset condition. `ref_name` is required for `branch` and `tag` targets, but must not be set for `push` targets. One of `repository_name` or `repository_id` is always required.",
Description: "Parameters for an organization ruleset condition. `ref_name` is required alongside one of `repository_name`, `repository_id` or `repository_property` for branch and tag rulesets. The push rulesets conditions object does not require the ref_name property.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"ref_name": {
Expand Down Expand Up @@ -124,12 +124,12 @@ func resourceGithubOrganizationRuleset() *schema.Resource {
},
},
"repository_name": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Description: "Targets repositories that match the specified name patterns.",
ExactlyOneOf: []string{"conditions.0.repository_id"},
AtLeastOneOf: []string{"conditions.0.repository_id"},
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Description: "Targets repositories that match the specified name patterns.",
ConflictsWith: []string{"conditions.0.repository_id", "conditions.0.repository_property"},
AtLeastOneOf: []string{"conditions.0.repository_name", "conditions.0.repository_id", "conditions.0.repository_property"},
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"include": {
Expand Down Expand Up @@ -158,13 +158,85 @@ func resourceGithubOrganizationRuleset() *schema.Resource {
},
},
"repository_id": {
Type: schema.TypeList,
Optional: true,
Description: "The repository IDs that the ruleset applies to. One of these IDs must match for the condition to pass.",
Type: schema.TypeList,
Optional: true,
Description: "The repository IDs that the ruleset applies to. One of these IDs must match for the condition to pass.",
ConflictsWith: []string{"conditions.0.repository_name", "conditions.0.repository_property"},
AtLeastOneOf: []string{"conditions.0.repository_name", "conditions.0.repository_id", "conditions.0.repository_property"},
Elem: &schema.Schema{
Type: schema.TypeInt,
},
},
"repository_property": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
ConflictsWith: []string{"conditions.0.repository_name", "conditions.0.repository_id"},
AtLeastOneOf: []string{"conditions.0.repository_name", "conditions.0.repository_id", "conditions.0.repository_property"},
Description: "Condition to target repositories by property.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"include": {
Type: schema.TypeList,
Optional: true,
Description: "The repository properties and values to include. All of these properties must match for the condition to pass.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"property_name": {
Type: schema.TypeString,
Required: true,
Description: "The name of the repository property to target.",
},
"property_value": {
Type: schema.TypeList,
Required: true,
Description: "The values to match for the repository property.",
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"source": {
Type: schema.TypeString,
Optional: true,
Default: "custom",
Description: "The source of the repository property. Defaults to `custom` if not specified. Can be one of: `custom`, `system`.",
ValidateFunc: validation.StringInSlice([]string{"custom", "system"}, false),
},
},
},
},
"exclude": {
Type: schema.TypeList,
Optional: true,
Description: "The repository properties and values to exclude. The condition will not pass if any of these properties match.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"property_name": {
Type: schema.TypeString,
Required: true,
Description: "The name of the repository property to target.",
},
"property_value": {
Type: schema.TypeList,
Required: true,
Description: "The values to match for the repository property.",
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
"source": {
Type: schema.TypeString,
Optional: true,
Default: "custom",
Description: "The source of the repository property. Defaults to `custom` if not specified. Can be one of: `custom`, `system`.",
ValidateFunc: validation.StringInSlice([]string{"custom", "system"}, false),
},
},
},
},
},
},
},
},
},
},
Expand Down
89 changes: 89 additions & 0 deletions github/util_rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,53 @@ func expandConditions(input []any, org bool) *github.RepositoryRulesetConditions
}

rulesetConditions.RepositoryID = &github.RepositoryRulesetRepositoryIDsConditionParameters{RepositoryIDs: repositoryIDs}
} else if v, ok := inputConditions["repository_property"].([]any); ok && v != nil && len(v) != 0 {
repositoryPropertyInput := v[0].(map[string]any)

buildTargets := func(items []any) []*github.RepositoryRulesetRepositoryPropertyTargetParameters {
targets := make([]*github.RepositoryRulesetRepositoryPropertyTargetParameters, 0)
for _, item := range items {
if item == nil {
continue
}
itemMap := item.(map[string]any)

propertyValues := make([]string, 0)
if values, ok := itemMap["property_value"].([]any); ok {
for _, value := range values {
if value != nil {
propertyValues = append(propertyValues, value.(string))
}
}
}

sourceValue := itemMap["source"].(string)

targets = append(targets, &github.RepositoryRulesetRepositoryPropertyTargetParameters{
Name: itemMap["property_name"].(string),
PropertyValues: propertyValues,
Source: &sourceValue,
})
}
return targets
}

includeTargets := make([]*github.RepositoryRulesetRepositoryPropertyTargetParameters, 0)
if includes, ok := repositoryPropertyInput["include"].([]any); ok {
includeTargets = buildTargets(includes)
}

excludeTargets := make([]*github.RepositoryRulesetRepositoryPropertyTargetParameters, 0)
if excludes, ok := repositoryPropertyInput["exclude"].([]any); ok {
excludeTargets = buildTargets(excludes)
}

if len(includeTargets) > 0 || len(excludeTargets) > 0 {
rulesetConditions.RepositoryProperty = &github.RepositoryRulesetRepositoryPropertyConditionParameters{
Include: includeTargets,
Exclude: excludeTargets,
}
}
}
}

Expand Down Expand Up @@ -296,6 +343,48 @@ func flattenConditions(ctx context.Context, conditions *github.RepositoryRuleset
if conditions.RepositoryID != nil {
conditionsMap["repository_id"] = conditions.RepositoryID.RepositoryIDs
}

if conditions.RepositoryProperty != nil {
repositoryPropertyMap := make(map[string]any)

// Flatten include
includeSlice := make([]map[string]any, 0)
for _, item := range conditions.RepositoryProperty.Include {
if item != nil {
itemMap := map[string]any{
"property_name": item.Name,
"property_value": item.PropertyValues,
}
if item.Source != nil {
itemMap["source"] = *item.Source
} else {
itemMap["source"] = "custom"
}
includeSlice = append(includeSlice, itemMap)
}
}
repositoryPropertyMap["include"] = includeSlice

// Flatten exclude
excludeSlice := make([]map[string]any, 0)
for _, item := range conditions.RepositoryProperty.Exclude {
if item != nil {
itemMap := map[string]any{
"property_name": item.Name,
"property_value": item.PropertyValues,
}
if item.Source != nil {
itemMap["source"] = *item.Source
} else {
itemMap["source"] = "custom"
}
excludeSlice = append(excludeSlice, itemMap)
}
}
repositoryPropertyMap["exclude"] = excludeSlice

conditionsMap["repository_property"] = []any{repositoryPropertyMap}
}
}

return []any{conditionsMap}
Expand Down
57 changes: 54 additions & 3 deletions website/docs/r/organization_ruleset.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,42 @@ resource "github_organization_ruleset" "example_push" {
}
```

## Example Usage - Targeting Repositories by Custom Property

This example creates a ruleset that targets all repositories with a specific custom property value, using `repository_property` conditions instead of `repository_name`:

```hcl
resource "github_organization_ruleset" "example_property_condition" {
name = "production-repos"
target = "branch"
enforcement = "active"

conditions {
ref_name {
include = ["~DEFAULT_BRANCH"]
exclude = []
}

repository_property {
include {
property_name = "environment"
property_value = ["production"]
}
}
}

rules {
required_signatures = true
required_linear_history = true

pull_request {
required_approving_review_count = 2
require_code_owner_review = true
}
}
}
```

## Argument Reference

- `enforcement` - (Required) (String) Possible values for Enforcement are `disabled`, `active`, `evaluate`. Note: `evaluate` is currently only supported for owners of type `organization`.
Expand Down Expand Up @@ -325,10 +361,11 @@ The `rules` block supports the following:
#### conditions ####

- `ref_name` - (Optional) (Block List, Max: 1) Required for `branch` and `tag` targets. Must NOT be set for `push` targets. (see [below for nested schema](#conditionsref_name))
- `repository_id` (Optional) (List of Number) The repository IDs that the ruleset applies to. One of these IDs must match for the condition to pass. Conflicts with `repository_name`.
- `repository_name` (Optional) (Block List, Max: 1) Conflicts with `repository_id`. (see [below for nested schema](#conditionsrepository_name))
- `repository_id` (Optional) (List of Number) The repository IDs that the ruleset applies to. One of these IDs must match for the condition to pass. Conflicts with `repository_name` and `repository_property`.
- `repository_name` (Optional) (Block List, Max: 1) Conflicts with `repository_id` and `repository_property`. (see [below for nested schema](#conditionsrepository_name))
- `repository_property` (Optional) (Block List, Max: 1) Target repositories based on their custom property values. Conflicts with `repository_id` and `repository_name`. (see [below for nested schema](#conditionsrepository_property))

One of `repository_id` and `repository_name` must be set for the rule to target any repositories.
Exactly one of `repository_id`, `repository_name`, or `repository_property` must be set for the rule to target any repositories.

~> **Note:** For `push` targets, do not include `ref_name` in conditions. Push rulesets operate on file content, not on refs.

Expand All @@ -344,6 +381,20 @@ One of `repository_id` and `repository_name` must be set for the rule to target
- `include` - (Required) (List of String) Array of repository names or patterns to include. One of these patterns must match for the condition to pass. Also accepts `~ALL` to include all repositories.
- `protected` - (Optional) (Boolean) Whether renaming of target repositories is prevented. Defaults to `false`.

#### conditions.repository_property ####

- `include` - (Optional) (Block List) The repository properties and values to include. All of these properties must match for the condition to pass. (see [below for nested schema](#conditionsrepository_propertyincludeexclude))

- `exclude` - (Optional) (Block List) The repository properties and values to exclude. If any of these properties match, the condition will not pass. (see [below for nested schema](#conditionsrepository_propertyincludeexclude))

#### conditions.repository_property.include/exclude ####

- `property_name` - (Required) (String) The name of the repository property to target.

- `property_value` - (Required) (List of String) The values to match for the repository property.

- `source` - (Optional) (String) The source of the repository property. Can be one of: `custom`, `system`. Defaults to `custom`.

## Attributes Reference

The following additional attributes are exported:
Expand Down