Skip to content
Merged
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
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ export GITHUB_OWNER=
# enable testing of enterprise appliances
export GITHUB_BASE_URL=

# enable testing of GitHub Paid features, these normally also require an organization e.g. repository push rulesets
export GITHUB_PAID_FEATURES=true

# leverage helper accounts for tests requiring them
# examples include:
# - https://github.com/github-terraform-test-user
Expand Down
1 change: 1 addition & 0 deletions github/provider_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

var testCollaborator = os.Getenv("GITHUB_TEST_COLLABORATOR")
var isEnterprise = os.Getenv("ENTERPRISE_ACCOUNT")
var isPaidPlan = os.Getenv("GITHUB_PAID_FEATURES")
var testEnterprise = os.Getenv("ENTERPRISE_SLUG")
var testOrganization = testOrganizationFunc()
var testOwner = os.Getenv("GITHUB_OWNER")
Expand Down
57 changes: 55 additions & 2 deletions github/resource_github_repository_ruleset.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ func resourceGithubRepositoryRuleset() *schema.Resource {
"target": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validation.StringInSlice([]string{"branch", "tag"}, false),
Description: "Possible values are `branch` and `tag`.",
ValidateFunc: validation.StringInSlice([]string{"branch", "push", "tag"}, false),
Description: "Possible values are `branch`, `push` and `tag`.",
},
"repository": {
Type: schema.TypeString,
Expand Down Expand Up @@ -509,6 +509,59 @@ func resourceGithubRepositoryRuleset() *schema.Resource {
},
},
},
"file_path_restriction": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Description: "Prevent commits that include changes in specified file paths from being pushed to the commit graph.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"restricted_file_paths": {
Type: schema.TypeList,
MinItems: 1,
Required: true,
Description: "The file paths that are restricted from being pushed to the commit graph.",
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
},
},
"max_file_size": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Description: "Prevent pushes based on file size.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"max_file_size": {
Type: schema.TypeInt,
Required: true,
Description: "The maximum allowed size of a file in bytes.",
},
},
},
},
"file_extension_restriction": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Description: "Prevent pushes based on file extensions.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"restricted_file_extensions": {
Type: schema.TypeSet,
MinItems: 1,
Required: true,
Description: "A list of file extensions.",
Elem: &schema.Schema{
Type: schema.TypeString,
},
},
},
},
},
},
},
},
Expand Down
76 changes: 76 additions & 0 deletions github/resource_github_repository_ruleset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,82 @@ func TestGithubRepositoryRulesets(t *testing.T) {
})

})
t.Run("Creates a push repository ruleset without errors", func(t *testing.T) {
if isPaidPlan != "true" {
t.Skip("Skipping because `GITHUB_PAID_FEATURES` is not set to true")
}
config := fmt.Sprintf(`
resource "github_repository" "test" {
name = "tf-acc-test-%s"
auto_init = false
visibility = "internal"
vulnerability_alerts = true
}

resource "github_repository_ruleset" "test_push" {
name = "test-push"
repository = github_repository.test.id
target = "push"
enforcement = "active"

rules {
file_path_restriction {
restricted_file_paths = ["test.txt"]
}
max_file_size {
max_file_size = 1048576
}
file_extension_restriction {
restricted_file_extensions = ["*.zip"]
}
}
}

`, randomID)
check := resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(
"github_repository_ruleset.test_push", "name",
"test-push",
),
resource.TestCheckResourceAttr(
"github_repository_ruleset.test_push", "target",
"push",
),
resource.TestCheckResourceAttr(
"github_repository_ruleset.test_push", "rules.0.file_path_restriction.0.restricted_file_paths.0",
"test.txt",
),
resource.TestCheckResourceAttr(
"github_repository_ruleset.test_push", "rules.0.max_file_size.0.max_file_size",
"1048576",
),
resource.TestCheckResourceAttr(
"github_repository_ruleset.test_push", "rules.0.file_extension_restriction.0.restricted_file_extensions.0",
"*.zip",
),
)
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 a paid plan in an organization", func(t *testing.T) {
testCase(t, organization)
})
})

