Skip to content

Commit dddd42b

Browse files
authored
[BUG] Ensure repository creation works if vulnerability-alerts isn't modifiable on repo level (#3024)
* `github_repository`: Convert to use Context-aware functions Signed-off-by: Timo Sand <[email protected]> * Add tests to ensure `vulnerability_alerts` behaviour keeps working Signed-off-by: Timo Sand <[email protected]> * Add error handling to ignore 422 response In the case of `vulnerability_alerts = false` and the message indicates control from parent Org/Enterprise Signed-off-by: Timo Sand <[email protected]> * Improve descriptions to make interactions slightly clearer Signed-off-by: Timo Sand <[email protected]> * Add comment to describe this as a temporary workaround Signed-off-by: Timo Sand <[email protected]> --------- Signed-off-by: Timo Sand <[email protected]>
1 parent 63076f7 commit dddd42b

2 files changed

Lines changed: 228 additions & 51 deletions

File tree

github/resource_github_repository.go

Lines changed: 62 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,20 @@ import (
1010
"strings"
1111

1212
"github.com/google/go-github/v67/github"
13+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
1314
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
1415
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1516
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
1617
)
1718

1819
func resourceGithubRepository() *schema.Resource {
1920
return &schema.Resource{
20-
Create: resourceGithubRepositoryCreate,
21-
Read: resourceGithubRepositoryRead,
22-
Update: resourceGithubRepositoryUpdate,
23-
Delete: resourceGithubRepositoryDelete,
21+
CreateContext: resourceGithubRepositoryCreate,
22+
ReadContext: resourceGithubRepositoryRead,
23+
UpdateContext: resourceGithubRepositoryUpdate,
24+
DeleteContext: resourceGithubRepositoryDelete,
2425
Importer: &schema.ResourceImporter{
25-
State: func(d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) {
26+
StateContext: func(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) {
2627
if err := d.Set("auto_init", false); err != nil {
2728
return nil, err
2829
}
@@ -390,7 +391,7 @@ func resourceGithubRepository() *schema.Resource {
390391
Type: schema.TypeBool,
391392
Optional: true,
392393
Computed: true,
393-
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 public repos but disables them on private repos by default). Note that vulnerability alerts have not been successfully tested on any GitHub Enterprise instance and may be unavailable in those settings.",
394+
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.",
394395
},
395396
"ignore_vulnerability_alerts_during_read": {
396397
Type: schema.TypeBool,
@@ -620,18 +621,17 @@ func resourceGithubRepositoryObject(d *schema.ResourceData) *github.Repository {
620621
return repository
621622
}
622623

623-
func resourceGithubRepositoryCreate(d *schema.ResourceData, meta any) error {
624+
func resourceGithubRepositoryCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
624625
client := meta.(*Owner).v3client
625626

626627
if branchName, hasDefaultBranch := d.GetOk("default_branch"); hasDefaultBranch && (branchName != "main") {
627-
return fmt.Errorf("cannot set the default branch on a new repository to something other than 'main'")
628+
return diag.Errorf("cannot set the default branch on a new repository to something other than 'main'")
628629
}
629630

630631
repoReq := resourceGithubRepositoryObject(d)
631632
owner := meta.(*Owner).name
632633

633634
repoName := repoReq.GetName()
634-
ctx := context.Background()
635635

636636
// determine if repository should be private. assume public to start
637637
isPrivate := false
@@ -657,7 +657,7 @@ func resourceGithubRepositoryCreate(d *schema.ResourceData, meta any) error {
657657
for _, templateConfigBlock := range templateConfigBlocks {
658658
templateConfigMap, ok := templateConfigBlock.(map[string]any)
659659
if !ok {
660-
return errors.New("failed to unpack template configuration block")
660+
return diag.FromErr(errors.New("failed to unpack template configuration block"))
661661
}
662662

663663
templateRepo := templateConfigMap["repository"].(string)
@@ -678,7 +678,7 @@ func resourceGithubRepositoryCreate(d *schema.ResourceData, meta any) error {
678678
&templateRepoReq,
679679
)
680680
if err != nil {
681-
return err
681+
return diag.FromErr(err)
682682
}
683683

684684
d.SetId(*repo.Name)
@@ -691,7 +691,7 @@ func resourceGithubRepositoryCreate(d *schema.ResourceData, meta any) error {
691691
log.Printf("[INFO] Creating fork of %s/%s in %s", sourceOwner, sourceRepo, owner)
692692

693693
if sourceOwner == "" || sourceRepo == "" {
694-
return fmt.Errorf("source_owner and source_repo must be provided when forking a repository")
694+
return diag.Errorf("source_owner and source_repo must be provided when forking a repository")
695695
}
696696

697697
// Create the fork using the GitHub client library
@@ -712,18 +712,18 @@ func resourceGithubRepositoryCreate(d *schema.ResourceData, meta any) error {
712712
log.Printf("[INFO] Fork is being created asynchronously")
713713
// Despite the 202 status, the API should still return preliminary fork information
714714
if fork == nil {
715-
return fmt.Errorf("fork information not available after accepted status")
715+
return diag.Errorf("fork information not available after accepted status")
716716
}
717717
log.Printf("[DEBUG] Fork name: %s", fork.GetName())
718718
} else {
719-
return fmt.Errorf("failed to create fork: %w", err)
719+
return diag.Errorf("failed to create fork: %s", err.Error())
720720
}
721721
} else if resp != nil {
722722
log.Printf("[DEBUG] Fork response status: %d", resp.StatusCode)
723723
}
724724

725725
if fork == nil {
726-
return fmt.Errorf("fork creation failed - no repository returned")
726+
return diag.Errorf("fork creation failed - no repository returned")
727727
}
728728

729729
log.Printf("[INFO] Fork created with name: %s", fork.GetName())
@@ -747,7 +747,7 @@ func resourceGithubRepositoryCreate(d *schema.ResourceData, meta any) error {
747747
repo, _, err = client.Repositories.Create(ctx, "", repoReq)
748748
}
749749
if err != nil {
750-
return err
750+
return diag.FromErr(err)
751751
}
752752
d.SetId(repo.GetName())
753753
}
@@ -756,27 +756,27 @@ func resourceGithubRepositoryCreate(d *schema.ResourceData, meta any) error {
756756
if len(topics) > 0 {
757757
_, _, err := client.Repositories.ReplaceAllTopics(ctx, owner, repoName, topics)
758758
if err != nil {
759-
return err
759+
return diag.FromErr(err)
760760
}
761761
}
762762

763763
pages := expandPages(d.Get("pages").([]any))
764764
if pages != nil {
765765
_, _, err := client.Repositories.EnablePages(ctx, owner, repoName, pages)
766766
if err != nil {
767-
return err
767+
return diag.FromErr(err)
768768
}
769769
}
770770

771771
err := updateVulnerabilityAlerts(d, client, ctx, owner, repoName)
772772
if err != nil {
773-
return err
773+
return diag.FromErr(err)
774774
}
775775

776-
return resourceGithubRepositoryUpdate(d, meta)
776+
return resourceGithubRepositoryUpdate(ctx, d, meta)
777777
}
778778

779-
func resourceGithubRepositoryRead(d *schema.ResourceData, meta any) error {
779+
func resourceGithubRepositoryRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
780780
client := meta.(*Owner).v3client
781781

782782
owner := meta.(*Owner).name
@@ -788,7 +788,7 @@ func resourceGithubRepositoryRead(d *schema.ResourceData, meta any) error {
788788
owner = explicitOwner
789789
}
790790

791-
ctx := context.WithValue(context.Background(), ctxId, d.Id())
791+
ctx = context.WithValue(ctx, ctxId, d.Id())
792792
if !d.IsNewResource() {
793793
ctx = context.WithValue(ctx, ctxEtag, d.Get("etag").(string))
794794
}
@@ -807,7 +807,7 @@ func resourceGithubRepositoryRead(d *schema.ResourceData, meta any) error {
807807
return nil
808808
}
809809
}
810-
return err
810+
return diag.FromErr(err)
811811
}
812812

813813
_ = d.Set("etag", resp.Header.Get("ETag"))
@@ -853,10 +853,10 @@ func resourceGithubRepositoryRead(d *schema.ResourceData, meta any) error {
853853
if repo.GetHasPages() {
854854
pages, _, err := client.Repositories.GetPagesInfo(ctx, owner, repoName)
855855
if err != nil {
856-
return err
856+
return diag.FromErr(err)
857857
}
858858
if err := d.Set("pages", flattenPages(pages)); err != nil {
859-
return fmt.Errorf("error setting pages: %w", err)
859+
return diag.Errorf("error setting pages: %s", err.Error())
860860
}
861861
}
862862

@@ -882,32 +882,32 @@ func resourceGithubRepositoryRead(d *schema.ResourceData, meta any) error {
882882
"repository": repo.TemplateRepository.Name,
883883
},
884884
}); err != nil {
885-
return err
885+
return diag.FromErr(err)
886886
}
887887
} else {
888888
if err = d.Set("template", []any{}); err != nil {
889-
return err
889+
return diag.FromErr(err)
890890
}
891891
}
892892

893893
if !d.Get("ignore_vulnerability_alerts_during_read").(bool) {
894894
vulnerabilityAlerts, _, err := client.Repositories.GetVulnerabilityAlerts(ctx, owner, repoName)
895895
if err != nil {
896-
return fmt.Errorf("error reading repository vulnerability alerts: %w", err)
896+
return diag.Errorf("error reading repository vulnerability alerts: %s", err.Error())
897897
}
898898
if err = d.Set("vulnerability_alerts", vulnerabilityAlerts); err != nil {
899-
return err
899+
return diag.FromErr(err)
900900
}
901901
}
902902

903903
if err = d.Set("security_and_analysis", flattenSecurityAndAnalysis(repo.GetSecurityAndAnalysis())); err != nil {
904-
return err
904+
return diag.FromErr(err)
905905
}
906906

907907
return nil
908908
}
909909

910-
func resourceGithubRepositoryUpdate(d *schema.ResourceData, meta any) error {
910+
func resourceGithubRepositoryUpdate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
911911
// Can only update a repository if it is not archived or the update is to
912912
// archive the repository (unarchiving is not supported by the GitHub API)
913913
if d.Get("archived").(bool) && !d.HasChange("archived") {
@@ -937,7 +937,7 @@ func resourceGithubRepositoryUpdate(d *schema.ResourceData, meta any) error {
937937

938938
repoName := d.Id()
939939
owner := meta.(*Owner).name
940-
ctx := context.WithValue(context.Background(), ctxId, d.Id())
940+
ctx = context.WithValue(ctx, ctxId, d.Id())
941941

942942
// When the organization has "Require sign off on web-based commits" enabled,
943943
// the API doesn't allow you to send `web_commit_signoff_required` in order to
@@ -955,7 +955,7 @@ func resourceGithubRepositoryUpdate(d *schema.ResourceData, meta any) error {
955955

956956
repo, _, err := client.Repositories.Edit(ctx, owner, repoName, repoReq)
957957
if err != nil {
958-
return err
958+
return diag.FromErr(err)
959959
}
960960
d.SetId(*repo.Name)
961961

@@ -964,7 +964,7 @@ func resourceGithubRepositoryUpdate(d *schema.ResourceData, meta any) error {
964964
if opts != nil {
965965
pages, res, err := client.Repositories.GetPagesInfo(ctx, owner, repoName)
966966
if res.StatusCode != http.StatusNotFound && err != nil {
967-
return err
967+
return diag.FromErr(err)
968968
}
969969

970970
if pages == nil {
@@ -973,12 +973,12 @@ func resourceGithubRepositoryUpdate(d *schema.ResourceData, meta any) error {
973973
_, err = client.Repositories.UpdatePages(ctx, owner, repoName, opts)
974974
}
975975
if err != nil {
976-
return err
976+
return diag.FromErr(err)
977977
}
978978
} else {
979979
_, err := client.Repositories.DisablePages(ctx, owner, repoName)
980980
if err != nil {
981-
return err
981+
return diag.FromErr(err)
982982
}
983983
}
984984
}
@@ -987,23 +987,23 @@ func resourceGithubRepositoryUpdate(d *schema.ResourceData, meta any) error {
987987
topics := repoReq.Topics
988988
_, _, err = client.Repositories.ReplaceAllTopics(ctx, owner, *repo.Name, topics)
989989
if err != nil {
990-
return err
990+
return diag.FromErr(err)
991991
}
992992
d.SetId(*repo.Name)
993993

994994
if d.HasChange("topics") {
995995
topics := repoReq.Topics
996996
_, _, err = client.Repositories.ReplaceAllTopics(ctx, owner, *repo.Name, topics)
997997
if err != nil {
998-
return err
998+
return diag.FromErr(err)
999999
}
10001000
}
10011001
}
10021002

10031003
if d.HasChange("vulnerability_alerts") {
10041004
err = updateVulnerabilityAlerts(d, client, ctx, owner, repoName)
10051005
if err != nil {
1006-
return err
1006+
return diag.FromErr(err)
10071007
}
10081008
}
10091009

@@ -1014,7 +1014,7 @@ func resourceGithubRepositoryUpdate(d *schema.ResourceData, meta any) error {
10141014
_, resp, err := client.Repositories.Edit(ctx, owner, repoName, repoReq)
10151015
if err != nil {
10161016
if resp.StatusCode != 422 || !strings.Contains(err.Error(), fmt.Sprintf("Visibility is already %s", n.(string))) {
1017-
return err
1017+
return diag.FromErr(err)
10181018
}
10191019
}
10201020
} else {
@@ -1028,21 +1028,21 @@ func resourceGithubRepositoryUpdate(d *schema.ResourceData, meta any) error {
10281028
_, _, err = client.Repositories.Edit(ctx, owner, repoName, repoReq)
10291029
if err != nil {
10301030
if !strings.Contains(err.Error(), "422 Privacy is already set") {
1031-
return err
1031+
return diag.FromErr(err)
10321032
}
10331033
}
10341034
} else {
10351035
log.Printf("[DEBUG] No privacy update required. private: %v", d.Get("private"))
10361036
}
10371037

1038-
return resourceGithubRepositoryRead(d, meta)
1038+
return resourceGithubRepositoryRead(ctx, d, meta)
10391039
}
10401040

1041-
func resourceGithubRepositoryDelete(d *schema.ResourceData, meta any) error {
1041+
func resourceGithubRepositoryDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
10421042
client := meta.(*Owner).v3client
10431043
repoName := d.Id()
10441044
owner := meta.(*Owner).name
1045-
ctx := context.WithValue(context.Background(), ctxId, d.Id())
1045+
ctx = context.WithValue(ctx, ctxId, d.Id())
10461046

10471047
archiveOnDestroy := d.Get("archive_on_destroy").(bool)
10481048
if archiveOnDestroy {
@@ -1051,20 +1051,20 @@ func resourceGithubRepositoryDelete(d *schema.ResourceData, meta any) error {
10511051
return nil
10521052
} else {
10531053
if err := d.Set("archived", true); err != nil {
1054-
return err
1054+
return diag.FromErr(err)
10551055
}
10561056
repoReq := resourceGithubRepositoryObject(d)
10571057
// Always remove `web_commit_signoff_required` when archiving, to avoid 422 error
10581058
repoReq.WebCommitSignoffRequired = nil
10591059
log.Printf("[DEBUG] Archiving repository on delete: %s/%s", owner, repoName)
10601060
_, _, err := client.Repositories.Edit(ctx, owner, repoName, repoReq)
1061-
return err
1061+
return diag.FromErr(err)
10621062
}
10631063
}
10641064

10651065
log.Printf("[DEBUG] Deleting repository: %s/%s", owner, repoName)
10661066
_, err := client.Repositories.Delete(ctx, owner, repoName)
1067-
return err
1067+
return diag.FromErr(err)
10681068
}
10691069

10701070
func expandPages(input []any) *github.Pages {
@@ -1243,11 +1243,22 @@ func resourceGithubParseFullName(resourceDataLike interface {
12431243
}
12441244

12451245
func updateVulnerabilityAlerts(d *schema.ResourceData, client *github.Client, ctx context.Context, owner, repoName string) error {
1246-
updateVulnerabilityAlerts := client.Repositories.DisableVulnerabilityAlerts
1247-
if vulnerabilityAlerts, ok := d.GetOk("vulnerability_alerts"); ok && vulnerabilityAlerts.(bool) {
1248-
updateVulnerabilityAlerts = client.Repositories.EnableVulnerabilityAlerts
1246+
updateVulnerabilityAlertsSDK := client.Repositories.DisableVulnerabilityAlerts
1247+
vulnerabilityAlerts, ok := d.GetOk("vulnerability_alerts")
1248+
1249+
// Only if the vulnerability alerts are specifically set to true, enable them.
1250+
// Otherwise, disable them as GitHub defaults to enabled and we have not wanted to introduce a breaking change for this yet.
1251+
if ok && vulnerabilityAlerts.(bool) {
1252+
updateVulnerabilityAlertsSDK = client.Repositories.EnableVulnerabilityAlerts
12491253
}
12501254

1251-
_, err := updateVulnerabilityAlerts(ctx, owner, repoName)
1255+
resp, err := updateVulnerabilityAlertsSDK(ctx, owner, repoName)
1256+
if err != nil {
1257+
// Check if the error is because an Organization or Enterprise policy is preventing the change
1258+
// This is a temporary workaround while we extract Vulnerability Alerts into a separate resource.
1259+
if resp.StatusCode == http.StatusUnprocessableEntity && strings.Contains(err.Error(), "An enforced security configuration prevented modifying") && !ok {
1260+
return nil
1261+
}
1262+
}
12521263
return err
12531264
}

0 commit comments

Comments
 (0)