[FEAT] Add github_repository_pages Resource and Data Source#3168
[FEAT] Add github_repository_pages Resource and Data Source#3168deiga wants to merge 21 commits intointegrations:mainfrom
github_repository_pages Resource and Data Source#3168Conversation
|
👋 Hi! Thank you for this contribution! Just to let you know, our GitHub SDK team does a round of issue and PR reviews twice a week, every Monday and Friday! We have a process in place for prioritizing and responding to your input. Because you are a part of this community please feel free to comment, add to, or pick up any issues/PRs that are labeled with |
e081a2d to
c75d366
Compare
stevehipwell
left a comment
There was a problem hiding this comment.
This is looking really good.
c75d366 to
91f6484
Compare
|
@healiha I'll check that out |
github_repository_pages Reasource and Data Sourcegithub_repository_pages Resource and Data Source
|
@deiga could you please rebase this PR? |
18c8135 to
55b203a
Compare
|
|
||
| update := &github.PagesUpdate{} | ||
|
|
||
| if d.HasChange("cname") { |
There was a problem hiding this comment.
We generally don't use this pattern, I'd expect to see d.get() & d.GetOk() here.
There was a problem hiding this comment.
I'm not sure I understand what you mean. d.HasChange has been used ~36 times in the codebase.
d.HasChange() tells us if there is a value change in the plan, whereas d.Get() and d.GetOk() would cause us to send fields even if they are not changed
There was a problem hiding this comment.
I don't think 36 usages, some of which are definitely in diff functions, comes close to the occurrences of the get functions. The only reason you're in the update function is that there are changes to apply, so building the struct using the get functions (d.Get() for bools & d.GetOk() for strings where that aren't always set) is going to be the most efficient and readable pattern.
There was a problem hiding this comment.
Since the API supports partial updates, I don't fully understand why we wouldn't want to utilize that.
For example these Optional & Computed fields are things that I wouldn't want to send unless they are actually changed in the plan and have been modified by the user.
Though I guess d.GetOk() isn't able to determine the latter once they have been set by Create or Read?
Could you elaborate the issue you're seeing with using d.HasChange()?
There was a problem hiding this comment.
The API supports partial updates, but Terraform doesn't. The idiomatic pattern in this provider is to set all of the fields being managed by Terraform before making a request; this makes the code as readable as possible and makes it less likely that we are impacted by edge case issues (our code path is as small and non-branching as possible). By using d.hasChanges() you're adding unnecessary complexity.
There was a problem hiding this comment.
I'm not sure what you mean with "Terraform doesn't support partial updates" as that is exactly what it supports.
But I do understand your point about less conditionals == less complexity
There was a problem hiding this comment.
After removing them I encounter that at least cname requires special handling, because we don't want to send the null value we get from the SDK ever.
Do you think it would be better to us if d.HasChanges("cname") there or something like if cname != ""?
There was a problem hiding this comment.
Actually, that turned out not be relevant. I already check if cname != "" I think this stems from sending the same value for https_enforced which the API returns 😬
I think it's not allowed to send https_enforced = true if cname = null . Although that is the default API response when creating a new page 😬
Signed-off-by: Timo Sand <timo.sand@f-secure.com>
Signed-off-by: Timo Sand <timo.sand@f-secure.com>
Signed-off-by: Timo Sand <timo.sand@f-secure.com>
… config Signed-off-by: Timo Sand <timo.sand@f-secure.com>
Signed-off-by: Timo Sand <timo.sand@f-secure.com>
Signed-off-by: Timo Sand <timo.sand@f-secure.com>
Signed-off-by: Timo Sand <timo.sand@f-secure.com>
Signed-off-by: Timo Sand <timo.sand@f-secure.com>
Signed-off-by: Timo Sand <timo.sand@f-secure.com>
Signed-off-by: Timo Sand <timo.sand@f-secure.com>
Signed-off-by: Timo Sand <timo.sand@f-secure.com>
Signed-off-by: Timo Sand <timo.sand@f-secure.com>
Signed-off-by: Timo Sand <timo.sand@f-secure.com>
Signed-off-by: Timo Sand <timo.sand@f-secure.com>
Signed-off-by: Timo Sand <timo.sand@f-secure.com>
Signed-off-by: Timo Sand <timo.sand@f-secure.com>
Signed-off-by: Timo Sand <timo.sand@f-secure.com>
Signed-off-by: Timo Sand <timo.sand@f-secure.com>
Signed-off-by: Timo Sand <timo.sand@f-secure.com>
Signed-off-by: Timo Sand <timo.sand@f-secure.com>
Signed-off-by: Timo Sand <timo.sand@f-secure.com>
7a1f79f to
56b7d70
Compare
| "public": { | ||
| Type: schema.TypeBool, | ||
| Optional: true, | ||
| Computed: true, |
There was a problem hiding this comment.
| Computed: true, | |
| Default: false, |
There was a problem hiding this comment.
I don't think it's a good idea to set a default, since the API default is different depending on which type of Owner you have. Only GHEC (and GHES?) are able to have private pages AFAIK, so for most users we'd be forcing them to configure this explicitly. We could default to true, but GHEC with EMU can't even have public pages and it'd be annoying for every other Enterprise owner to have to remember to set it to false.
What is the issue with using Computed and trust the API default?
There was a problem hiding this comment.
The logic is simpler with default for booleans. If the behaviour is dynamic we can keep it computed and but we then need to use d.GetOkExists() and know if we're in the create or update phase. The enforce https isn't dynamic so that should keep a default for simplicity.
| "https_enforced": { | ||
| Type: schema.TypeBool, | ||
| Optional: true, | ||
| Computed: true, |
There was a problem hiding this comment.
| Computed: true, | |
| Default: false, |
There was a problem hiding this comment.
I'm not sure this should have a default value, as it's default value isn't straighforward IMO. In GHEC with EMU the API set's that value as true by default and apparently so does a Free Org as well.
So we could maybe set the default as true
There was a problem hiding this comment.
Yeah, sorry my suggestion was incorrect I meant to make it true.
There was a problem hiding this comment.
I'll verify, but I think it's not possible to send https_enforced = true with no name set 😬
| // Check if we have values set that can't be configured as part of the create logic | ||
| cname, cnameOK := d.GetOk("cname") | ||
| public, publicOKExists := d.GetOkExists("public") //nolint:staticcheck // SA1019: d.GetOkExists is deprecated but necessary for bool fields | ||
| httpsEnforced, httpsEnforcedExists := d.GetOkExists("https_enforced") //nolint:staticcheck // SA1019: d.GetOkExists is deprecated but necessary for bool fields | ||
| tflog.Debug(ctx, "Do we have values set that need the update logic?", map[string]any{ | ||
| "public": public, | ||
| "public_ok_exists": publicOKExists, | ||
| "https_enforced": httpsEnforced, | ||
| "https_enforced_exists": httpsEnforcedExists, | ||
| "cname": cname, | ||
| "cname_ok": cnameOK, | ||
| }) | ||
|
|
||
| if cnameOK || publicOKExists || httpsEnforcedExists { | ||
| update := &github.PagesUpdate{} | ||
|
|
||
| if cnameOK { | ||
| update.CNAME = github.Ptr(cname.(string)) | ||
| } | ||
|
|
||
| if publicOKExists { | ||
| update.Public = github.Ptr(public.(bool)) | ||
| } | ||
|
|
||
| if httpsEnforcedExists { | ||
| update.HTTPSEnforced = github.Ptr(httpsEnforced.(bool)) | ||
| } | ||
|
|
||
| _, err = client.Repositories.UpdatePages(ctx, owner, repoName, update) | ||
| if err != nil { | ||
| return diag.FromErr(err) | ||
| } | ||
| } | ||
| if !publicOKExists { | ||
| if err := d.Set("public", pages.GetPublic()); err != nil { | ||
| return diag.FromErr(err) | ||
| } | ||
| } | ||
| if !httpsEnforcedExists { | ||
| if err := d.Set("https_enforced", pages.GetHTTPSEnforced()); err != nil { | ||
| return diag.FromErr(err) | ||
| } | ||
| } |
There was a problem hiding this comment.
| // Check if we have values set that can't be configured as part of the create logic | |
| cname, cnameOK := d.GetOk("cname") | |
| public, publicOKExists := d.GetOkExists("public") //nolint:staticcheck // SA1019: d.GetOkExists is deprecated but necessary for bool fields | |
| httpsEnforced, httpsEnforcedExists := d.GetOkExists("https_enforced") //nolint:staticcheck // SA1019: d.GetOkExists is deprecated but necessary for bool fields | |
| tflog.Debug(ctx, "Do we have values set that need the update logic?", map[string]any{ | |
| "public": public, | |
| "public_ok_exists": publicOKExists, | |
| "https_enforced": httpsEnforced, | |
| "https_enforced_exists": httpsEnforcedExists, | |
| "cname": cname, | |
| "cname_ok": cnameOK, | |
| }) | |
| if cnameOK || publicOKExists || httpsEnforcedExists { | |
| update := &github.PagesUpdate{} | |
| if cnameOK { | |
| update.CNAME = github.Ptr(cname.(string)) | |
| } | |
| if publicOKExists { | |
| update.Public = github.Ptr(public.(bool)) | |
| } | |
| if httpsEnforcedExists { | |
| update.HTTPSEnforced = github.Ptr(httpsEnforced.(bool)) | |
| } | |
| _, err = client.Repositories.UpdatePages(ctx, owner, repoName, update) | |
| if err != nil { | |
| return diag.FromErr(err) | |
| } | |
| } | |
| if !publicOKExists { | |
| if err := d.Set("public", pages.GetPublic()); err != nil { | |
| return diag.FromErr(err) | |
| } | |
| } | |
| if !httpsEnforcedExists { | |
| if err := d.Set("https_enforced", pages.GetHTTPSEnforced()); err != nil { | |
| return diag.FromErr(err) | |
| } | |
| } | |
| // Check if we have values set that can't be configured as part of the create logic | |
| cname, cnameOK := d.GetOk("cname") | |
| public := d.Get("public").(bool) | |
| httpsEnforced := d.Get("https_enforced").(bool) | |
| if cnameOK || public || httpsEnforced { | |
| tflog.Debug(ctx, "values set that require an update after create", map[string]any{ | |
| "public": public, | |
| "https_enforced": httpsEnforced, | |
| "cname": cname, | |
| }) | |
| update := &github.PagesUpdate{} | |
| if cnameOK { | |
| update.CNAME = github.Ptr(cname.(string)) | |
| } | |
| if public { | |
| update.Public = github.Ptr(public) | |
| } | |
| if httpsEnforcedExists { | |
| update.HTTPSEnforced = github.Ptr(httpsEnforced) | |
| } | |
| _, err = client.Repositories.UpdatePages(ctx, owner, repoName, update) | |
| if err != nil { | |
| return diag.FromErr(err) | |
| } | |
| } |
We only care about the non-default values so we can short circuit the behaviour in the create function. Note the boolean values now have defaults so they don't need setting.
There was a problem hiding this comment.
See above why I think default values for those booleans are problematic

Resolves #3167
Resolves #2671
Resolves #3142
Resolves #1045
Resolves #2653
Resolves #2335
Before the change?
github_repositoryAfter the change?
github_repository_pagesresource to manage the pagesgithub_repository_pagesdata source to fetch information about the pagesgithub_repositoryResource and Data Sourcepublicfield ofgithub_repository_pageshttps_enforcedfield ofgithub_repository_pagesPull request checklist
Schema migrations have been created if needed (example)Does this introduce a breaking change?
Please see our docs on breaking changes to help!