Skip to content

Commit d855d9d

Browse files
feat: add github_user_ssh_signing_key
feat: add docs for github_user_ssh_signing_key fix: add github_user_ssh_signing_key to provider.go fix: add github_user_ssh_signing_key to github.erb fix: use tflog, context, test-structure fix: use resource-prefix, sweeper, 404-handling, direct read fix: key_id feat: import feat: adjust user_ssh_key to new structure fix: typo fix: dont overwrite key on create fix: add new fields to website
1 parent 1af72d4 commit d855d9d

9 files changed

+435
-78
lines changed

github/acc_test.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,16 @@ func configureSweepers() {
214214
Name: "teams",
215215
F: sweepTeams,
216216
})
217+
218+
resource.AddTestSweepers("user_ssh_keys", &resource.Sweeper{
219+
Name: "user_ssh_keys",
220+
F: sweepUserSSHKeys,
221+
})
222+
223+
resource.AddTestSweepers("user_ssh_signing_keys", &resource.Sweeper{
224+
Name: "user_ssh_signing_keys",
225+
F: sweepUserSSHSigningKeys,
226+
})
217227
}
218228

219229
func sweepTeams(_ string) error {
@@ -286,6 +296,66 @@ func sweepRepositories(_ string) error {
286296
return nil
287297
}
288298

299+
func sweepUserSSHKeys(_ string) error {
300+
fmt.Println("sweeping user SSH keys")
301+
302+
meta, err := getTestMeta()
303+
if err != nil {
304+
return fmt.Errorf("could not get test meta for sweeper: %w", err)
305+
}
306+
307+
client := meta.v3client
308+
owner := meta.name
309+
ctx := context.Background()
310+
311+
keys, _, err := client.Users.ListKeys(ctx, owner, nil)
312+
if err != nil {
313+
return err
314+
}
315+
316+
for _, k := range keys {
317+
if title := k.GetTitle(); strings.HasPrefix(title, testResourcePrefix) {
318+
fmt.Printf("destroying user SSH key %s\n", title)
319+
320+
if _, err := client.Users.DeleteKey(ctx, k.GetID()); err != nil {
321+
return err
322+
}
323+
}
324+
}
325+
326+
return nil
327+
}
328+
329+
func sweepUserSSHSigningKeys(_ string) error {
330+
fmt.Println("sweeping user SSH signing keys")
331+
332+
meta, err := getTestMeta()
333+
if err != nil {
334+
return fmt.Errorf("could not get test meta for sweeper: %w", err)
335+
}
336+
337+
client := meta.v3client
338+
owner := meta.name
339+
ctx := context.Background()
340+
341+
keys, _, err := client.Users.ListSSHSigningKeys(ctx, owner, nil)
342+
if err != nil {
343+
return err
344+
}
345+
346+
for _, k := range keys {
347+
if title := k.GetTitle(); strings.HasPrefix(title, testResourcePrefix) {
348+
fmt.Printf("destroying user SSH signing key %s\n", title)
349+
350+
if _, err := client.Users.DeleteSSHSigningKey(ctx, k.GetID()); err != nil {
351+
return err
352+
}
353+
}
354+
}
355+
356+
return nil
357+
}
358+
289359
func skipUnauthenticated(t *testing.T) {
290360
if testAccConf.authMode == anonymous {
291361
t.Skip("Skipping as test mode not authenticated")

github/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ func Provider() *schema.Provider {
212212
"github_user_gpg_key": resourceGithubUserGpgKey(),
213213
"github_user_invitation_accepter": resourceGithubUserInvitationAccepter(),
214214
"github_user_ssh_key": resourceGithubUserSshKey(),
215+
"github_user_ssh_signing_key": resourceGithubUserSshSigningKey(),
215216
"github_enterprise_organization": resourceGithubEnterpriseOrganization(),
216217
"github_enterprise_actions_runner_group": resourceGithubActionsEnterpriseRunnerGroup(),
217218
"github_enterprise_actions_workflow_permissions": resourceGithubEnterpriseActionsWorkflowPermissions(),

github/resource_github_user_ssh_key.go

Lines changed: 71 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,23 @@ package github
22

33
import (
44
"context"
5-
"errors"
6-
"log"
5+
"fmt"
76
"net/http"
87
"strconv"
9-
"strings"
108

119
"github.com/google/go-github/v83/github"
10+
"github.com/hashicorp/terraform-plugin-log/tflog"
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
1212
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1313
)
1414

1515
func resourceGithubUserSshKey() *schema.Resource {
1616
return &schema.Resource{
17-
Create: resourceGithubUserSshKeyCreate,
18-
Read: resourceGithubUserSshKeyRead,
19-
Delete: resourceGithubUserSshKeyDelete,
17+
CreateContext: resourceGithubUserSshKeyCreate,
18+
ReadContext: resourceGithubUserSshKeyRead,
19+
DeleteContext: resourceGithubUserSshKeyDelete,
2020
Importer: &schema.ResourceImporter{
21-
StateContext: schema.ImportStatePassthroughContext,
21+
StateContext: resourceGithubUserSshKeyImport,
2222
},
2323

2424
Schema: map[string]*schema.Schema{
@@ -33,15 +33,11 @@ func resourceGithubUserSshKey() *schema.Resource {
3333
Required: true,
3434
ForceNew: true,
3535
Description: "The public SSH key to add to your GitHub account.",
36-
DiffSuppressFunc: func(k, oldV, newV string, d *schema.ResourceData) bool {
37-
newTrimmed := strings.TrimSpace(newV)
38-
return oldV == newTrimmed
39-
},
4036
},
41-
"url": {
42-
Type: schema.TypeString,
37+
"key_id": {
38+
Type: schema.TypeInt,
4339
Computed: true,
44-
Description: "The URL of the SSH key.",
40+
Description: "The unique identifier of the SSH key.",
4541
},
4642
"etag": {
4743
Type: schema.TypeString,
@@ -51,80 +47,100 @@ func resourceGithubUserSshKey() *schema.Resource {
5147
}
5248
}
5349

54-
func resourceGithubUserSshKeyCreate(d *schema.ResourceData, meta any) error {
50+
func resourceGithubUserSshKeyCreate(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
5551
client := meta.(*Owner).v3client
5652

5753
title := d.Get("title").(string)
5854
key := d.Get("key").(string)
59-
ctx := context.Background()
6055

61-
userKey, _, err := client.Users.CreateKey(ctx, &github.Key{
56+
userKey, resp, err := client.Users.CreateKey(ctx, &github.Key{
6257
Title: github.Ptr(title),
6358
Key: github.Ptr(key),
6459
})
6560
if err != nil {
66-
return err
61+
return diag.FromErr(err)
6762
}
6863

69-
d.SetId(strconv.FormatInt(*userKey.ID, 10))
64+
d.SetId(strconv.FormatInt(userKey.GetID(), 10))
65+
66+
if err = d.Set("key_id", userKey.GetID()); err != nil {
67+
return diag.FromErr(err)
68+
}
69+
if err = d.Set("etag", resp.Header.Get("ETag")); err != nil {
70+
return diag.FromErr(err)
71+
}
72+
if err = d.Set("title", userKey.GetTitle()); err != nil {
73+
return diag.FromErr(err)
74+
}
7075

71-
return resourceGithubUserSshKeyRead(d, meta)
76+
return nil
7277
}
7378

74-
func resourceGithubUserSshKeyRead(d *schema.ResourceData, meta any) error {
79+
func resourceGithubUserSshKeyRead(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
7580
client := meta.(*Owner).v3client
7681

77-
id, err := strconv.ParseInt(d.Id(), 10, 64)
82+
keyID := d.Get("key_id").(int64)
83+
_, _, err := client.Users.GetKey(ctx, keyID)
7884
if err != nil {
79-
return unconvertibleIdErr(d.Id(), err)
80-
}
81-
ctx := context.WithValue(context.Background(), ctxId, d.Id())
82-
if !d.IsNewResource() {
83-
ctx = context.WithValue(ctx, ctxEtag, d.Get("etag").(string))
84-
}
85-
86-
key, resp, err := client.Users.GetKey(ctx, id)
87-
if err != nil {
88-
var ghErr *github.ErrorResponse
89-
if errors.As(err, &ghErr) {
85+
if ghErr, ok := err.(*github.ErrorResponse); ok {
9086
if ghErr.Response.StatusCode == http.StatusNotModified {
9187
return nil
9288
}
9389
if ghErr.Response.StatusCode == http.StatusNotFound {
94-
log.Printf("[INFO] Removing user SSH key %s from state because it no longer exists in GitHub",
95-
d.Id())
90+
tflog.Info(ctx, fmt.Sprintf("Removing user SSH key %s from state because it no longer exists in GitHub", d.Id()), map[string]any{
91+
"ssh_key_id": d.Id(),
92+
})
9693
d.SetId("")
9794
return nil
9895
}
9996
}
100-
return err
10197
}
98+
return nil
99+
}
102100

103-
if err = d.Set("etag", resp.Header.Get("ETag")); err != nil {
104-
return err
105-
}
106-
if err = d.Set("title", key.GetTitle()); err != nil {
107-
return err
108-
}
109-
if err = d.Set("key", key.GetKey()); err != nil {
110-
return err
111-
}
112-
if err = d.Set("url", key.GetURL()); err != nil {
113-
return err
114-
}
101+
func resourceGithubUserSshKeyDelete(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
102+
client := meta.(*Owner).v3client
115103

116-
return nil
104+
keyID := d.Get("key_id").(int64)
105+
resp, err := client.Users.DeleteKey(ctx, keyID)
106+
if resp.StatusCode == http.StatusNotFound {
107+
return nil
108+
}
109+
return diag.FromErr(err)
117110
}
118111

119-
func resourceGithubUserSshKeyDelete(d *schema.ResourceData, meta any) error {
112+
func resourceGithubUserSshKeyImport(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) {
120113
client := meta.(*Owner).v3client
121114

122-
id, err := strconv.ParseInt(d.Id(), 10, 64)
115+
keyID, err := strconv.ParseInt(d.Id(), 10, 64)
116+
if err != nil {
117+
return nil, fmt.Errorf("invalid SSH key ID format: %v", err)
118+
}
119+
120+
key, resp, err := client.Users.GetKey(ctx, keyID)
123121
if err != nil {
124-
return unconvertibleIdErr(d.Id(), err)
122+
if ghErr, ok := err.(*github.ErrorResponse); ok {
123+
if ghErr.Response.StatusCode == http.StatusNotFound {
124+
return nil, fmt.Errorf("SSH key with ID %d not found", keyID)
125+
}
126+
}
127+
return nil, err
128+
}
129+
130+
d.SetId(strconv.FormatInt(key.GetID(), 10))
131+
132+
if err = d.Set("key_id", key.GetID()); err != nil {
133+
return nil, err
134+
}
135+
if err = d.Set("etag", resp.Header.Get("ETag")); err != nil {
136+
return nil, err
137+
}
138+
if err = d.Set("title", key.GetTitle()); err != nil {
139+
return nil, err
140+
}
141+
if err = d.Set("key", key.GetKey()); err != nil {
142+
return nil, err
125143
}
126-
ctx := context.WithValue(context.Background(), ctxId, d.Id())
127144

128-
_, err = client.Users.DeleteKey(ctx, id)
129-
return err
145+
return []*schema.ResourceData{d}, nil
130146
}

github/resource_github_user_ssh_key_test.go

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,19 @@ import (
1616
func TestAccGithubUserSshKey(t *testing.T) {
1717
t.Run("creates and destroys a user SSH key without error", func(t *testing.T) {
1818
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
19+
name := fmt.Sprintf(`%s-%s`, testResourcePrefix, randomID)
1920
testKey := newTestKey()
21+
2022
config := fmt.Sprintf(`
2123
resource "github_user_ssh_key" "test" {
22-
title = "tf-acc-test-%s"
23-
key = "%s"
24+
title = "%[1]s"
25+
key = "%[2]s"
2426
}
25-
`, randomID, testKey)
27+
`, name, testKey)
2628

2729
check := resource.ComposeTestCheckFunc(
28-
resource.TestMatchResourceAttr(
29-
"github_user_ssh_key.test", "title",
30-
regexp.MustCompile(randomID),
31-
),
32-
resource.TestMatchResourceAttr(
33-
"github_user_ssh_key.test", "key",
34-
regexp.MustCompile("^ssh-rsa "),
35-
),
36-
resource.TestMatchResourceAttr(
37-
"github_user_ssh_key.test", "url",
38-
regexp.MustCompile("^https://api.github.com/[a-z0-9]+/keys/"),
39-
),
30+
resource.TestMatchResourceAttr("github_user_ssh_key.test", "title", regexp.MustCompile(randomID)),
31+
resource.TestMatchResourceAttr("github_user_ssh_key.test", "key", regexp.MustCompile("^ssh-rsa ")),
4032
)
4133

4234
resource.Test(t, resource.TestCase{
@@ -53,13 +45,15 @@ func TestAccGithubUserSshKey(t *testing.T) {
5345

5446
t.Run("imports an individual account SSH key without error", func(t *testing.T) {
5547
randomID := acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)
48+
name := fmt.Sprintf(`%s-%s`, testResourcePrefix, randomID)
5649
testKey := newTestKey()
50+
5751
config := fmt.Sprintf(`
5852
resource "github_user_ssh_key" "test" {
59-
title = "tf-acc-test-%s"
60-
key = "%s"
53+
title = "%[1]s"
54+
key = "%[2]s"
6155
}
62-
`, randomID, testKey)
56+
`, name, testKey)
6357

6458
check := resource.ComposeTestCheckFunc(
6559
resource.TestCheckResourceAttrSet("github_user_ssh_key.test", "title"),
@@ -87,6 +81,5 @@ func TestAccGithubUserSshKey(t *testing.T) {
8781
func newTestKey() string {
8882
privateKey, _ := rsa.GenerateKey(rand.Reader, 1024)
8983
publicKey, _ := ssh.NewPublicKey(&privateKey.PublicKey)
90-
testKey := strings.TrimRight(string(ssh.MarshalAuthorizedKey(publicKey)), "\n")
91-
return testKey
84+
return strings.TrimRight(string(ssh.MarshalAuthorizedKey(publicKey)), "\n")
9285
}

0 commit comments

Comments
 (0)