diff --git a/builtin/providers/rundeck/provider.go b/builtin/providers/rundeck/provider.go index 4a59d8cfe..71506ef1f 100644 --- a/builtin/providers/rundeck/provider.go +++ b/builtin/providers/rundeck/provider.go @@ -32,7 +32,7 @@ func Provider() terraform.ResourceProvider { ResourcesMap: map[string]*schema.Resource{ "rundeck_project": resourceRundeckProject(), //"rundeck_job": resourceRundeckJob(), - //"rundeck_private_key": resourceRundeckPrivateKey(), + "rundeck_private_key": resourceRundeckPrivateKey(), "rundeck_public_key": resourceRundeckPublicKey(), }, diff --git a/builtin/providers/rundeck/resource_private_key.go b/builtin/providers/rundeck/resource_private_key.go new file mode 100644 index 000000000..a717f85f1 --- /dev/null +++ b/builtin/providers/rundeck/resource_private_key.go @@ -0,0 +1,114 @@ +package rundeck + +import ( + "crypto/sha1" + "encoding/hex" + + "github.com/hashicorp/terraform/helper/schema" + + "github.com/apparentlymart/go-rundeck-api/rundeck" +) + +func resourceRundeckPrivateKey() *schema.Resource { + return &schema.Resource{ + Create: CreateOrUpdatePrivateKey, + Update: CreateOrUpdatePrivateKey, + Delete: DeletePrivateKey, + Exists: PrivateKeyExists, + Read: ReadPrivateKey, + + Schema: map[string]*schema.Schema{ + "path": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "Path to the key within the key store", + ForceNew: true, + }, + + "key_material": &schema.Schema{ + Type: schema.TypeString, + Required: true, + Description: "The private key material to store, in PEM format", + StateFunc: func(v interface{}) string { + switch v.(type) { + case string: + hash := sha1.Sum([]byte(v.(string))) + return hex.EncodeToString(hash[:]) + default: + return "" + } + }, + }, + }, + } +} + +func CreateOrUpdatePrivateKey(d *schema.ResourceData, meta interface{}) error { + client := meta.(*rundeck.Client) + + path := d.Get("path").(string) + keyMaterial := d.Get("key_material").(string) + + var err error + + if d.Id() != "" { + err = client.ReplacePrivateKey(path, keyMaterial) + } else { + err = client.CreatePrivateKey(path, keyMaterial) + } + + if err != nil { + return err + } + + d.SetId(path) + + return ReadPrivateKey(d, meta) +} + +func DeletePrivateKey(d *schema.ResourceData, meta interface{}) error { + client := meta.(*rundeck.Client) + + path := d.Id() + + // The only "delete" call we have is oblivious to key type, but + // that's okay since our Exists implementation makes sure that we + // won't try to delete a key of the wrong type since we'll pretend + // that it's already been deleted. + err := client.DeleteKey(path) + if err != nil { + return err + } + + d.SetId("") + return nil +} + +func ReadPrivateKey(d *schema.ResourceData, meta interface{}) error { + // Nothing to read for a private key: existence is all we need to + // worry about, and PrivateKeyExists took care of that. + return nil +} + +func PrivateKeyExists(d *schema.ResourceData, meta interface{}) (bool, error) { + client := meta.(*rundeck.Client) + + path := d.Id() + + key, err := client.GetKeyMeta(path) + if err != nil { + if _, ok := err.(rundeck.NotFoundError); ok { + err = nil + } + return false, err + } + + if key.KeyType != "private" { + // If the key type isn't public then as far as this resource is + // concerned it doesn't exist. (We'll fail properly when we try to + // create a key where one already exists.) + return false, nil + } + + return true, nil +} diff --git a/builtin/providers/rundeck/resource_private_key_test.go b/builtin/providers/rundeck/resource_private_key_test.go new file mode 100644 index 000000000..da2dad67f --- /dev/null +++ b/builtin/providers/rundeck/resource_private_key_test.go @@ -0,0 +1,92 @@ +package rundeck + +import ( + "fmt" + "strings" + "testing" + + "github.com/apparentlymart/go-rundeck-api/rundeck" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccPrivateKey_basic(t *testing.T) { + var key rundeck.KeyMeta + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccPrivateKeyCheckDestroy(&key), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccPrivateKeyConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccPrivateKeyCheckExists("rundeck_private_key.test", &key), + func(s *terraform.State) error { + if expected := "keys/terraform_acceptance_tests/private_key"; key.Path != expected { + return fmt.Errorf("wrong path; expected %v, got %v", expected, key.Path) + } + if !strings.HasSuffix(key.URL, "/storage/keys/terraform_acceptance_tests/private_key") { + return fmt.Errorf("wrong URL; expected to end with the key path") + } + if expected := "file"; key.ResourceType != expected { + return fmt.Errorf("wrong resource type; expected %v, got %v", expected, key.ResourceType) + } + if expected := "private"; key.KeyType != expected { + return fmt.Errorf("wrong key type; expected %v, got %v", expected, key.KeyType) + } + // Rundeck won't let us re-retrieve a private key payload, so we can't test + // that the key material was submitted and stored correctly. + return nil + }, + ), + }, + }, + }) +} + +func testAccPrivateKeyCheckDestroy(key *rundeck.KeyMeta) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := testAccProvider.Meta().(*rundeck.Client) + _, err := client.GetKeyMeta(key.Path) + if err == nil { + return fmt.Errorf("key still exists") + } + if _, ok := err.(*rundeck.NotFoundError); !ok { + return fmt.Errorf("got something other than NotFoundError (%v) when getting key", err) + } + + return nil + } +} + +func testAccPrivateKeyCheckExists(rn string, key *rundeck.KeyMeta) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[rn] + if !ok { + return fmt.Errorf("resource not found: %s", rn) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("key id not set") + } + + client := testAccProvider.Meta().(*rundeck.Client) + gotKey, err := client.GetKeyMeta(rs.Primary.ID) + if err != nil { + return fmt.Errorf("error getting key metadata: %s", err) + } + + *key = *gotKey + + return nil + } +} + +const testAccPrivateKeyConfig_basic = ` +resource "rundeck_private_key" "test" { + path = "terraform_acceptance_tests/private_key" + key_material = "this is not a real private key" +} +`