Skip to content

Commit e6ede50

Browse files
committed
feat: support signed commits for resource 'github_repository_file'
1 parent 5752b25 commit e6ede50

File tree

247 files changed

+196771
-19
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

247 files changed

+196771
-19
lines changed

github/resource_github_repository_file.go

+261-12
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ package github
22

33
import (
44
"context"
5+
"io"
56
"log"
67
"net/url"
78
"strings"
89

910
"fmt"
1011

12+
"github.com/ProtonMail/gopenpgp/v2/crypto"
1113
"github.com/google/go-github/v57/github"
1214
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
1315
)
@@ -115,10 +117,75 @@ func resourceGithubRepositoryFile() *schema.Resource {
115117
Description: "Enable overwriting existing files, defaults to \"false\"",
116118
Default: false,
117119
},
120+
"modify_with_api": {
121+
Type: schema.TypeBool,
122+
Optional: true,
123+
Description: "Enable to modify github files with the github contents API, rather than git.",
124+
Default: true,
125+
},
126+
"pgp_signing_key": {
127+
Type: schema.TypeString,
128+
Optional: true,
129+
Description: "PGP signing key used to sign commits.",
130+
Sensitive: true,
131+
},
132+
"pgp_signing_key_passphrase": {
133+
Type: schema.TypeString,
134+
Optional: true,
135+
Description: "Passphrase to unlock PGP signing key used to sign commits.",
136+
Sensitive: true,
137+
},
118138
},
119139
}
120140
}
121141

