Skip to content

Commit 29b7f83

Browse files
authored
Merge branch 'main' into feat/github-app-for-users
2 parents 57527c4 + 66b1d20 commit 29b7f83

15 files changed

+759
-16
lines changed

.github/workflows/labeler.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,4 @@ jobs:
1515
- name: Run Labeler
1616
uses: actions/labeler@634933edcd8ababfe52f92936142cc22ac488b1b # v6.0.1
1717
with:
18-
sync-labels: true # Whether or not to remove labels when matching files are reverted or no longer changed by the PR
18+
sync-labels: false # Whether or not to remove labels when matching files are reverted or no longer changed by the PR
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package github
2+
3+
import (
4+
"context"
5+
"net/url"
6+
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8+
)
9+
10+
func dataSourceGithubActionsEnvironmentPublicKey() *schema.Resource {
11+
return &schema.Resource{
12+
Read: dataSourceGithubActionsEnvironmentPublicKeyRead,
13+
14+
Schema: map[string]*schema.Schema{
15+
"repository": {
16+
Type: schema.TypeString,
17+
Required: true,
18+
},
19+
"environment": {
20+
Type: schema.TypeString,
21+
Required: true,
22+
},
23+
"key_id": {
24+
Type: schema.TypeString,
25+
Computed: true,
26+
},
27+
"key": {
28+
Type: schema.TypeString,
29+
Computed: true,
30+
},
31+
},
32+
}
33+
}
34+
35+
func dataSourceGithubActionsEnvironmentPublicKeyRead(d *schema.ResourceData, meta interface{}) error {
36+
client := meta.(*Owner).v3client
37+
owner := meta.(*Owner).name
38+
repository := d.Get("repository").(string)
39+
40+
envName := d.Get("environment").(string)
41+
escapedEnvName := url.PathEscape(envName)
42+
43+
repo, _, err := client.Repositories.Get(context.TODO(), owner, repository)
44+
if err != nil {
45+
return err
46+
}
47+
48+
publicKey, _, err := client.Actions.GetEnvPublicKey(context.TODO(), int(repo.GetID()), escapedEnvName)
49+
if err != nil {
50+
return err
51+
}
52+
53+
d.SetId(publicKey.GetKeyID())
54+
err = d.Set("key_id", publicKey.GetKeyID())
55+
if err != nil {
56+
return err
57+
}
58+
err = d.Set("key", publicKey.GetKey())
59+
if err != nil {
60+
return err
61+
}
62+
63+
return nil
64+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package github
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest"
8+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
9+
)
10+
11+
func TestAccGithubActionsEnvironmentPublicKeyDataSource(t *testing.T) {
12+
13+
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
14+
15+
t.Run("queries a repository environment public key without error", func(t *testing.T) {
16+
17+
config := fmt.Sprintf(`
18+
resource "github_repository" "test" {
19+
name = "tf-acc-test-%[1]s"
20+
auto_init = true
21+
}
22+
23+
resource "github_repository_environment" "test" {
24+
repository = github_repository.test.name
25+
environment = "tf-acc-test-%[1]s"
26+
}
27+
28+
data "github_actions_environment_public_key" "test" {
29+
repository = github_repository.test.name
30+
environment = github_repository_environment.test.environment
31+
}`, randomID)
32+
33+
check := resource.ComposeTestCheckFunc(
34+
resource.TestCheckResourceAttrSet(
35+
"data.github_actions_environment_public_key.test", "key",
36+
),
37+
)
38+
39+
testCase := func(t *testing.T, mode string) {
40+
resource.Test(t, resource.TestCase{
41+
PreCheck: func() { skipUnlessMode(t, mode) },
42+
Providers: testAccProviders,
43+
Steps: []resource.TestStep{
44+
{
45+
Config: config,
46+
Check: check,
47+
},
48+
},
49+
})
50+
}
51+
52+
t.Run("with an anonymous account", func(t *testing.T) {
53+
t.Skip("anonymous account not supported for this operation")
54+
})
55+
56+
t.Run("with an individual account", func(t *testing.T) {
57+
testCase(t, individual)
58+
})
59+
60+
t.Run("with an organization account", func(t *testing.T) {
61+
testCase(t, organization)
62+
})
63+
64+
})
65+
}

