Skip to content

Commit

Permalink
Adds support for creating KMS CryptoKeys resources
Browse files Browse the repository at this point in the history
  • Loading branch information
amfarrell committed Nov 6, 2017
1 parent 08f4539 commit 1c4023a
Show file tree
Hide file tree
Showing 3 changed files with 393 additions and 0 deletions.
1 change: 1 addition & 0 deletions google/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ func Provider() terraform.ResourceProvider {
"google_logging_folder_sink": resourceLoggingFolderSink(),
"google_logging_project_sink": resourceLoggingProjectSink(),
"google_kms_key_ring": resourceKmsKeyRing(),
"google_kms_crypto_key": resourceKmsCryptoKey(),
"google_sourcerepo_repository": resourceSourceRepoRepository(),
"google_spanner_instance": resourceSpannerInstance(),
"google_spanner_database": resourceSpannerDatabase(),
Expand Down
187 changes: 187 additions & 0 deletions google/resource_kms_crypto_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package google

import (
"fmt"
"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/cloudkms/v1"
"log"
"regexp"
"strings"
)

func resourceKmsCryptoKey() *schema.Resource {
return &schema.Resource{
Create: resourceKmsCryptoKeyCreate,
Read: resourceKmsCryptoKeyRead,
Delete: resourceKmsCryptoKeyDelete,
Importer: &schema.ResourceImporter{
State: resourceKmsCryptoKeyImportState,
},

Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"location": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"key_ring": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"project": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},
},
}
}

type kmsCryptoKeyId struct {
Project string
Location string
KeyRing string
Name string
}
// TODO: Add the info about rotation frequency and start time.

func (s *kmsCryptoKeyId) cryptoKeyId() string {
return fmt.Sprintf("projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s", s.Project, s.Location, s.KeyRing, s.Name)
}

func (s *kmsCryptoKeyId) parentId() string {
return fmt.Sprintf("projects/%s/locations/%s/keyRings/%s", s.Project, s.Location, s.KeyRing)
}

func (s *kmsCryptoKeyId) terraformId() string {
return fmt.Sprintf("%s/%s/%s/%s", s.Project, s.Location, s.KeyRing, s.Name)
}

func resourceKmsCryptoKeyCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)

project, err := getProject(d, config)
if err != nil {
return err
}

cryptoKeyId := &kmsCryptoKeyId{
Project: project,
Location: d.Get("location").(string),
KeyRing: d.Get("key_ring").(string),
Name: d.Get("name").(string),
}

cryptoKey, err := config.clientKms.Projects.Locations.KeyRings.CryptoKeys.Create(cryptoKeyId.parentId(), &cloudkms.CryptoKey{Purpose: "ENCRYPT_DECRYPT"}).CryptoKeyId(cryptoKeyId.Name).Do()

if err != nil {
return fmt.Errorf("Error creating CryptoKey: %s", err)
}

log.Printf("[DEBUG] Created CryptoKey %s", cryptoKey.Name)

d.SetId(cryptoKeyId.terraformId())

return resourceKmsCryptoKeyRead(d, meta)
}

func resourceKmsCryptoKeyRead(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)

cryptoKeyId, err := parseKmsCryptoKeyId(d.Id(), config)
if err != nil {
return err
}

log.Printf("[DEBUG] Executing read for KMS CryptoKey %s", cryptoKeyId.cryptoKeyId())

_, err = config.clientKms.Projects.Locations.KeyRings.CryptoKeys.Get(cryptoKeyId.cryptoKeyId()).Do()

if err != nil {
return fmt.Errorf("Error reading CryptoKey: %s", err)
}

return nil
}

/*
Because KMS CryptoKey resources cannot be deleted on GCP, we are only going to remove it from state.
Re-creation of this resource through Terraform will produce an error.
*/

func resourceKmsCryptoKeyDelete(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config)

cryptoKeyId, err := parseKmsCryptoKeyId(d.Id(), config)
if err != nil {
return err
}

log.Printf("[WARNING] KMS CryptoKey resources cannot be deleted from GCP. This CryptoKey %s will be removed from Terraform state, but will still be present on the server.", cryptoKeyId.cryptoKeyId())

d.SetId("")

return nil
}

func parseKmsCryptoKeyId(id string, config *Config) (*kmsCryptoKeyId, error) {
parts := strings.Split(id, "/")

cryptoKeyIdRegex := regexp.MustCompile("^([a-z0-9-]+)/([a-z0-9-])+/([a-zA-Z0-9_-]{1,63})+/([a-zA-Z0-9_-]{1,63})$")
cryptoKeyIdWithoutProjectRegex := regexp.MustCompile("^([a-z0-9-])+/([a-zA-Z0-9_-]{1,63})+/([a-zA-Z0-9_-]{1,63})$")

if cryptoKeyIdRegex.MatchString(id) {
return &kmsCryptoKeyId{
Project: parts[0],
Location: parts[1],
KeyRing: parts[2],
Name: parts[3],
}, nil
}

if cryptoKeyIdWithoutProjectRegex.MatchString(id) {
if config.Project == "" {
return nil, fmt.Errorf("The default project for the provider must be set when using the `{location}/{cryptoKeyName}` id format.")
}

return &kmsCryptoKeyId{
Project: config.Project,
Location: parts[0],
KeyRing: parts[1],
Name: parts[2],
}, nil
}

return nil, fmt.Errorf("Invalid CryptoKey id format, expecting `{projectId}/{locationId}/{KeyringName}/{cryptoKeyName}` or `{locationId}/{KeyringName}/{cryptoKeyName}.`")
}

func resourceKmsCryptoKeyImportState(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
config := meta.(*Config)

cryptoKeyId, err := parseKmsCryptoKeyId(d.Id(), config)
if err != nil {
return nil, err
}

d.Set("name", cryptoKeyId.Name)
d.Set("location", cryptoKeyId.Location)
d.Set("key_ring", cryptoKeyId.KeyRing)

if config.Project != cryptoKeyId.Project {
d.Set("project", cryptoKeyId.Project)
}

if d.Get("purpose") == "" {
d.Set("purpose", "ENCRYPT_DECRYPT")
}

d.SetId(cryptoKeyId.terraformId())

return []*schema.ResourceData{d}, nil
}
Loading

0 comments on commit 1c4023a

Please sign in to comment.