-
Notifications
You must be signed in to change notification settings - Fork 927
fix: Correct secret drift implementation #3069
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
base: main
Are you sure you want to change the base?
Conversation
|
👋 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 |
d8a4694 to
4a14a57
Compare
nickfloyd
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like this approach vs what we have. I just had some thoughts to consider.
| // | ||
| // To solve this, we must unset the values and allow Terraform to decide whether or | ||
| // not this resource should be modified or left as-is (ignore_changes). | ||
| if updatedAt, ok := d.GetOk("updated_at"); ok && updatedAt != secret.UpdatedAt.String() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I belive this will be a breaking change due to ignore_changes - there will be forced recreations now. Not sure if we want a migration path for the users who rely on that or just mark it as a breaking change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I'm not sure I follow how the new code would be breaking? The comment here is incorrect as this will always force new (note this resource doesn't have the destroy_on_drift input to suppress this behaviour).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just looking into this further and it looks like this resource was relying on a lifecycle block to ignore changes. I've updated the docs to show this correctly being implemented on the new remote_updated_at field. I wouldn't consider a change to how meta arguments work a breaking change.
| ReadContext: resourceGithubActionsEnvironmentSecretRead, | ||
| DeleteContext: resourceGithubActionsEnvironmentSecretDelete, | ||
|
|
||
| CustomizeDiff: func(ctx context.Context, diff *schema.ResourceDiff, m any) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really like this approach. Any concerns around external changes to the resource forcing the destruction and recreation of the secret?
For instance...
Let's say a user config has:
resource "github_actions_environment_secret" "api_key" {
...
plaintext_value = "secret-value-123"
lifecycle {
ignore_changes = [plaintext_value]
}
}
- User runs terraform apply
- Other user updates the secret via UI
- User runs terraform apply
The old way the apply would detect no changes. This new approach updated_at and remote_updated_at would be different and therefore force a recreation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIK setting ignore_changes = [plaintext_value, updated_at, remote_updated_at] should resolve that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO this is a documentation issue, what @deiga added should be the correct solution. If it's not then we'd need to add in the explicit destroy_on_drift field that I'd planned on deprecating.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The correct setting is ignore_changes = [remote_updated_at] and there is now a test to validate this.
| var testAccProviders map[string]*schema.Provider = map[string]*schema.Provider{ | ||
| "github": Provider(), | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIK we should be getting rid of using testAccProviders the recommended approach is to only use ProviderFactories.
Could you mark this as deprecated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've made the change to do this already (and stashed them), but I'm holding off adding them in to the PR until the actual changes have been reviewed.
| ReadContext: resourceGithubActionsEnvironmentSecretRead, | ||
| DeleteContext: resourceGithubActionsEnvironmentSecretDelete, | ||
|
|
||
| CustomizeDiff: func(ctx context.Context, diff *schema.ResourceDiff, m any) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: I think we should be able to forgo adding the remote_updated_at field as we could use customdiff.ForceNewIfChange (https://pkg.go.dev/github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff#ForceNewIfChange) here
Something along the lines of:
CustomizeDiff:
customdiff.ForceNewIfChange(
"updated_at",
func(ctx context.Context, oldValue, newValue, meta any) bool {
remoteUpdatedAt := newValue
if len(remoteUpdatedAt) == 0 {
return false
}
updatedAt := oldValue
if updatedAt != remoteUpdatedAt {
if len(updatedAt) != 0 {
return true
}
}
return false
}
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately you can't do this (I actually coded it up to make sure my mental model was correct). If we're doing drift detection we can't rely on a field updated in the read path as it will never have a change in the pan path. In this case update_at is only updated in read if it's not been able to be calculated as part of the create path, this contrasts with remote_updated_at which is always updated.
| ReadContext: resourceGithubActionsEnvironmentSecretRead, | ||
| DeleteContext: resourceGithubActionsEnvironmentSecretDelete, | ||
|
|
||
| CustomizeDiff: func(ctx context.Context, diff *schema.ResourceDiff, m any) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIK setting ignore_changes = [plaintext_value, updated_at, remote_updated_at] should resolve that
299e8db to
c1c5bc3
Compare
|
I've updated the implementation to not require a recreate now that the diff is being handled correctly. I've also updated the pattern to correctly handle repo identity and support repo renames. |
d34ded0 to
12aef66
Compare
5a5289a to
edfa9c9
Compare
|
There is an open PR for adding import of should that be taken into account or closed? |
|
Further prior art on this topic: #2499 |
|
This might resolve #2047 |
edfa9c9 to
4d05762
Compare
|
Thanks for the context @deiga. PR #2462 can be closed by this PR; FYI it has the same special character escaping issues seen across all of the environment resources (& data sources). PR #2499 can also be closed by this PR as it looks like it's just a refactoring of the existing flawed pattern. This PR should fix #2047, that's one of the failure mode it's intended to resolve. |
Signed-off-by: Steve Hipwell <steve.hipwell@gmail.com>
4d05762 to
852c032
Compare
|
@deiga FYI I've updated this PR to use your ID as computed pattern, as well as shared diff functions. |
deiga
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some comments. Disregard if not applicable due to draft state
| } else { | ||
| d.SetId(id) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not your change, but I wonder if the else is superfluous here
| } else { | ||
| d.SetId(id) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: does this else increase clarity?
| if err = d.Set("updated_at", secret.UpdatedAt.String()); err != nil { | ||
| // GitHub API does not return on update so we have to lookup the secret to get timestamps | ||
| if secret, _, err := client.Actions.GetEnvSecret(ctx, repoID, escapedEnvName, secretName); err == nil { | ||
| if err := d.Set("created_at", secret.CreatedAt.String()); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: Is there a case where setting created_at is necessary in Update? I'm thinking that it's only and import and create concern
| if err := d.Set("remote_updated_at", secret.UpdatedAt.String()); err != nil { | ||
| return diag.FromErr(err) | ||
| } | ||
| } else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: so, if the API returns an error when fetching secret, we set timestamps to nil. Why not I'd to nil as well? Isn't the secret then not existing?
| envName := d.Get("environment").(string) | ||
| secretName := d.Get("secret_name").(string) | ||
|
|
||
| log.Printf("[INFO] Deleting environment secret: %s", d.Id()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: would you be willing to make the tflog refactoring in the same PR? They should be fairly safe changes
|
|
||
| repoName, envNamePart, secretName, err := parseID3(d.Id()) | ||
| if err != nil { | ||
| return nil, err |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: Should the error message include the import ID format, to make the UX nicer?
| @@ -0,0 +1,53 @@ | |||
| package github | |||
|
|
|||
| // TODO: Enable this test one there is a way to mock the metadata. | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: which metadata are you referring to here?
| }) | ||
| if err != nil { | ||
| var ghErr *github.ErrorResponse | ||
| if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusConflict { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love this! Implicit import can feel nice for the user but is a TF antipattern
Resolves #3049
Resolves #2782
Resolves #2913
Resolves #2047
Resolves #2518
Closes #2462
Closes #2499
Before the change?
After the change?
Pull request checklist
Does this introduce a breaking change?
Please see our docs on breaking changes to help!