Skip to content
Open
1 change: 1 addition & 0 deletions github/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ func Provider() *schema.Provider {
"github_repository_ruleset": resourceGithubRepositoryRuleset(),
"github_repository_topics": resourceGithubRepositoryTopics(),
"github_repository_webhook": resourceGithubRepositoryWebhook(),
"github_repository_vulnerability_alerts": resourceGithubRepositoryVulnerabilityAlerts(),
"github_team": resourceGithubTeam(),
"github_team_members": resourceGithubTeamMembers(),
"github_team_membership": resourceGithubTeamMembership(),
Expand Down
3 changes: 2 additions & 1 deletion github/resource_github_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -400,12 +400,13 @@ func resourceGithubRepository() *schema.Resource {
Optional: true,
Computed: true,
Description: "Set to 'true' to enable security alerts for vulnerable dependencies. Enabling requires alerts to be enabled on the owner level. (Note for importing: GitHub enables the alerts on all repos by default). Note that vulnerability alerts have not been successfully tested on any GitHub Enterprise instance and may be unavailable in those settings.",
Deprecated: "Use the github_repository_vulnerability_alerts resource instead. This field will be removed in a future version.",
},
"ignore_vulnerability_alerts_during_read": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Deprecated: "This is ignored as the provider now handles lack of permissions automatically.",
Deprecated: "This is ignored as the provider now handles lack of permissions automatically. This field will be removed in a future version.",
},
"full_name": {
Type: schema.TypeString,
Expand Down
181 changes: 181 additions & 0 deletions github/resource_github_repository_vulnerability_alerts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package github

import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
"strings"

"github.com/google/go-github/v83/github"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourceGithubRepositoryVulnerabilityAlerts() *schema.Resource {
return &schema.Resource{
CreateContext: resourceGithubRepositoryVulnerabilityAlertsCreate,
ReadContext: resourceGithubRepositoryVulnerabilityAlertsRead,
UpdateContext: resourceGithubRepositoryVulnerabilityAlertsUpdate,
DeleteContext: resourceGithubRepositoryVulnerabilityAlertsDelete,
Importer: &schema.ResourceImporter{
StateContext: resourceGithubRepositoryVulnerabilityAlertsImport,
},

Schema: map[string]*schema.Schema{
"repository": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "The repository name to configure vulnerability alerts for.",
},
"repository_id": {
Type: schema.TypeInt,
Computed: true,
Description: "The ID of the repository to configure vulnerability alerts for.",
},
// TODO: Uncomment this when we are ready to support owner fields properly. https://github.com/integrations/terraform-provider-github/pull/3166#discussion_r2816053082
// "owner": {
// Type: schema.TypeString,
// Required: true,
// ForceNew: true,
// Description: "The owner of the repository to configure vulnerability alerts for.",
// },
"enabled": {
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "Whether vulnerability alerts are enabled for the repository.",
},
},

CustomizeDiff: diffRepository,
}
}

func resourceGithubRepositoryVulnerabilityAlertsCreate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
meta := m.(*Owner)
client := meta.v3client

owner := meta.name // TODO: Add owner support // d.Get("owner").(string)
repoName := d.Get("repository").(string)

vulnerabilityAlertsEnabled := d.Get("enabled").(bool)
if vulnerabilityAlertsEnabled {
resp, err := client.Repositories.EnableVulnerabilityAlerts(ctx, owner, repoName)
if err != nil {
return diag.FromErr(handleVulnerabilityAlertsErrorOnArchivedRepository(err, resp, repoName))
}
} else {
resp, err := client.Repositories.DisableVulnerabilityAlerts(ctx, owner, repoName)
if err != nil {
return diag.FromErr(handleVulnerabilityAlertsErrorOnArchivedRepository(err, resp, repoName))
}
}
repo, _, err := client.Repositories.Get(ctx, owner, repoName)
if err != nil {
return diag.FromErr(err)
}

d.SetId(strconv.Itoa(int(repo.GetID())))

if err = d.Set("repository_id", repo.GetID()); err != nil {
return diag.FromErr(err)
}

return nil
}

func resourceGithubRepositoryVulnerabilityAlertsRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
meta := m.(*Owner)
client := meta.v3client

owner := meta.name // TODO: Add owner support // d.Get("owner").(string)
repoName := d.Get("repository").(string)
vulnerabilityAlertsEnabled, _, err := client.Repositories.GetVulnerabilityAlerts(ctx, owner, repoName)
if err != nil {
return diag.Errorf("error reading repository vulnerability alerts: %s", err.Error())
}
if err = d.Set("enabled", vulnerabilityAlertsEnabled); err != nil {
return diag.FromErr(err)
}

return nil
}

func resourceGithubRepositoryVulnerabilityAlertsUpdate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
meta := m.(*Owner)
client := meta.v3client

owner := meta.name // TODO: Add owner support // d.Get("owner").(string)
repoName := d.Get("repository").(string)

vulnerabilityAlertsEnabled := d.Get("enabled").(bool)
if vulnerabilityAlertsEnabled {
_, err := client.Repositories.EnableVulnerabilityAlerts(ctx, owner, repoName)
if err != nil {
return diag.FromErr(err)
}
} else {
_, err := client.Repositories.DisableVulnerabilityAlerts(ctx, owner, repoName)
if err != nil {
return diag.FromErr(err)
}
}

return nil
}

func resourceGithubRepositoryVulnerabilityAlertsDelete(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
meta := m.(*Owner)
client := meta.v3client

owner := meta.name // TODO: Add owner support // d.Get("owner").(string)
repoName := d.Get("repository").(string)
_, err := client.Repositories.DisableVulnerabilityAlerts(ctx, owner, repoName)
if err != nil {
return diag.FromErr(handleArchivedRepoDelete(err, "repository vulnerability alerts", d.Id(), owner, repoName))
}

return nil
}

func resourceGithubRepositoryVulnerabilityAlertsImport(ctx context.Context, d *schema.ResourceData, m any) ([]*schema.ResourceData, error) {
tflog.Debug(ctx, "Importing repository vulnerability alerts", map[string]any{"id": d.Id()})
repoName := d.Id()
// if err := d.Set("owner", repoOwner); err != nil { // TODO: Add owner support
// return nil, err
// }
if err := d.Set("repository", repoName); err != nil {
return nil, err
}

meta := m.(*Owner)
owner := meta.name
client := meta.v3client

repo, _, err := client.Repositories.Get(ctx, owner, repoName)
if err != nil {
return nil, err
}

d.SetId(strconv.Itoa(int(repo.GetID())))

if err = d.Set("repository_id", repo.GetID()); err != nil {
return nil, err
}
return []*schema.ResourceData{d}, nil
}

func handleVulnerabilityAlertsErrorOnArchivedRepository(err error, resp *github.Response, repoName string) error {
var ghErr *github.ErrorResponse
if errors.As(err, &ghErr) {
// Error response when trying to enable vulnerability alerts on an archived repository. "422 Failed to change dependabot alerts status"
if resp.StatusCode == http.StatusUnprocessableEntity && strings.Contains(strings.ToLower(ghErr.Message), "failed to change dependabot alerts status") {
return fmt.Errorf("failed to change vulnerability alerts for repository %s. The repository is most likely archived: %s", repoName, ghErr.Message)
}
}
return err
}
Loading