Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add github_actions_enterprise_permissions #2155

Merged
merged 3 commits into from Mar 8, 2024
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions github/provider.go
Expand Up @@ -125,6 +125,7 @@ func Provider() *schema.Provider {
},

ResourcesMap: map[string]*schema.Resource{
"github_enterprise_actions_permissions": resourceGithubActionsEnterprisePermissions(),
"github_actions_environment_secret": resourceGithubActionsEnvironmentSecret(),
"github_actions_environment_variable": resourceGithubActionsEnvironmentVariable(),
"github_actions_organization_oidc_subject_claim_customization_template": resourceGithubActionsOrganizationOIDCSubjectClaimCustomizationTemplate(),
Expand Down
288 changes: 288 additions & 0 deletions github/resource_github_enterprise_actions_permissions.go
@@ -0,0 +1,288 @@
package github

import (
"context"
"errors"

"github.com/google/go-github/v57/github"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

func resourceGithubActionsEnterprisePermissions() *schema.Resource {
return &schema.Resource{
Create: resourceGithubActionsEnterprisePermissionsCreateOrUpdate,
Read: resourceGithubActionsEnterprisePermissionsRead,
Update: resourceGithubActionsEnterprisePermissionsCreateOrUpdate,
Delete: resourceGithubActionsEnterprisePermissionsDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},

Schema: map[string]*schema.Schema{
"enterprise_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "The ID of the enterprise.",
},
"allowed_actions": {
Type: schema.TypeString,
Optional: true,
Description: "The permissions policy that controls the actions that are allowed to run. Can be one of: 'all', 'local_only', or 'selected'.",
ValidateDiagFunc: toDiagFunc(validation.StringInSlice([]string{"all", "local_only", "selected"}, false), "allowed_actions"),
},
"enabled_organizations": {
Type: schema.TypeString,
Required: true,
Description: "The policy that controls the organizations in the enterprise that are allowed to run GitHub Actions. Can be one of: 'all', 'none', or 'selected'.",
ValidateDiagFunc: toDiagFunc(validation.StringInSlice([]string{"all", "none", "selected"}, false), "enabled_organizations"),
},
"allowed_actions_config": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Description: "Sets the actions that are allowed in an enterprise. Only available when 'allowed_actions' = 'selected'",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"github_owned_allowed": {
Type: schema.TypeBool,
Required: true,
Description: "Whether GitHub-owned actions are allowed in the enterprise.",
},
"patterns_allowed": {
Type: schema.TypeSet,
Optional: true,
Description: "Specifies a list of string-matching patterns to allow specific action(s). Wildcards, tags, and SHAs are allowed. For example, 'monalisa/octocat@', 'monalisa/octocat@v2', 'monalisa/'.",
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
},
"verified_allowed": {
Type: schema.TypeBool,
Optional: true,
Description: "Whether actions in GitHub Marketplace from verified creators are allowed. Set to 'true' to allow all GitHub Marketplace actions by verified creators.",
},
},
},
},
"enabled_organizations_config": {
Type: schema.TypeList,
Optional: true,
MaxItems: 1,
Description: "Sets the list of selected organizations that are enabled for GitHub Actions in an enterprise. Only available when 'enabled_organizations' = 'selected'.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"organization_ids": {
Type: schema.TypeSet,
Description: "List of organization IDs to enable for GitHub Actions.",
Elem: &schema.Schema{Type: schema.TypeInt},
Required: true,
},
},
},
},
},
}
}

func resourceGithubActionsEnterpriseAllowedObject(d *schema.ResourceData) (*github.ActionsAllowed, error) {
allowed := &github.ActionsAllowed{}

config := d.Get("allowed_actions_config").([]interface{})
if len(config) > 0 {
data := config[0].(map[string]interface{})
switch x := data["github_owned_allowed"].(type) {
case bool:
allowed.GithubOwnedAllowed = &x
}

switch x := data["verified_allowed"].(type) {
case bool:
allowed.VerifiedAllowed = &x
}

patternsAllowed := []string{}

switch t := data["patterns_allowed"].(type) {
case *schema.Set:
for _, value := range t.List() {
patternsAllowed = append(patternsAllowed, value.(string))
}
}

allowed.PatternsAllowed = patternsAllowed
} else {
return &github.ActionsAllowed{},
errors.New("the allowed_actions_config {} block must be specified if allowed_actions == 'selected'")
}

return allowed, nil
}

