Skip to content

Conversation

@stevehipwell
Copy link
Collaborator

@stevehipwell stevehipwell commented Jan 9, 2026

Resolves #3049
Resolves #2782
Resolves #2913
Resolves #2047
Resolves #2518

Closes #2462
Closes #2499


Before the change?

After the change?

Pull request checklist

  • Schema migrations have been created if needed (example)
  • Tests for the changes have been added (for bug fixes / features)
  • Docs have been reviewed and added / updated if needed (for bug fixes / features)

Does this introduce a breaking change?

Please see our docs on breaking changes to help!

  • Yes
  • No

@stevehipwell stevehipwell added this to the v6.10.0 Release milestone Jan 9, 2026
@stevehipwell stevehipwell requested a review from nickfloyd January 9, 2026 20:16
@stevehipwell stevehipwell self-assigned this Jan 9, 2026
@stevehipwell stevehipwell added the Type: Bug Something isn't working as documented label Jan 9, 2026
@github-actions
Copy link

github-actions bot commented Jan 9, 2026

👋 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 Status: Up for grabs. You & others like you are the reason all of this works! So thank you & happy coding! 🚀

Copy link
Member

@nickfloyd nickfloyd left a 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() {
Copy link
Member

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.

Copy link
Collaborator Author

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).

Copy link
Collaborator Author

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 {
Copy link
Member

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]
  }
}
  1. User runs terraform apply
  2. Other user updates the secret via UI
  3. 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.

Copy link
Collaborator

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

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

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.

Comment on lines +77 to +85
var testAccProviders map[string]*schema.Provider = map[string]*schema.Provider{
"github": Provider(),
}
Copy link
Collaborator

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?

Copy link
Collaborator Author

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 {
Copy link
Collaborator

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
		}
)

Copy link
Collaborator Author

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 {
Copy link
Collaborator

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

@stevehipwell
Copy link
Collaborator Author

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.

nickfloyd
nickfloyd previously approved these changes Jan 13, 2026
@deiga
Copy link
Collaborator

deiga commented Jan 17, 2026

There is an open PR for adding import of resourceGithubActionsEnvironmentSecret
https://github.com/integrations/terraform-provider-github/pull/2462/changes#diff-bd7e54bc1e5ce2c06ae7d38565efee3c760620c960497f9c1d6db2f230415cbc

should that be taken into account or closed?

@deiga
Copy link
Collaborator

deiga commented Jan 17, 2026

Further prior art on this topic: #2499

@deiga
Copy link
Collaborator

deiga commented Jan 18, 2026

This might resolve #2047

@stevehipwell
Copy link
Collaborator Author

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>
@stevehipwell
Copy link
Collaborator Author

@deiga FYI I've updated this PR to use your ID as computed pattern, as well as shared diff functions.

Copy link
Collaborator

@deiga deiga left a 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

Comment on lines 154 to 156
} else {
d.SetId(id)
}
Copy link
Collaborator

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

Comment on lines +205 to +207
} else {
d.SetId(id)
}
Copy link
Collaborator

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 {
Copy link
Collaborator

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 {
Copy link
Collaborator

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())
Copy link
Collaborator

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
Copy link
Collaborator

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.
Copy link
Collaborator

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 {
Copy link
Collaborator

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Type: Bug Something isn't working as documented

Projects

None yet

3 participants