From aba9698cf8456eaffd988dc81c1441928392dc43 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Mon, 22 Jun 2015 09:23:42 -0700 Subject: [PATCH] rundeck_public_key resource type. --- builtin/providers/rundeck/provider.go | 2 +- .../providers/rundeck/resource_public_key.go | 148 ++++++++++++++++++ .../rundeck/resource_public_key_test.go | 99 ++++++++++++ 3 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 builtin/providers/rundeck/resource_public_key.go create mode 100644 builtin/providers/rundeck/resource_public_key_test.go diff --git a/builtin/providers/rundeck/provider.go b/builtin/providers/rundeck/provider.go index 5e547e15e..4a59d8cfe 100644 --- a/builtin/providers/rundeck/provider.go +++ b/builtin/providers/rundeck/provider.go @@ -33,7 +33,7 @@ func Provider() terraform.ResourceProvider { "rundeck_project": resourceRundeckProject(), //"rundeck_job": resourceRundeckJob(), //"rundeck_private_key": resourceRundeckPrivateKey(), - //"rundeck_public_key": resourceRundeckPublicKey(), + "rundeck_public_key": resourceRundeckPublicKey(), }, ConfigureFunc: providerConfigure, diff --git a/builtin/providers/rundeck/resource_public_key.go b/builtin/providers/rundeck/resource_public_key.go new file mode 100644 index 000000000..11ee2a3f9 --- /dev/null +++ b/builtin/providers/rundeck/resource_public_key.go @@ -0,0 +1,148 @@ +package rundeck + +import ( + "github.com/hashicorp/terraform/helper/schema" + + "github.com/apparentlymart/go-rundeck-api/rundeck" +) + +func resourceRundeckPublicKey() *schema.Resource { + return &schema.Resource{ + Create: CreatePublicKey, + Update: UpdatePublicKey, + Delete: DeletePublicKey, + Exists: PublicKeyExists, + Read: ReadPublicKey, + + 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, + Optional: true, + Computed: true, + Description: "The public key data to store, in the usual OpenSSH public key file format", + }, + + "url": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Description: "URL at which the key content can be retrieved", + }, + + "delete": &schema.Schema{ + Type: schema.TypeBool, + Computed: true, + Description: "True if the key should be deleted when the resource is deleted. Defaults to true if key_material is provided in the configuration.", + }, + }, + } +} + +func CreatePublicKey(d *schema.ResourceData, meta interface{}) error { + client := meta.(*rundeck.Client) + + path := d.Get("path").(string) + keyMaterial := d.Get("key_material").(string) + + if keyMaterial != "" { + err := client.CreatePublicKey(path, keyMaterial) + if err != nil { + return err + } + d.Set("delete", true) + } + + d.SetId(path) + + return ReadPublicKey(d, meta) +} + +func UpdatePublicKey(d *schema.ResourceData, meta interface{}) error { + client := meta.(*rundeck.Client) + + if d.HasChange("key_material") { + path := d.Get("path").(string) + keyMaterial := d.Get("key_material").(string) + + err := client.ReplacePublicKey(path, keyMaterial) + if err != nil { + return err + } + } + + return ReadPublicKey(d, meta) +} + +func DeletePublicKey(d *schema.ResourceData, meta interface{}) error { + client := meta.(*rundeck.Client) + + path := d.Id() + + // Since this resource can be used both to create and to read existing + // public keys, we'll only actually delete the key if we remember that + // we created the key in the first place, or if the user explicitly + // opted in to have an existing key deleted. + if d.Get("delete").(bool) { + // 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 ReadPublicKey(d *schema.ResourceData, meta interface{}) error { + client := meta.(*rundeck.Client) + + path := d.Id() + + key, err := client.GetKeyMeta(path) + if err != nil { + return err + } + + keyMaterial, err := client.GetKeyContent(path) + if err != nil { + return err + } + + d.Set("key_material", keyMaterial) + d.Set("url", key.URL) + + return nil +} + +func PublicKeyExists(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 != "public" { + // 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_public_key_test.go b/builtin/providers/rundeck/resource_public_key_test.go new file mode 100644 index 000000000..c8b9a1865 --- /dev/null +++ b/builtin/providers/rundeck/resource_public_key_test.go @@ -0,0 +1,99 @@ +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 TestAccPublicKey_basic(t *testing.T) { + var key rundeck.KeyMeta + var keyMaterial string + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccPublicKeyCheckDestroy(&key), + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccPublicKeyConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccPublicKeyCheckExists("rundeck_public_key.test", &key, &keyMaterial), + func(s *terraform.State) error { + if expected := "keys/terraform_acceptance_tests/public_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/public_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 := "public"; key.KeyType != expected { + return fmt.Errorf("wrong key type; expected %v, got %v", expected, key.KeyType) + } + if !strings.Contains(keyMaterial, "test+public+key+for+terraform") { + return fmt.Errorf("wrong key material") + } + return nil + }, + ), + }, + }, + }) +} + +func testAccPublicKeyCheckDestroy(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 testAccPublicKeyCheckExists(rn string, key *rundeck.KeyMeta, keyMaterial *string) 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 + + *keyMaterial, err = client.GetKeyContent(rs.Primary.ID) + if err != nil { + return fmt.Errorf("error getting key contents: %s", err) + } + + return nil + } +} + +const testAccPublicKeyConfig_basic = ` +resource "rundeck_public_key" "test" { + path = "terraform_acceptance_tests/public_key" + key_material = "ssh-rsa test+public+key+for+terraform nobody@nowhere" +} +`