func resourceGithubActionsEnabledOrganizationsObject(d *schema.ResourceData) ([]int64, error) {
var enabled []int64

config := d.Get("enabled_organizations_config").([]interface{})
if len(config) > 0 {
data := config[0].(map[string]interface{})
switch x := data["organization_ids"].(type) {
case *schema.Set:
for _, value := range x.List() {
enabled = append(enabled, int64(value.(int)))
}
}
} else {
return nil, errors.New("the enabled_organizations_config {} block must be specified if enabled_organizations == 'selected'")
}
return enabled, nil
}

func resourceGithubActionsEnterprisePermissionsCreateOrUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Owner).v3client

ctx := context.Background()
if !d.IsNewResource() {
ctx = context.WithValue(ctx, ctxId, d.Id())
}

enterpriseId := d.Get("enterprise_id").(string)
allowedActions := d.Get("allowed_actions").(string)
enabledOrganizations := d.Get("enabled_organizations").(string)

_, _, err := client.Actions.EditActionsPermissionsInEnterprise(ctx,
enterpriseId,
github.ActionsPermissionsEnterprise{
AllowedActions: &allowedActions,
EnabledOrganizations: &enabledOrganizations,
})
if err != nil {
return err
}

if allowedActions == "selected" {
actionsAllowedData, err := resourceGithubActionsEnterpriseAllowedObject(d)
if err != nil {
return err
}
_, _, err = client.Actions.EditActionsAllowedInEnterprise(ctx,
enterpriseId,
*actionsAllowedData)
if err != nil {
return err
}
}

if enabledOrganizations == "selected" {
enabledOrgsData, err := resourceGithubActionsEnabledOrganizationsObject(d)
if err != nil {
return err
}
_, err = client.Actions.SetEnabledOrgsInEnterprise(ctx,
enterpriseId,
enabledOrgsData)
if err != nil {
return err
}
}

d.SetId(enterpriseId)
return resourceGithubActionsEnterprisePermissionsRead(d, meta)
}

func resourceGithubActionsEnterprisePermissionsRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Owner).v3client
ctx := context.Background()

actionsPermissions, _, err := client.Actions.GetActionsPermissionsInEnterprise(ctx, d.Id())
if err != nil {
return err
}

if actionsPermissions.GetAllowedActions() == "selected" {
actionsAllowed, _, err := client.Actions.GetActionsAllowedInEnterprise(ctx, d.Id())
if err != nil {
return err
}

// If actionsAllowed set to local/all by removing all actions config settings, the response will be empty
if actionsAllowed != nil {
if err = d.Set("allowed_actions_config", []interface{}{
map[string]interface{}{
"github_owned_allowed": actionsAllowed.GetGithubOwnedAllowed(),
"patterns_allowed": actionsAllowed.PatternsAllowed,
"verified_allowed": actionsAllowed.GetVerifiedAllowed(),
},
}); err != nil {
return err
}
}
} else {
if err = d.Set("allowed_actions_config", []interface{}{}); err != nil {
return err
}
}

if actionsPermissions.GetEnabledOrganizations() == "selected" {
opts := github.ListOptions{PerPage: 10, Page: 1}
var orgList []int64
var allOrgs []*github.Organization

for {
enabledOrgs, resp, err := client.Actions.ListEnabledOrgsInEnterprise(ctx, d.Id(), &opts)
if err != nil {
return err
}
allOrgs = append(allOrgs, enabledOrgs.Organizations...)

opts.Page = resp.NextPage

if resp.NextPage == 0 {
break
}
}
for index := range allOrgs {
orgList = append(orgList, *allOrgs[index].ID)
}
if allOrgs != nil {
if err = d.Set("enabled_organizations_config", []interface{}{
map[string]interface{}{
"organization_ids": orgList,
},
}); err != nil {
return err
}
} else {
if err = d.Set("enabled_organizations_config", []interface{}{}); err != nil {
return err
}
}
}

if err = d.Set("allowed_actions", actionsPermissions.GetAllowedActions()); err != nil {
return err
}
if err = d.Set("enabled_organizations", actionsPermissions.GetEnabledOrganizations()); err != nil {
return err
}

return nil
}

func resourceGithubActionsEnterprisePermissionsDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Owner).v3client

ctx := context.WithValue(context.Background(), ctxId, d.Id())

// This will nullify any allowedActions elements
_, _, err := client.Actions.EditActionsPermissionsInEnterprise(ctx,
d.Get("enterprise_id").(string),
github.ActionsPermissionsEnterprise{
AllowedActions: github.String("all"),
EnabledOrganizations: github.String("all"),
})
if err != nil {
return err
}

return nil
}