@@ -2,12 +2,14 @@ package github
2
2
3
3
import (
4
4
"context"
5
+ "io"
5
6
"log"
6
7
"net/url"
7
8
"strings"
8
9
9
10
"fmt"
10
11
12
+ "github.com/ProtonMail/gopenpgp/v2/crypto"
11
13
"github.com/google/go-github/v57/github"
12
14
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
13
15
)
@@ -115,10 +117,75 @@ func resourceGithubRepositoryFile() *schema.Resource {
115
117
Description : "Enable overwriting existing files, defaults to \" false\" " ,
116
118
Default : false ,
117
119
},
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
+ },
118
138
},
119
139
}
120
140
}
121
141
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
+
122
189
func resourceGithubRepositoryFileOptions (d * schema.ResourceData ) (* github.RepositoryContentFileOptions , error ) {
123
190
opts := & github.RepositoryContentFileOptions {
124
191
Content : []byte (* github .String (d .Get ("content" ).(string ))),
@@ -214,13 +281,68 @@ func resourceGithubRepositoryFileCreate(d *schema.ResourceData, meta interface{}
214
281
}
215
282
216
283
// 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 )
220
343
}
221
344
222
345
d .SetId (fmt .Sprintf ("%s/%s" , repo , file ))
223
- d .Set ("commit_sha" , create .Commit .GetSHA ())
224
346
225
347
return resourceGithubRepositoryFileRead (d , meta )
226
348
}
@@ -334,12 +456,66 @@ func resourceGithubRepositoryFileUpdate(d *schema.ResourceData, meta interface{}
334
456
opts .Message = & m
335
457
}
336
458
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
+ }
341
490
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
+ }
343
519
344
520
return resourceGithubRepositoryFileRead (d , meta )
345
521
}
@@ -373,9 +549,82 @@ func resourceGithubRepositoryFileDelete(d *schema.ResourceData, meta interface{}
373
549
opts .Branch = & branch
374
550
}
375
551
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 )
379
628
}
380
629
381
630
return nil
0 commit comments