diff --git a/aws/resource_aws_iam_user_ssh_key.go b/aws/resource_aws_iam_user_ssh_key.go index 0f6feffab67..241c3040f8e 100644 --- a/aws/resource_aws_iam_user_ssh_key.go +++ b/aws/resource_aws_iam_user_ssh_key.go @@ -3,6 +3,7 @@ package aws import ( "fmt" "log" + "strings" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" @@ -18,6 +19,9 @@ func resourceAwsIamUserSshKey() *schema.Resource { Read: resourceAwsIamUserSshKeyRead, Update: resourceAwsIamUserSshKeyUpdate, Delete: resourceAwsIamUserSshKeyDelete, + Importer: &schema.ResourceImporter{ + State: resourceAwsIamUserSshKeyImport, + }, Schema: map[string]*schema.Schema{ "ssh_public_key_id": { @@ -37,6 +41,13 @@ func resourceAwsIamUserSshKey() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, + DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { + if d.Get("encoding").(string) == "SSH" { + old = cleanSshKey(old) + new = cleanSshKey(new) + } + return strings.Trim(old, "\n") == strings.Trim(new, "\n") + }, }, "encoding": { @@ -82,10 +93,11 @@ func resourceAwsIamUserSshKeyCreate(d *schema.ResourceData, meta interface{}) er func resourceAwsIamUserSshKeyRead(d *schema.ResourceData, meta interface{}) error { iamconn := meta.(*AWSClient).iamconn username := d.Get("username").(string) + encoding := d.Get("encoding").(string) request := &iam.GetSSHPublicKeyInput{ UserName: aws.String(username), SSHPublicKeyId: aws.String(d.Id()), - Encoding: aws.String(d.Get("encoding").(string)), + Encoding: aws.String(encoding), } getResp, err := iamconn.GetSSHPublicKey(request) @@ -98,9 +110,15 @@ func resourceAwsIamUserSshKeyRead(d *schema.ResourceData, meta interface{}) erro return fmt.Errorf("Error reading IAM User SSH Key %s: %s", d.Id(), err) } + publicKey := *getResp.SSHPublicKey.SSHPublicKeyBody + if encoding == "SSH" { + publicKey = cleanSshKey(publicKey) + } + d.Set("fingerprint", getResp.SSHPublicKey.Fingerprint) d.Set("status", getResp.SSHPublicKey.Status) d.Set("ssh_public_key_id", getResp.SSHPublicKey.SSHPublicKeyId) + d.Set("public_key", publicKey) return nil } @@ -142,3 +160,32 @@ func resourceAwsIamUserSshKeyDelete(d *schema.ResourceData, meta interface{}) er } return nil } + +func resourceAwsIamUserSshKeyImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + idParts := strings.SplitN(d.Id(), ":", 3) + + if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" { + return nil, fmt.Errorf("unexpected format of ID (%q), UserName:SSHPublicKeyId:Encoding", d.Id()) + } + + username := idParts[0] + sshPublicKeyId := idParts[1] + encoding := idParts[2] + + d.Set("username", username) + d.Set("ssh_public_key_id", sshPublicKeyId) + d.Set("encoding", encoding) + d.SetId(sshPublicKeyId) + + return []*schema.ResourceData{d}, nil +} + +func cleanSshKey(key string) string { + // Remove comments from SSH Keys + // Comments are anything after "ssh-rsa XXXX" where XXXX is the key. + parts := strings.Split(key, " ") + if len(parts) > 2 { + parts = parts[0:2] + } + return strings.Join(parts, " ") +} diff --git a/aws/resource_aws_iam_user_ssh_key_test.go b/aws/resource_aws_iam_user_ssh_key_test.go index f2e4b3034a8..86b77d404fd 100644 --- a/aws/resource_aws_iam_user_ssh_key_test.go +++ b/aws/resource_aws_iam_user_ssh_key_test.go @@ -17,6 +17,7 @@ func TestAccAWSUserSSHKey_basic(t *testing.T) { ri := acctest.RandInt() config := fmt.Sprintf(testAccAWSSSHKeyConfig_sshEncoding, ri) + resourceName := "aws_iam_user_ssh_key.user" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -26,9 +27,15 @@ func TestAccAWSUserSSHKey_basic(t *testing.T) { { Config: config, Check: resource.ComposeTestCheckFunc( - testAccCheckAWSUserSSHKeyExists("aws_iam_user_ssh_key.user", "Inactive", &conf), + testAccCheckAWSUserSSHKeyExists(resourceName, "Inactive", &conf), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccAWSUserSSHKeyImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, }, }) } @@ -38,6 +45,7 @@ func TestAccAWSUserSSHKey_pemEncoding(t *testing.T) { ri := acctest.RandInt() config := fmt.Sprintf(testAccAWSSSHKeyConfig_pemEncoding, ri) + resourceName := "aws_iam_user_ssh_key.user" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -47,9 +55,15 @@ func TestAccAWSUserSSHKey_pemEncoding(t *testing.T) { { Config: config, Check: resource.ComposeTestCheckFunc( - testAccCheckAWSUserSSHKeyExists("aws_iam_user_ssh_key.user", "Active", &conf), + testAccCheckAWSUserSSHKeyExists(resourceName, "Active", &conf), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccAWSUserSSHKeyImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, }, }) } @@ -122,6 +136,21 @@ func testAccCheckAWSUserSSHKeyExists(n, status string, res *iam.GetSSHPublicKeyO } } +func testAccAWSUserSSHKeyImportStateIdFunc(resourceName string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return "", fmt.Errorf("not found: %s", resourceName) + } + + username := rs.Primary.Attributes["username"] + sshPublicKeyId := rs.Primary.Attributes["ssh_public_key_id"] + encoding := rs.Primary.Attributes["encoding"] + + return fmt.Sprintf("%s:%s:%s", username, sshPublicKeyId, encoding), nil + } +} + const testAccAWSSSHKeyConfig_sshEncoding = ` resource "aws_iam_user" "user" { name = "test-user-%d" @@ -145,6 +174,16 @@ resource "aws_iam_user" "user" { resource "aws_iam_user_ssh_key" "user" { username = "${aws_iam_user.user.name}" encoding = "PEM" - public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD3F6tyPEFEzV0LX3X8BsXdMsQz1x2cEikKDEY0aIj41qgxMCP/iteneqXSIFZBp5vizPvaoIR3Um9xK7PGoW8giupGn+EPuxIA4cDM4vzOqOkiMPhz5XK0whEjkVzTo4+S0puvDZuwIsdiW9mxhJc7tgBNL0cYlWSYVkz4G/fslNfRPW5mYAM49f4fhtxPb5ok4Q2Lg9dPKVHO/Bgeu5woMc7RY0p1ej6D4CKFE6lymSDJpW0YHX/wqE9+cfEauh7xZcG0q9t2ta6F6fmX0agvpFyZo8aFbXeUBr7osSCJNgvavWbM/06niWrOvYX2xwWdhXmXSrbX8ZbabVohBK41 phodgson@thoughtworks.com" + public_key = <