142+
func resourceGithubRepositoryFileCreateCommitOptions(d *schema.ResourceData) (*github.CreateCommitOptions, error) {
143+
opts := &github.CreateCommitOptions{}
144+
145+
pgpSigningKey, hasPgpSigningKey := d.GetOk("pgp_signing_key")
146+
147+
if hasPgpSigningKey {
148+
privateKeyObj, err := crypto.NewKeyFromArmored(pgpSigningKey.(string))
149+
if err != nil {
150+
return nil, err
151+
}
152+
153+
isLocked, err := privateKeyObj.IsLocked()
154+
if err != nil {
155+
return nil, err
156+
}
157+
if isLocked {
158+
pgpSigningKeyPassphrase, hasPgpSigningKeyPassphrase := d.GetOk("pgp_signing_key_passphrase")
159+
if hasPgpSigningKeyPassphrase {
160+
privateKeyObj, err = privateKeyObj.Unlock([]byte(pgpSigningKeyPassphrase.(string)))
161+
if err != nil {
162+
return nil, err
163+
}
164+
} else {
165+
return nil, fmt.Errorf("cannot unlock pgp_signing_key, configure pgp_signing_key_passphrase")
166+
}
167+
}
168+
169+
signingKeyRing, err := crypto.NewKeyRing(privateKeyObj)
170+
171+
opts.Signer = github.MessageSignerFunc(func(w io.Writer, r io.Reader) error {
172+
signature, err := signingKeyRing.SignDetachedStream(r)
173+
if err != nil {
174+
return err
175+
}
176+
armoredSignature, err := signature.GetArmored()
177+
if err != nil {
178+
return err
179+
}
180+
181+
_, err = w.Write([]byte(armoredSignature))
182+
return err
183+
})
184+
}
185+
186+
return opts, nil
187+
}
188+
122189
func resourceGithubRepositoryFileOptions(d *schema.ResourceData) (*github.RepositoryContentFileOptions, error) {
123190
opts := &github.RepositoryContentFileOptions{
124191
Content: []byte(*github.String(d.Get("content").(string))),
@@ -214,13 +281,68 @@ func resourceGithubRepositoryFileCreate(d *schema.ResourceData, meta interface{}
214281
}
215282

216283
// Create a new or overwritten file
217-
create, _, err := client.Repositories.CreateFile(ctx, owner, repo, file, opts)
218-
if err != nil {
219-
return err
284+
modifyWithApi := d.Get("modify_with_api")
285+
if modifyWithApi.(bool) {
286+
create, _, err := client.Repositories.CreateFile(ctx, owner, repo, file, opts)
287+
if err != nil {
288+
return err
289+
}
290+
d.Set("commit_sha", create.Commit.GetSHA())
291+
} else {
292+
commitOpts, err := resourceGithubRepositoryFileCreateCommitOptions(d)
293+
if err != nil {
294+
return err
295+
}
296+
297+
ref, _, err := client.Git.GetRef(ctx, owner, repo, "refs/heads/"+*opts.Branch)
298+
if err != nil {
299+
return err
300+
}
301+
302+
tree, _, err := client.Git.CreateTree(
303+
ctx, owner, repo, *ref.Object.SHA, []*github.TreeEntry{
304+
{
305+
Path: github.String(d.Get("file").(string)),
306+
Type: github.String("blob"),
307+
Content: github.String(d.Get("content").(string)),
308+
Mode: github.String("100644"),
309+
},
310+
},
311+
)
312+
if err != nil {
313+
return err
314+
}
315+
316+
parent, _, err := client.Repositories.GetCommit(ctx, owner, repo, *ref.Object.SHA, nil)
317+
if err != nil {
318+
return err
319+
}
320+
// This is not always populated, but is needed.
321+
parent.Commit.SHA = parent.SHA
322+
323+
commit := github.Commit{
324+
Author: opts.Author,
325+
Committer: opts.Committer,
326+
Message: opts.Message,
327+
Tree: tree,
328+
Parents: []*github.Commit{
329+
parent.Commit,
330+
},
331+
}
332+
newCommit, _, err := client.Git.CreateCommit(ctx, owner, repo, &commit, commitOpts)
333+
if err != nil {
334+
return err
335+
}
336+
337+
ref.Object.SHA = newCommit.SHA
338+
_, _, err = client.Git.UpdateRef(ctx, owner, repo, ref, false)
339+
if err != nil {
340+
return err
341+
}
342+
d.Set("commit_sha", newCommit.SHA)
220343
}
221344

222345
d.SetId(fmt.Sprintf("%s/%s", repo, file))
223-
d.Set("commit_sha", create.Commit.GetSHA())
224346

225347
return resourceGithubRepositoryFileRead(d, meta)
226348
}
@@ -334,12 +456,66 @@ func resourceGithubRepositoryFileUpdate(d *schema.ResourceData, meta interface{}
334456
opts.Message = &m
335457
}
336458

337-
create, _, err := client.Repositories.CreateFile(ctx, owner, repo, file, opts)
338-
if err != nil {
339-
return err
340-
}
459+
modifyWithApi := d.Get("modify_with_api")
460+
if modifyWithApi.(bool) {
461+
create, _, err := client.Repositories.CreateFile(ctx, owner, repo, file, opts)
462+
if err != nil {
463+
return err
464+
}
465+
d.Set("commit_sha", create.GetSHA())
466+
} else {
467+
commitOpts, err := resourceGithubRepositoryFileCreateCommitOptions(d)
468+
if err != nil {
469+
return err
470+
}
471+
472+
ref, _, err := client.Git.GetRef(ctx, owner, repo, "refs/heads/"+*opts.Branch)
473+
if err != nil {
474+
return err
475+
}
476+
477+
tree, _, err := client.Git.CreateTree(
478+
ctx, owner, repo, *ref.Object.SHA, []*github.TreeEntry{
479+
{
480+
Path: github.String(d.Get("file").(string)),
481+
Type: github.String("blob"),
482+
Content: github.String(d.Get("content").(string)),
483+
Mode: github.String("100644"),
484+
},
485+
},
486+
)
487+
if err != nil {
488+
return err
489+
}
341490

342-
d.Set("commit_sha", create.GetSHA())
491+
parent, _, err := client.Repositories.GetCommit(ctx, owner, repo, *ref.Object.SHA, nil)
492+
if err != nil {
493+
return err
494+
}
495+
// This is not always populated, but is needed.
496+
parent.Commit.SHA = parent.SHA
497+
498+
commit := github.Commit{
499+
Author: opts.Author,
500+
Committer: opts.Committer,
501+
Message: opts.Message,
502+
Tree: tree,
503+
Parents: []*github.Commit{
504+
parent.Commit,
505+
},
506+
}
507+
newCommit, _, err := client.Git.CreateCommit(ctx, owner, repo, &commit, commitOpts)
508+
if err != nil {
509+
return err
510+
}
511+
512+
ref.Object.SHA = newCommit.SHA
513+
_, _, err = client.Git.UpdateRef(ctx, owner, repo, ref, false)
514+
if err != nil {
515+
return err
516+
}
517+
d.Set("commit_sha", newCommit.SHA)
518+
}
343519

344520
return resourceGithubRepositoryFileRead(d, meta)
345521
}
@@ -373,9 +549,82 @@ func resourceGithubRepositoryFileDelete(d *schema.ResourceData, meta interface{}
373549
opts.Branch = &branch
374550
}
375551

376-
_, _, err := client.Repositories.DeleteFile(ctx, owner, repo, file, opts)
377-
if err != nil {
378-
return nil
552+
modifyWithApi := d.Get("modify_with_api")
553+
if modifyWithApi.(bool) {
554+
_, _, err := client.Repositories.DeleteFile(ctx, owner, repo, file, opts)
555+
if err != nil {
556+
return nil
557+
}
558+
} else {
559+
commitOpts, err := resourceGithubRepositoryFileCreateCommitOptions(d)
560+
if err != nil {
561+
return err
562+
}
563+
564+
ref, _, err := client.Git.GetRef(ctx, owner, repo, "refs/heads/"+*opts.Branch)
565+
if err != nil {
566+
return err
567+
}
568+
569+
tree, _, err := client.Git.CreateTree(
570+
ctx, owner, repo, *ref.Object.SHA, []*github.TreeEntry{
571+
{
572+
Path: github.String(d.Get("file").(string)),
573+
Type: github.String("blob"),
574+
Mode: github.String("100644"),
575+
SHA: nil,
576+
},
577+
},
578+
)
579+
if err != nil {
580+
return err
581+
}
582+
583+
parent, _, err := client.Repositories.GetCommit(ctx, owner, repo, *ref.Object.SHA, nil)
584+
if err != nil {
585+
return err
586+
}
587+
// This is not always populated, but is needed.
588+
parent.Commit.SHA = parent.SHA
589+
590+
commitAuthor, hasCommitAuthor := d.GetOk("commit_author")
591+
commitEmail, hasCommitEmail := d.GetOk("commit_email")
592+
593+
if hasCommitAuthor && !hasCommitEmail {
594+
return fmt.Errorf("cannot set commit_author without setting commit_email")
595+
}
596+
597+
if hasCommitEmail && !hasCommitAuthor {
598+
return fmt.Errorf("cannot set commit_email without setting commit_author")
599+
}
600+
601+
if hasCommitAuthor && hasCommitEmail {
602+
name := commitAuthor.(string)
603+
mail := commitEmail.(string)
604+
opts.Author = &github.CommitAuthor{Name: &name, Email: &mail}
605+
opts.Committer = &github.CommitAuthor{Name: &name, Email: &mail}
606+
}
607+
608+
commit := github.Commit{
609+
Author: opts.Author,
610+
Committer: opts.Committer,
611+
Message: opts.Message,
612+
Tree: tree,
613+
Parents: []*github.Commit{
614+
parent.Commit,
615+
},
616+
}
617+
newCommit, _, err := client.Git.CreateCommit(ctx, owner, repo, &commit, commitOpts)
618+
if err != nil {
619+
return err
620+
}
621+
622+
ref.Object.SHA = newCommit.SHA
623+
_, _, err = client.Git.UpdateRef(ctx, owner, repo, ref, false)
624+
if err != nil {
625+
return err
626+
}
627+
d.Set("commit_sha", newCommit.SHA)
379628
}
380629

381630
return nil

0 commit comments

Comments
 (0)