Skip to content

Commit c9fbaa9

Browse files
committed
Refactor to no longer call CRUD methods directly
Signed-off-by: Timo Sand <[email protected]>
1 parent 24a7373 commit c9fbaa9

1 file changed

Lines changed: 175 additions & 106 deletions

File tree

github/resource_github_repository.go

Lines changed: 175 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -740,14 +740,6 @@ func resourceGithubRepositoryCreate(ctx context.Context, d *schema.ResourceData,
740740

741741
tflog.Info(ctx, "Fork created", map[string]any{"name": fork.GetName()})
742742
d.SetId(fork.GetName())
743-
tflog.Debug(ctx, "Set resource ID to just the name", map[string]any{"resource_id": d.Id()})
744-
745-
_ = d.Set("name", fork.GetName())
746-
_ = d.Set("full_name", fork.GetFullName()) // Add the full name for reference
747-
_ = d.Set("html_url", fork.GetHTMLURL())
748-
_ = d.Set("ssh_clone_url", fork.GetSSHURL())
749-
_ = d.Set("git_clone_url", fork.GetGitURL())
750-
_ = d.Set("http_clone_url", fork.GetCloneURL())
751743
} else {
752744
// Create without a repository template
753745
var repo *github.Repository
@@ -780,7 +772,56 @@ func resourceGithubRepositoryCreate(ctx context.Context, d *schema.ResourceData,
780772
}
781773
}
782774

783-
return resourceGithubRepositoryUpdate(ctx, d, meta)
775+
// handle visibility updates separately from other fields
776+
visibility := repoReq.GetVisibility()
777+
repoReq.Visibility = nil
778+
779+
// This change needs to be made with the correct visibility
780+
allowForking := repoReq.AllowForking
781+
if d.HasChanges("visibility", "private") {
782+
repoReq.AllowForking = nil
783+
}
784+
785+
if !d.HasChange("security_and_analysis") {
786+
repoReq.SecurityAndAnalysis = nil
787+
}
788+
789+
_, _, err := client.Repositories.Edit(ctx, owner, repoName, repoReq)
790+
if err != nil {
791+
return diag.FromErr(err)
792+
}
793+
794+
if v, ok := d.GetOkExists("vulnerability_alerts"); ok { //nolint:staticcheck // SA1019 // We sometimes need to use GetOkExists for booleans
795+
if val, ok := v.(bool); ok {
796+
if err := updateVulnerabilityAlerts(ctx, client, owner, repoName, val); err != nil {
797+
return diag.FromErr(err)
798+
}
799+
}
800+
}
801+
802+
if d.HasChanges("visibility", "private") {
803+
repoReq.Visibility = new(visibility)
804+
repoReq.AllowForking = allowForking
805+
806+
_, resp, err := client.Repositories.Edit(ctx, owner, repoName, repoReq)
807+
if err != nil {
808+
if resp.StatusCode != 422 || !strings.Contains(err.Error(), fmt.Sprintf("Visibility is already %s", visibility)) {
809+
return diag.FromErr(err)
810+
}
811+
}
812+
}
813+
814+
// Fetch final repo state after all mutations and set Computed fields
815+
finalRepo, _, err := client.Repositories.Get(ctx, owner, d.Id())
816+
if err != nil {
817+
return diag.FromErr(err)
818+
}
819+
820+
if diags := setRepositoryFieldsFromRepo(ctx, d, client, owner, finalRepo); diags.HasError() {
821+
return diags
822+
}
823+
824+
return nil
784825
}
785826

786827
func resourceGithubRepositoryRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
@@ -795,7 +836,6 @@ func resourceGithubRepositoryRead(ctx context.Context, d *schema.ResourceData, m
795836
owner = explicitOwner
796837
}
797838

798-
ctx = context.WithValue(ctx, ctxId, d.Id())
799839
if !d.IsNewResource() {
800840
ctx = context.WithValue(ctx, ctxEtag, d.Get("etag").(string))
801841
}
@@ -816,100 +856,12 @@ func resourceGithubRepositoryRead(ctx context.Context, d *schema.ResourceData, m
816856
return diag.FromErr(err)
817857
}
818858