t.Run("Creates repository ruleset with merge queue SQUASH method", func(t *testing.T) {

Expand Down
48 changes: 48 additions & 0 deletions github/respository_rules_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,43 @@ func expandRules(input []interface{}, org bool) []*github.RepositoryRule {
rulesSlice = append(rulesSlice, github.NewRequiredCodeScanningRule(params))
}

// file_path_restriction rule
if v, ok := rulesMap["file_path_restriction"].([]interface{}); ok && len(v) != 0 {
filePathRestrictionMap := v[0].(map[string]interface{})
restrictedFilePaths := make([]string, 0)
for _, path := range filePathRestrictionMap["restricted_file_paths"].([]interface{}) {
restrictedFilePaths = append(restrictedFilePaths, path.(string))
}
params := &github.RuleFileParameters{
RestrictedFilePaths: &restrictedFilePaths,
}
rulesSlice = append(rulesSlice, github.NewFilePathRestrictionRule(params))
}

// max_file_size rule
if v, ok := rulesMap["max_file_size"].([]interface{}); ok && len(v) != 0 {
maxFileSizeMap := v[0].(map[string]interface{})
maxFileSize := int64(maxFileSizeMap["max_file_size"].(float64))
params := &github.RuleMaxFileSizeParameters{
MaxFileSize: maxFileSize,
}
rulesSlice = append(rulesSlice, github.NewMaxFileSizeRule(params))

}

// file_extension_restriction rule
if v, ok := rulesMap["file_extension_restriction"].([]interface{}); ok && len(v) != 0 {
fileExtensionRestrictionMap := v[0].(map[string]interface{})
restrictedFileExtensions := make([]string, 0)
for _, extension := range fileExtensionRestrictionMap["restricted_file_extensions"].([]interface{}) {
restrictedFileExtensions = append(restrictedFileExtensions, extension.(string))
}
params := &github.RuleFileExtensionRestrictionParameters{
RestrictedFileExtensions: restrictedFileExtensions,
}
rulesSlice = append(rulesSlice, github.NewFileExtensionRestrictionRule(params))
}

return rulesSlice
}

Expand Down Expand Up @@ -588,6 +625,17 @@ func flattenRules(rules []*github.RepositoryRule, org bool) []interface{} {
rule["min_entries_to_merge"] = params.MinEntriesToMerge
rule["min_entries_to_merge_wait_minutes"] = params.MinEntriesToMergeWaitMinutes
rulesMap[v.Type] = []map[string]interface{}{rule}

case "file_path_restriction":
var params github.RuleFileParameters
err := json.Unmarshal(*v.Parameters, &params)
if err != nil {
log.Printf("[INFO] Unexpected error unmarshalling rule %s with parameters: %v",
v.Type, v.Parameters)
}
rule := make(map[string]interface{})
rule["restricted_file_paths"] = params.GetRestrictedFilePaths()
rulesMap[v.Type] = []map[string]interface{}{rule}
}
}

Expand Down
40 changes: 39 additions & 1 deletion website/docs/r/repository_ruleset.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,28 @@ resource "github_repository_ruleset" "example" {

}
}

# Example with push ruleset
resource "github_repository_ruleset" "example_push" {
name = "example_push"
repository = github_repository.example.name
target = "push"
enforcement = "active"

rules {
file_path_restriction {
restricted_file_paths = [".github/workflows/*", "*.env"]
}

max_file_size {
max_file_size = 104857600 # 100 MB in bytes
}

file_extension_restriction {
restricted_file_extensions = ["*.exe", "*.dll", "*.so"]
}
}
}
```

## Argument Reference
Expand All @@ -62,7 +84,7 @@ resource "github_repository_ruleset" "example" {

* `rules` - (Required) (Block List, Min: 1, Max: 1) Rules within the ruleset. (see [below for nested schema](#rules))

* `target` - (Required) (String) Possible values are `branch` and `tag`.
* `target` - (Required) (String) Possible values are `branch`, `tag` and `push`.

* `bypass_actors` - (Optional) (Block List) The actors that can bypass the rules in this ruleset. (see [below for nested schema](#bypass_actors))

Expand Down Expand Up @@ -104,6 +126,10 @@ The `rules` block supports the following:

* `required_code_scanning` - (Optional) (Block List, Max: 1) Define which tools must provide code scanning results before the reference is updated. When configured, code scanning must be enabled and have results for both the commit and the reference being updated. Multiple code scanning tools can be specified. (see [below for nested schema](#rules.required_code_scanning))

* `file_path_restriction` - (Optional) (Block List, Max 1) Parameters to be used for the file_path_restriction rule. When enabled restricts access to files within the repository. (See [below for nested schema](#rules.file_path_restriction))

* `max_file_size` - (Optional) (Block List, Max 1) Parameters to be used for the max_file_size rule. When enabled restricts the maximum size of a file that can be pushed to the repository. (See [below for nested schema](#rules.max_file_size))
* `file_extension_restriction` - (Optional) (Block List, Max: 1) Prevent commits that include files with specified file extensions from being pushed to the commit graph. This rule only applies to rulesets with target `push`. (see [below for nested schema](#rules.file_extension_restriction))
* `update` - (Optional) (Boolean) Only allow users with bypass permission to update matching refs.

* `update_allows_fetch_and_merge` - (Optional) (Boolean) Branch can pull changes from its upstream repository. This is only applicable to forked repositories. Requires `update` to be set to `true`. Note: behaviour is affected by a known bug on the GitHub side which may cause issues when using this parameter.
Expand Down Expand Up @@ -216,6 +242,18 @@ The `rules` block supports the following:

* `tool` - (Required) (String) The name of a code scanning tool.

#### rules.file_path_restriction ####

* `restricted_file_paths` - (Required) (Block Set, Min: 1) The file paths that are restricted from being pushed to the commit graph.

#### rules.max_file_size ####

* `max_file_size` - (Required) (Integer) The maximum allowed size, in bytes, of a file.

#### rules.file_extension_restriction ####

* `restricted_file_extensions` - (Required) (Block Set, Min: 1) The file extensions that are restricted from being pushed to the commit graph.

#### bypass_actors ####

* `actor_id` - (Required) (Number) The ID of the actor that can bypass a ruleset. If `actor_type` is `Integration`, `actor_id` is a GitHub App ID. App ID can be obtained by following instructions from the [Get an App API docs](https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#get-an-app)
Expand Down
Loading