Skip to content

Commit 70adeac

Browse files
committed
fix support for environment secrets lifecycle ignore changes
1 parent 6008909 commit 70adeac

3 files changed

+166
-8
lines changed

github/resource_github_actions_environment_secret.go

+6-7
Original file line numberDiff line numberDiff line change
@@ -172,16 +172,15 @@ func resourceGithubActionsEnvironmentSecretRead(d *schema.ResourceData, meta int
172172
//
173173
// If the resource is changed externally in the meantime then reading back
174174
// the last update timestamp will return a result different than the
175-
// timestamp we've persisted in the state. In that case, we can no longer
176-
// trust that the value (which we don't see) is equal to what we've declared
177-
// previously.
175+
// timestamp we've persisted in the state. In this case, we can no longer
176+
// trust that the value matches what is in the state file.
178177
//
179-
// The only solution to enforce consistency between is to mark the resource
180-
// as deleted (unset the ID) in order to fix potential drift by recreating
181-
// the resource.
178+
// To solve this, we must unset the values and allow Terraform to decide whether or
179+
// not this resource should be modified or left as-is (ignore_changes).
182180
if updatedAt, ok := d.GetOk("updated_at"); ok && updatedAt != secret.UpdatedAt.String() {
183181
log.Printf("[INFO] The environment secret %s has been externally updated in GitHub", d.Id())
184-
d.SetId("")
182+
d.Set("encrypted_value", "")
183+
d.Set("plaintext_value", "")
185184
} else if !ok {
186185
if err = d.Set("updated_at", secret.UpdatedAt.String()); err != nil {
187186
return err

github/resource_github_actions_environment_secret_test.go

+131
Original file line numberDiff line numberDiff line change
@@ -166,5 +166,136 @@ func TestAccGithubActionsEnvironmentSecret(t *testing.T) {
166166
})
167167

168168
})
169+
}
170+
171+
func TestAccGithubActionsEnvironmentSecretIgnoreChanges(t *testing.T) {
172+
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
173+
174+
t.Run("creates environment secrets using lifecycle ignore_changes", func(t *testing.T) {
175+
secretValue := base64.StdEncoding.EncodeToString([]byte("super_secret_value"))
176+
modifiedSecretValue := base64.StdEncoding.EncodeToString([]byte("a_modified_super_secret_value"))
177+
178+
configFmtStr := `
179+
resource "github_repository" "test" {
180+
name = "tf-acc-test-%s"
181+
182+
# TODO: provider appears to have issues destroying repositories while running the tests.
183+
#
184+
# Even with Organization Admin an error is seen:
185+
# Error: DELETE https://api.<cut>/tf-acc-test-<id>: "403 Must have admin rights to Repository. []"
186+
#
187+
# Workaround to using 'archive_on_destroy' instead.
188+
archive_on_destroy = true
189+
190+
visibility = "private"
191+
}
192+
193+
resource "github_repository_environment" "test" {
194+
repository = github_repository.test.name
195+
environment = "environment / test"
196+
}
197+
198+
resource "github_actions_environment_secret" "plaintext_secret" {
199+
repository = github_repository.test.name
200+
environment = github_repository_environment.test.environment
201+
secret_name = "test_plaintext_secret_name"
202+
plaintext_value = "%s"
203+
204+
lifecycle {
205+
ignore_changes = [plaintext_value]
206+
}
207+
}
208+
209+
resource "github_actions_environment_secret" "encrypted_secret" {
210+
repository = github_repository.test.name
211+
environment = github_repository_environment.test.environment
212+
secret_name = "test_encrypted_secret_name"
213+
encrypted_value = "%s"
214+
215+
lifecycle {
216+
ignore_changes = [encrypted_value]
217+
}
218+
}
219+
`
220+
221+
checks := map[string]resource.TestCheckFunc{
222+
"before": resource.ComposeTestCheckFunc(
223+
resource.TestCheckResourceAttr(
224+
"github_actions_environment_secret.plaintext_secret", "plaintext_value",
225+
secretValue,
226+
),
227+
resource.TestCheckResourceAttr(
228+
"github_actions_environment_secret.encrypted_secret", "encrypted_value",
229+
secretValue,
230+
),
231+
resource.TestCheckResourceAttrSet(
232+
"github_actions_environment_secret.plaintext_secret", "created_at",
233+
),
234+
resource.TestCheckResourceAttrSet(
235+
"github_actions_environment_secret.plaintext_secret", "updated_at",
236+
),
237+
),
238+
"after": resource.ComposeTestCheckFunc(
239+
resource.TestCheckResourceAttr(
240+
"github_actions_environment_secret.plaintext_secret", "plaintext_value",
241+
secretValue,
242+
),
243+
resource.TestCheckResourceAttr(
244+
"github_actions_environment_secret.encrypted_secret", "encrypted_value",
245+
secretValue,
246+
),
247+
resource.TestCheckResourceAttrSet(
248+
"github_actions_environment_secret.plaintext_secret", "created_at",
249+
),
250+
resource.TestCheckResourceAttrSet(
251+
"github_actions_environment_secret.plaintext_secret", "updated_at",
252+
),
253+
),
254+
}
255+
256+
testCase := func(t *testing.T, mode string) {
257+
resource.Test(t, resource.TestCase{
258+
PreCheck: func() { skipUnlessMode(t, mode) },
259+
Providers: testAccProviders,
260+
Steps: []resource.TestStep{
261+
{
262+
Config: fmt.Sprintf(configFmtStr, randomID, secretValue, secretValue),
263+
Check: checks["before"],
264+
},
265+
{
266+
Config: fmt.Sprintf(configFmtStr, randomID, secretValue, secretValue),
267+
Check: checks["after"],
268+
},
269+
{
270+
// In this case the values change in the config, but the lifecycle ignore_changes should
271+
// not cause the actual values to be updated. This would also be the case when a secret
272+
// is externally modified (when what is in state does not match what is given).
273+
Config: fmt.Sprintf(configFmtStr, randomID, modifiedSecretValue, modifiedSecretValue),
274+
Check: resource.ComposeTestCheckFunc(
275+
resource.TestCheckResourceAttr(
276+
"github_actions_environment_secret.plaintext_secret", "plaintext_value",
277+
secretValue, // Should still have the original value in state.
278+
),
279+
resource.TestCheckResourceAttr(
280+
"github_actions_environment_secret.encrypted_secret", "encrypted_value",
281+
secretValue, // Should still have the original value in state.
282+
),
283+
),
284+
},
285+
},
286+
})
287+
}
288+
289+
t.Run("with an anonymous account", func(t *testing.T) {
290+
t.Skip("anonymous account not supported for this operation")
291+
})
292+
293+
t.Run("with an individual account", func(t *testing.T) {
294+
testCase(t, individual)
295+
})
169296

297+
t.Run("with an organization account", func(t *testing.T) {
298+
testCase(t, organization)
299+
})
300+
})
170301
}

