Skip to content

Commit

Permalink
Support signed commits
Browse files Browse the repository at this point in the history
  • Loading branch information
Liam Galvin committed Feb 18, 2019
1 parent 8b643ee commit 40331f4
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 110 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,17 @@ Do you use terraform to manage your GitHub organisation? Are you frustrated that

Download the relevant binary from [releases](https://github.com/form3tech-oss/terraform-provider-codeowners/releases) and copy it to `$HOME/.terraform.d/plugins/`.

## Authentication
## Configuration

The following provider block variables are available for configuration:

- `github_token` GitHub auth token - see below section. (read from env var `$GITHUB_TOKEN`)
- `username` Username to use in commits (read from env var `$GITHUB_USERNAME`)
- `email` Email to use in commits - this must match the email in your GPG key if you are signing commits (read from env var `$GITHUB_EMAIL`)
- `gpg_secret_key` The private GPG key to use to sign commits (optional) (read from env var `$GPG_SECRET_KEY`)
- `gpg_passphrase` The passphrase associated with the aforementioned GPG key (optional) (read from env var `$GPG_PASSPHRASE`)

### Authentication

There are two methods for authenticating with this provider.

Expand Down
46 changes: 43 additions & 3 deletions codeowners/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,34 @@ func Provider() *schema.Provider {
DefaultFunc: schema.EnvDefaultFunc("GITHUB_TOKEN", nil),
Sensitive: true,
},
"gpg_passphrase": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Description: "The passphrase associated with your gpg_secret_key, if you have provided one",
DefaultFunc: schema.EnvDefaultFunc("GPG_PASSPHRASE", ""),
Sensitive: true,
},
"gpg_secret_key": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Description: "GPG secret key to use to sign github commits",
DefaultFunc: schema.EnvDefaultFunc("GPG_SECRET_KEY", ""),
Sensitive: true,
},
"email": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: "Email to use for commit messages - if a GPG key is provided, this email must match that used in the key",
DefaultFunc: schema.EnvDefaultFunc("GITHUB_EMAIL", nil),
Sensitive: true,
},
"username": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: "Username to use for commit messages",
DefaultFunc: schema.EnvDefaultFunc("GITHUB_USERNAME", nil),
Sensitive: true,
},
},
ResourcesMap: map[string]*schema.Resource{
"codeowners_file": resourceFile(),
Expand All @@ -26,6 +54,14 @@ func Provider() *schema.Provider {
}
}

type providerConfiguration struct {
client *github.Client
ghUsername string
ghEmail string
gpgKey string
gpgPassphrase string
}

func providerConfigure(d *schema.ResourceData) (interface{}, error) {

ctx := context.Background()
Expand All @@ -34,7 +70,11 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
)
tc := oauth2.NewClient(ctx, ts)

client := github.NewClient(tc)

return client, nil
return &providerConfiguration{
client: github.NewClient(tc),
ghEmail: d.Get("email").(string),
ghUsername: d.Get("username").(string),
gpgKey: d.Get("gpg_secret_key").(string),
gpgPassphrase: d.Get("gpg_passphrase").(string),
}, nil
}
95 changes: 85 additions & 10 deletions codeowners/resource_file.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package codeowners