819-
_ = d.Set("etag", resp.Header.Get("ETag"))
820-
_ = d.Set("name", repoName)
821-
_ = d.Set("description", repo.GetDescription())
822-
_ = d.Set("primary_language", repo.GetLanguage())
823-
_ = d.Set("homepage_url", repo.GetHomepage())
824-
_ = d.Set("private", repo.GetPrivate())
825-
_ = d.Set("visibility", repo.GetVisibility())
826-
_ = d.Set("has_issues", repo.GetHasIssues())
827-
_ = d.Set("has_discussions", repo.GetHasDiscussions())
828-
_ = d.Set("has_projects", repo.GetHasProjects())
829-
_ = d.Set("has_wiki", repo.GetHasWiki())
830-
_ = d.Set("is_template", repo.GetIsTemplate())
831-
_ = d.Set("full_name", repo.GetFullName())
832-
_ = d.Set("default_branch", repo.GetDefaultBranch())
833-
_ = d.Set("html_url", repo.GetHTMLURL())
834-
_ = d.Set("ssh_clone_url", repo.GetSSHURL())
835-
_ = d.Set("svn_url", repo.GetSVNURL())
836-
_ = d.Set("git_clone_url", repo.GetGitURL())
837-
_ = d.Set("http_clone_url", repo.GetCloneURL())
838-
_ = d.Set("archived", repo.GetArchived())
839-
_ = d.Set("topics", flattenStringList(repo.Topics))
840-
_ = d.Set("node_id", repo.GetNodeID())
841-
_ = d.Set("repo_id", repo.GetID())
842-
843-
if err := errors.Join(
844-
d.Set("allow_auto_merge", repo.GetAllowAutoMerge()),
845-
d.Set("allow_merge_commit", repo.GetAllowMergeCommit()),
846-
d.Set("allow_rebase_merge", repo.GetAllowRebaseMerge()),
847-
d.Set("allow_squash_merge", repo.GetAllowSquashMerge()),
848-
d.Set("allow_update_branch", repo.GetAllowUpdateBranch()),
849-
d.Set("allow_forking", repo.GetAllowForking()),
850-
d.Set("delete_branch_on_merge", repo.GetDeleteBranchOnMerge()),
851-
d.Set("web_commit_signoff_required", repo.GetWebCommitSignoffRequired()),
852-
d.Set("has_downloads", repo.GetHasDownloads()),
853-
d.Set("merge_commit_message", repo.GetMergeCommitMessage()),
854-
d.Set("merge_commit_title", repo.GetMergeCommitTitle()),
855-
d.Set("squash_merge_commit_message", repo.GetSquashMergeCommitMessage()),
856-
d.Set("squash_merge_commit_title", repo.GetSquashMergeCommitTitle()),
857-
); err != nil {
858-
tflog.Warn(ctx, "Error setting repository fields, which were previously ignored for archived repositories", map[string]any{"error": err.Error()})
859-
}
860-
861-
if repo.GetHasPages() {
862-
pages, _, err := client.Repositories.GetPagesInfo(ctx, owner, repoName)
863-
if err != nil {
864-
return diag.FromErr(err)
865-
}
866-
if err := d.Set("pages", flattenPages(pages)); err != nil {
867-
return diag.Errorf("error setting pages: %s", err.Error())
868-
}
869-
}
870-
871-
// Set fork information if this is a fork
872-
if repo.GetFork() {
873-
_ = d.Set("fork", "true")
874-
875-
// If the repository has parent information, set the source details
876-
if repo.Parent != nil {
877-
_ = d.Set("source_owner", repo.Parent.GetOwner().GetLogin())
878-
_ = d.Set("source_repo", repo.Parent.GetName())
879-
}
880-
} else {
881-
_ = d.Set("fork", "false")
882-
_ = d.Set("source_owner", "")
883-
_ = d.Set("source_repo", "")
884-
}
885-
886-
if repo.TemplateRepository != nil {
887-
if err = d.Set("template", []any{
888-
map[string]any{
889-
"owner": repo.TemplateRepository.Owner.Login,
890-
"repository": repo.TemplateRepository.Name,
891-
},
892-
}); err != nil {
893-
return diag.FromErr(err)
894-
}
895-
} else {
896-
if err = d.Set("template", []any{}); err != nil {
897-
return diag.FromErr(err)
898-
}
859+
if err := d.Set("etag", resp.Header.Get("ETag")); err != nil {
860+
return diag.FromErr(err)
899861
}
900862

901-
if repo.GetSecurityAndAnalysis() != nil {
902-
vulnerabilityAlerts, _, err := client.Repositories.GetVulnerabilityAlerts(ctx, owner, repoName)
903-
if err != nil {
904-
return diag.Errorf("error reading repository vulnerability alerts: %s", err.Error())
905-
}
906-
if err = d.Set("vulnerability_alerts", vulnerabilityAlerts); err != nil {
907-
return diag.FromErr(err)
908-
}
909-
910-
if err = d.Set("security_and_analysis", flattenSecurityAndAnalysis(repo.SecurityAndAnalysis)); err != nil {
911-
return diag.FromErr(err)
912-
}
863+
if diags := setRepositoryFieldsFromRepo(ctx, d, client, owner, repo); diags.HasError() {
864+
return diags
913865
}
914866

915867
return nil
@@ -952,7 +904,6 @@ func resourceGithubRepositoryUpdate(ctx context.Context, d *schema.ResourceData,
952904

953905
repoName := d.Id()
954906
owner := meta.(*Owner).name
955-
ctx = context.WithValue(ctx, ctxId, d.Id())
956907

957908
repo, _, err := client.Repositories.Edit(ctx, owner, repoName, repoReq)
958909
if err != nil {
@@ -1009,22 +960,26 @@ func resourceGithubRepositoryUpdate(ctx context.Context, d *schema.ResourceData,
1009960
repoReq.AllowForking = allowForking
1010961

1011962
tflog.Debug(ctx, "Updating repository visibility", map[string]any{"from": repo.GetVisibility(), "to": visibility})
1012-
_, resp, err := client.Repositories.Edit(ctx, owner, repoName, repoReq)
963+
updatedRepo, resp, err := client.Repositories.Edit(ctx, owner, repoName, repoReq)
1013964
if err != nil {
1014965
if resp.StatusCode != 422 || !strings.Contains(err.Error(), fmt.Sprintf("Visibility is already %s", visibility)) {
1015966
return diag.FromErr(err)
1016967
}
1017968
}
969+
repo = updatedRepo
970+
}
971+
972+
if diags := setRepositoryFieldsFromRepo(ctx, d, client, owner, repo); diags.HasError() {
973+
return diags
1018974
}
1019975

1020-
return resourceGithubRepositoryRead(ctx, d, meta)
976+
return nil
1021977
}
1022978

1023979
func resourceGithubRepositoryDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
1024980
client := meta.(*Owner).v3client
1025981
repoName := d.Id()
1026982
owner := meta.(*Owner).name
1027-
ctx = context.WithValue(ctx, ctxId, d.Id())
1028983

1029984
archiveOnDestroy := d.Get("archive_on_destroy").(bool)
1030985
if archiveOnDestroy {
@@ -1054,6 +1009,120 @@ func resourceGithubRepositoryImport(ctx context.Context, d *schema.ResourceData,
10541009
return []*schema.ResourceData{d}, nil
10551010
}
10561011

1012+
// setRepositoryFieldsFromRepo populates all Computed and Optional+Computed schema fields
1013+
// from a *github.Repository API response. Used by Create and Update to set state directly
1014+
// instead of chaining to Read. For fields requiring separate API calls (pages, vulnerability
1015+
// alerts), targeted calls are made here.
1016+
func setRepositoryFieldsFromRepo(ctx context.Context, d *schema.ResourceData, client *github.Client, owner string, repo *github.Repository) diag.Diagnostics {
1017+
repoName := repo.GetName()
1018+
1019+
if err := errors.Join(
1020+
d.Set("name", repoName),
1021+
d.Set("description", repo.GetDescription()),
1022+
d.Set("primary_language", repo.GetLanguage()),
1023+
d.Set("homepage_url", repo.GetHomepage()),
1024+
d.Set("private", repo.GetPrivate()),
1025+
d.Set("visibility", repo.GetVisibility()),
1026+
d.Set("has_issues", repo.GetHasIssues()),
1027+
d.Set("has_discussions", repo.GetHasDiscussions()),
1028+
d.Set("has_projects", repo.GetHasProjects()),
1029+
d.Set("has_wiki", repo.GetHasWiki()),
1030+
d.Set("is_template", repo.GetIsTemplate()),
1031+
d.Set("full_name", repo.GetFullName()),
1032+
d.Set("default_branch", repo.GetDefaultBranch()),
1033+
d.Set("html_url", repo.GetHTMLURL()),
1034+
d.Set("ssh_clone_url", repo.GetSSHURL()),
1035+
d.Set("svn_url", repo.GetSVNURL()),
1036+
d.Set("git_clone_url", repo.GetGitURL()),
1037+
d.Set("http_clone_url", repo.GetCloneURL()),
1038+
d.Set("archived", repo.GetArchived()),
1039+
d.Set("topics", flattenStringList(repo.Topics)),
1040+
d.Set("node_id", repo.GetNodeID()),
1041+
d.Set("repo_id", repo.GetID()),
1042+
d.Set("allow_auto_merge", repo.GetAllowAutoMerge()),
1043+
d.Set("allow_merge_commit", repo.GetAllowMergeCommit()),
1044+
d.Set("allow_rebase_merge", repo.GetAllowRebaseMerge()),
1045+
d.Set("allow_squash_merge", repo.GetAllowSquashMerge()),
1046+
d.Set("allow_update_branch", repo.GetAllowUpdateBranch()),
1047+
d.Set("allow_forking", repo.GetAllowForking()),
1048+
d.Set("delete_branch_on_merge", repo.GetDeleteBranchOnMerge()),
1049+
d.Set("web_commit_signoff_required", repo.GetWebCommitSignoffRequired()),
1050+
d.Set("has_downloads", repo.GetHasDownloads()),
1051+
d.Set("merge_commit_message", repo.GetMergeCommitMessage()),
1052+
d.Set("merge_commit_title", repo.GetMergeCommitTitle()),
1053+
d.Set("squash_merge_commit_message", repo.GetSquashMergeCommitMessage()),
1054+
d.Set("squash_merge_commit_title", repo.GetSquashMergeCommitTitle()),
1055+
); err != nil {
1056+
return diag.FromErr(err)
1057+
}
1058+
1059+
// Fork info
1060+
if repo.GetFork() {
1061+
if err := d.Set("fork", "true"); err != nil {
1062+
return diag.FromErr(err)
1063+
}
1064+
if repo.Parent != nil {
1065+
if err := errors.Join(
1066+
d.Set("source_owner", repo.Parent.GetOwner().GetLogin()),
1067+
d.Set("source_repo", repo.Parent.GetName()),
1068+
); err != nil {
1069+
return diag.FromErr(err)
1070+
}
1071+
}
1072+
} else {
1073+
if err := errors.Join(
1074+
d.Set("fork", "false"),
1075+
d.Set("source_owner", ""),
1076+
d.Set("source_repo", ""),
1077+
); err != nil {
1078+
return diag.FromErr(err)
1079+
}
1080+
}
1081+
1082+
// Template info
1083+
if repo.TemplateRepository != nil {
1084+
if err := d.Set("template", []any{
1085+
map[string]any{
1086+
"owner": repo.TemplateRepository.Owner.Login,
1087+
"repository": repo.TemplateRepository.Name,
1088+
},
1089+
}); err != nil {
1090+
return diag.FromErr(err)
1091+
}
1092+
} else {
1093+
if err := d.Set("template", []any{}); err != nil {
1094+
return diag.FromErr(err)
1095+
}
1096+
}
1097+
1098+
// Pages (requires separate API call for computed fields like url, status)
1099+
if repo.GetHasPages() {
1100+
pages, _, err := client.Repositories.GetPagesInfo(ctx, owner, repoName)
1101+
if err != nil {
1102+
return diag.FromErr(err)
1103+
}
1104+
if err := d.Set("pages", flattenPages(pages)); err != nil {
1105+
return diag.FromErr(err)
1106+
}
1107+
}
1108+
1109+
// Security and analysis + vulnerability alerts (requires separate API call)
1110+
if repo.GetSecurityAndAnalysis() != nil {
1111+
vulnerabilityAlerts, _, err := client.Repositories.GetVulnerabilityAlerts(ctx, owner, repoName)
1112+
if err != nil {
1113+
return diag.Errorf("error reading repository vulnerability alerts: %s", err.Error())
1114+
}
1115+
if err := errors.Join(
1116+
d.Set("vulnerability_alerts", vulnerabilityAlerts),
1117+
d.Set("security_and_analysis", flattenSecurityAndAnalysis(repo.SecurityAndAnalysis)),
1118+
); err != nil {
1119+
return diag.FromErr(err)
1120+
}
1121+
}
1122+
1123+
return nil
1124+
}
1125+
10571126
func expandPages(input []any) *github.Pages {
10581127
if len(input) == 0 || input[0] == nil {
10591128
return nil

0 commit comments

Comments
 (0)