website/docs/r/actions_environment_secret.html.markdown

+29-1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,34 @@ resource "github_actions_environment_secret" "test_secret" {
5353
}
5454
```
5555

56+
## Example Lifecycle Ignore Changes
57+
58+
This resource supports the `lifecycle` `ignore_changes` block. This is for use cases where a secret value is created
59+
using a placeholder value and then modified after creation outside the scope of Terraform. This approach ensures only
60+
the initial placeholder value is referenced in your code and in the resulting state file.
61+
62+
```hcl
63+
resource "github_actions_environment_secret" "example_secret" {
64+
environment = "example_environment"
65+
secret_name = "example_secret_name"
66+
plaintext_value = "placeholder"
67+
68+
lifecycle {
69+
ignore_changes = [plaintext_value]
70+
}
71+
}
72+
73+
resource "github_actions_environment_secret" "example_secret" {
74+
environment = "example_environment"
75+
secret_name = "example_secret_name"
76+
encrypted_value = base64sha256("placeholder")
77+
78+
lifecycle {
79+
ignore_changes = [encrypted_value]
80+
}
81+
}
82+
```
83+
5684
## Argument Reference
5785

5886
The following arguments are supported:
@@ -71,4 +99,4 @@ The following arguments are supported:
7199

72100
## Import
73101

74-
This resource does not support importing. If you'd like to help contribute it, please visit our [GitHub page](https://github.com/integrations/terraform-provider-github)!
102+
This resource does not support importing. If you'd like to help contribute it, please visit our [GitHub page](https://github.com/integrations/terraform-provider-github)!

0 commit comments

Comments
 (0)