import (
"context"
"fmt"
"net/http"

"github.com/google/go-github/github"
"github.com/hashicorp/terraform/helper/schema"
Expand Down Expand Up @@ -64,27 +66,61 @@ func resourceFile() *schema.Resource {

func resourceFileRead(d *schema.ResourceData, m interface{}) error {

client := m.(*github.Client)
config := m.(*providerConfiguration)

file := expandFile(d)

ruleset, err := readRulesetForRepo(client, d.Get("branch").(string), file.RepositoryOwner, file.RepositoryName)
getOptions := &github.RepositoryContentGetOptions{}
if d.Get("branch").(string) != "" {
getOptions.Ref = d.Get("branch").(string)
}

ctx := context.Background()
codeOwnerContent, _, rr, err := config.client.Repositories.GetContents(ctx, file.RepositoryOwner, file.RepositoryName, codeownersPath, getOptions)
if err != nil || rr.StatusCode >= 500 {
return fmt.Errorf("failed to retrieve file %s: %v", codeownersPath, err)
}

if rr.StatusCode == http.StatusNotFound {
return fmt.Errorf("file %s does not exist", codeownersPath)
}

raw, err := codeOwnerContent.GetContent()
if err != nil {
return err
return fmt.Errorf("failed to retrieve content for %s: %s", codeownersPath, err)
}

file.Ruleset = ruleset
file.Ruleset = parseRulesFile(raw)

return flattenFile(file, d)
}

func resourceFileCreate(d *schema.ResourceData, m interface{}) error {

client := m.(*github.Client)
config := m.(*providerConfiguration)

file := expandFile(d)

if err := createRulesetForRepo(client, file.Branch, file.RepositoryOwner, file.RepositoryName, file.Ruleset, "Adding CODEOWNERS file"); err != nil {
entries := []github.TreeEntry{
github.TreeEntry{
Path: github.String(codeownersPath),
Content: github.String(string(file.Ruleset.Compile())),
Type: github.String("blob"),
Mode: github.String("100644"),
},
}

if err := createCommit(config.client, &signedCommitOptions{
repoName: file.RepositoryOwner,
repoOwner: file.RepositoryName,
branch: file.Branch,
commitMessage: "Adding CODEOWNERS file",
gpgPassphrase: config.gpgPassphrase,
gpgPrivateKey: config.gpgKey,
username: config.ghUsername,
email: config.ghEmail,
changes: entries,
}); err != nil {
return err
}

Expand All @@ -93,23 +129,62 @@ func resourceFileCreate(d *schema.ResourceData, m interface{}) error {

func resourceFileUpdate(d *schema.ResourceData, m interface{}) error {

client := m.(*github.Client)
config := m.(*providerConfiguration)

file := expandFile(d)

if err := updateRulesetForRepo(client, file.Branch, file.RepositoryOwner, file.RepositoryName, file.Ruleset, "Adding CODEOWNERS file"); err != nil {
entries := []github.TreeEntry{
github.TreeEntry{
Path: github.String(codeownersPath),
Content: github.String(string(file.Ruleset.Compile())),
Type: github.String("blob"),
Mode: github.String("100644"),
},
}

if err := createCommit(config.client, &signedCommitOptions{
repoName: file.RepositoryOwner,
repoOwner: file.RepositoryName,
branch: file.Branch,
commitMessage: "Updating CODEOWNERS file",
gpgPassphrase: config.gpgPassphrase,
gpgPrivateKey: config.gpgKey,
username: config.ghUsername,
email: config.ghEmail,
changes: entries,
}); err != nil {
return err
}

return resourceFileRead(d, m)
}

func resourceFileDelete(d *schema.ResourceData, m interface{}) error {
client := m.(*github.Client)
config := m.(*providerConfiguration)

owner, name := d.Get("repository_owner").(string), d.Get("repository_name").(string)

return deleteRulesetForRepo(client, d.Get("branch").(string), owner, name, "Removing CODEOWNERS file")
ctx := context.Background()

codeOwnerContent, _, rr, err := config.client.Repositories.GetContents(ctx, owner, name, codeownersPath, &github.RepositoryContentGetOptions{})
if err != nil {
return fmt.Errorf("failed to retrieve file %s: %v", codeownersPath, err)
}

if rr.StatusCode == http.StatusNotFound { // resource already removed
return nil
}

options := &github.RepositoryContentFileOptions{
Message: github.String("Removing CODEOWNERS file"),
SHA: codeOwnerContent.SHA,
}
if d.Get("branch").(string) != "" {
options.Branch = github.String(d.Get("branch").(string))
}

_, _, err = config.client.Repositories.DeleteFile(ctx, owner, name, codeownersPath, options)
return err
}

func flattenFile(file *File, d *schema.ResourceData) error {
Expand Down
Loading

0 comments on commit 40331f4

Please sign in to comment.