github/provider.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,9 +202,11 @@ func Provider() *schema.Provider {
202202
"github_user_ssh_key": resourceGithubUserSshKey(),
203203
"github_enterprise_organization": resourceGithubEnterpriseOrganization(),
204204
"github_enterprise_actions_runner_group": resourceGithubActionsEnterpriseRunnerGroup(),
205+
"github_workflow_repository_permissions": resourceGithubWorkflowRepositoryPermissions(),
205206
},
206207

207208
DataSourcesMap: map[string]*schema.Resource{
209+
"github_actions_environment_public_key": dataSourceGithubActionsEnvironmentPublicKey(),
208210
"github_actions_environment_secrets": dataSourceGithubActionsEnvironmentSecrets(),
209211
"github_actions_environment_variables": dataSourceGithubActionsEnvironmentVariables(),
210212
"github_actions_organization_oidc_subject_claim_customization_template": dataSourceGithubActionsOrganizationOIDCSubjectClaimCustomizationTemplate(),

github/resource_github_actions_environment_secret.go

Lines changed: 6 additions & 7 deletions
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

Lines changed: 131 additions & 0 deletions
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
}

github/resource_github_release.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,19 +181,25 @@ func resourceGithubReleaseCreateUpdate(d *schema.ResourceData, meta interface{})
181181
if resp != nil {
182182
log.Printf("[DEBUG] Response from creating release: %#v", *resp)
183183
}
184+
if err != nil {
185+
return err
186+
}
184187
} else {
185-
number := d.Get("number").(int64)
188+
id, err := strconv.ParseInt(d.Id(), 10, 64)
189+
if err != nil {
190+
return err
191+
}
186192
log.Printf("[DEBUG] Updating release: %d:%s (%s/%s)",
187-
number, targetCommitish, owner, repoName)
188-
release, resp, err = client.Repositories.EditRelease(ctx, owner, repoName, number, req)
193+
id, targetCommitish, owner, repoName)
194+
release, resp, err = client.Repositories.EditRelease(ctx, owner, repoName, id, req)
189195
if resp != nil {
190196
log.Printf("[DEBUG] Response from updating release: %#v", *resp)
191197
}
198+
if err != nil {
199+
return err
200+
}
192201
}
193202

194-
if err != nil {
195-
return err
196-
}
197203
transformResponseToResourceData(d, release, repoName)
198204
return nil
199205
}

github/resource_github_repository.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,20 @@ func resourceGithubRepositoryUpdate(d *schema.ResourceData, meta interface{}) er
779779
owner := meta.(*Owner).name
780780
ctx := context.WithValue(context.Background(), ctxId, d.Id())
781781

782+
// When the organization has "Require sign off on web-based commits" enabled,
783+
// the API doesn't allow you to send `web_commit_signoff_required` in order to
784+
// update the repository with this field or it will throw a 422 error.
785+
// As a workaround, we check if the organization requires it, and if so,
786+
// we remove the field from the request.
787+
if d.HasChange("web_commit_signoff_required") && meta.(*Owner).IsOrganization {
788+
organization, _, err := client.Organizations.Get(ctx, owner)
789+
if err == nil {
790+
if organization != nil && organization.GetWebCommitSignoffRequired() {
791+
repoReq.WebCommitSignoffRequired = nil
792+
}
793+
}
794+
}
795+
782796
repo, _, err := client.Repositories.Edit(ctx, owner, repoName, repoReq)
783797
if err != nil {
784798
return err
@@ -880,6 +894,8 @@ func resourceGithubRepositoryDelete(d *schema.ResourceData, meta interface{}) er
880894
return err
881895
}
882896
repoReq := resourceGithubRepositoryObject(d)
897+
// Always remove `web_commit_signoff_required` when archiving, to avoid 422 error
898+
repoReq.WebCommitSignoffRequired = nil
883899
log.Printf("[DEBUG] Archiving repository on delete: %s/%s", owner, repoName)
884900
_, _, err := client.Repositories.Edit(ctx, owner, repoName, repoReq)
885901
return err

0 commit comments

Comments
 (0)