Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Transfer SSH Public Key Resource Support #6932

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,7 @@ func Provider() terraform.ResourceProvider {
"aws_subnet": resourceAwsSubnet(),
"aws_swf_domain": resourceAwsSwfDomain(),
"aws_transfer_server": resourceAwsTransferServer(),
"aws_transfer_ssh_key": resourceAwsTransferSshKey(),
"aws_transfer_user": resourceAwsTransferUser(),
"aws_volume_attachment": resourceAwsVolumeAttachment(),
"aws_vpc_dhcp_options_association": resourceAwsVpcDhcpOptionsAssociation(),
Expand Down
150 changes: 150 additions & 0 deletions aws/resource_aws_transfer_ssh_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package aws

import (
"fmt"
"log"
"strings"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/transfer"

"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsTransferSshKey() *schema.Resource {

return &schema.Resource{
Create: resourceAwsTransferSshKeyCreate,
Read: resourceAwsTransferSshKeyRead,
Delete: resourceAwsTransferSshKeyDelete,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},
Schema: map[string]*schema.Schema{
"body": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
old = cleanSshKey(old)
new = cleanSshKey(new)
return strings.Trim(old, "\n") == strings.Trim(new, "\n")
},
},

"server_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateTransferServerID,
},

"user_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validateTransferUserName,
},
},
}
}

func resourceAwsTransferSshKeyCreate(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).transferconn
userName := d.Get("user_name").(string)
serverID := d.Get("server_id").(string)

createOpts := &transfer.ImportSshPublicKeyInput{
ServerId: aws.String(serverID),
UserName: aws.String(userName),
SshPublicKeyBody: aws.String(d.Get("body").(string)),
}

log.Printf("[DEBUG] Create Transfer SSH Public Key Option: %#v", createOpts)

resp, err := conn.ImportSshPublicKey(createOpts)
if err != nil {
return fmt.Errorf("Error importing ssh public key: %s", err)
}

d.SetId(fmt.Sprintf("%s/%s/%s", serverID, userName, *resp.SshPublicKeyId))

return nil
}

func resourceAwsTransferSshKeyRead(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).transferconn
serverID, userName, sshKeyID, err := decodeTransferSshKeyId(d.Id())
if err != nil {
return fmt.Errorf("error parsing Transfer SSH Public Key ID: %s", err)
}

descOpts := &transfer.DescribeUserInput{
UserName: aws.String(userName),
ServerId: aws.String(serverID),
}

log.Printf("[DEBUG] Describe Transfer User Option: %#v", descOpts)

resp, err := conn.DescribeUser(descOpts)
if err != nil {
if isAWSErr(err, transfer.ErrCodeResourceNotFoundException, "") {
log.Printf("[WARN] Transfer User (%s) for Server (%s) not found, removing ssh public key (%s) from state", userName, serverID, sshKeyID)
d.SetId("")
return nil
}
return err
}

var body string
for _, s := range resp.User.SshPublicKeys {
if sshKeyID == *s.SshPublicKeyId {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: To prevent potential panics, we prefer to dereference API response values using the AWS Go SDK functions, e.g. if sshKeyID == aws.StringValue(s.SshPublicKeyId)

body = *s.SshPublicKeyBody
}
}

if body == "" {
log.Printf("[WARN] No such ssh public key found for User (%s) in Server (%s)", userName, serverID)
d.SetId("")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Its safe in this case to be omitted, but generally we should immediately call return nil after d.SetId("") 👍

}

d.Set("server_id", resp.ServerId)
d.Set("user_name", resp.User.UserName)
d.Set("body", body)

return nil
}

func resourceAwsTransferSshKeyDelete(d *schema.ResourceData, meta interface{}) error {
conn := meta.(*AWSClient).transferconn
serverID, userName, sshKeyID, err := decodeTransferSshKeyId(d.Id())
if err != nil {
return fmt.Errorf("error parsing Transfer SSH Public Key ID: %s", err)
}

delOpts := &transfer.DeleteSshPublicKeyInput{
UserName: aws.String(userName),
ServerId: aws.String(serverID),
SshPublicKeyId: aws.String(sshKeyID),
}

log.Printf("[DEBUG] Delete Transfer SSH Public Key Option: %#v", delOpts)

_, err = conn.DeleteSshPublicKey(delOpts)
if err != nil {
if isAWSErr(err, transfer.ErrCodeResourceNotFoundException, "") {
return nil
}
return fmt.Errorf("error deleting Transfer User Ssh Key (%s): %s", d.Id(), err)
}

return nil
}

func decodeTransferSshKeyId(id string) (string, string, string, error) {
idParts := strings.SplitN(id, "/", 3)
if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
return "", "", "", fmt.Errorf("unexpected format of ID (%s), expected SERVERID/USERNAME/SSHKEYID", id)
}
return idParts[0], idParts[1], idParts[2], nil
}
175 changes: 175 additions & 0 deletions aws/resource_aws_transfer_ssh_key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package aws

import (
"fmt"
"testing"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/transfer"

"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccAWSTransferSshKey_basic(t *testing.T) {
var conf transfer.SshPublicKey
rName := acctest.RandString(5)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSTransferSshKeyDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSTransferSshKeyConfig_basic(rName),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSTransferSshKeyExists("aws_transfer_ssh_key.foo", &conf),
resource.TestCheckResourceAttrPair(
"aws_transfer_ssh_key.foo", "server_id", "aws_transfer_server.foo", "id"),
resource.TestCheckResourceAttrPair(
"aws_transfer_ssh_key.foo", "user_name", "aws_transfer_user.foo", "user_name"),
),
},
{
ResourceName: "aws_transfer_user.foo",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccCheckAWSTransferSshKeyExists(n string, res *transfer.SshPublicKey) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
if !ok {
return fmt.Errorf("Not found: %s", n)
}

if rs.Primary.ID == "" {
return fmt.Errorf("No Transfer Ssh Public Key ID is set")
}

conn := testAccProvider.Meta().(*AWSClient).transferconn
serverID, userName, sshKeyID, err := decodeTransferSshKeyId(rs.Primary.ID)
if err != nil {
return fmt.Errorf("error parsing Transfer SSH Public Key ID: %s", err)
}

describe, err := conn.DescribeUser(&transfer.DescribeUserInput{
ServerId: aws.String(serverID),
UserName: aws.String(userName),
})

if err != nil {
return err
}

for _, sshPublicKey := range describe.User.SshPublicKeys {
if sshKeyID == *sshPublicKey.SshPublicKeyId {
*res = *sshPublicKey
return nil
}
}

return fmt.Errorf("Transfer Ssh Public Key doesn't exists.")
}
}

func testAccCheckAWSTransferSshKeyDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*AWSClient).transferconn

for _, rs := range s.RootModule().Resources {
if rs.Type != "aws_transfer_ssh_key" {
continue
}
serverID, userName, sshKeyID, err := decodeTransferSshKeyId(rs.Primary.ID)
if err != nil {
return fmt.Errorf("error parsing Transfer SSH Public Key ID: %s", err)
}

describe, err := conn.DescribeUser(&transfer.DescribeUserInput{
UserName: aws.String(userName),
ServerId: aws.String(serverID),
})

if isAWSErr(err, transfer.ErrCodeResourceNotFoundException, "") {
continue
}

if err != nil {
return err
}

for _, sshPublicKey := range describe.User.SshPublicKeys {
if sshKeyID == *sshPublicKey.SshPublicKeyId {
return fmt.Errorf("Transfer SSH Public Key still exists")
}
}
}

return nil
}

func testAccAWSTransferSshKeyConfig_basic(rName string) string {
return fmt.Sprintf(`
resource "aws_transfer_server" "foo" {
identity_provider_type = "SERVICE_MANAGED"
}


resource "aws_iam_role" "foo" {
name = "tf-test-transfer-user-iam-role-%s"

assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "transfer.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}

resource "aws_iam_role_policy" "foo" {
name = "tf-test-transfer-user-iam-policy-%s"
role = "${aws_iam_role.foo.id}"
policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowFullAccesstoS3",
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": "*"
}
]
}
POLICY
}


resource "aws_transfer_user" "foo" {
server_id = "${aws_transfer_server.foo.id}"
user_name = "tftestuser"
role = "${aws_iam_role.foo.arn}"
}


resource "aws_transfer_ssh_key" "foo" {
server_id = "${aws_transfer_server.foo.id}"
user_name = "${aws_transfer_user.foo.user_name}"
body = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD3F6tyPEFEzV0LX3X8BsXdMsQz1x2cEikKDEY0aIj41qgxMCP/iteneqXSIFZBp5vizPvaoIR3Um9xK7PGoW8giupGn+EPuxIA4cDM4vzOqOkiMPhz5XK0whEjkVzTo4+S0puvDZuwIsdiW9mxhJc7tgBNL0cYlWSYVkz4G/fslNfRPW5mYAM49f4fhtxPb5ok4Q2Lg9dPKVHO/Bgeu5woMc7RY0p1ej6D4CKFE6lymSDJpW0YHX/wqE9+cfEauh7xZcG0q9t2ta6F6fmX0agvpFyZo8aFbXeUBr7osSCJNgvavWbM/06niWrOvYX2xwWdhXmXSrbX8ZbabVohBK41 phodgson@thoughtworks.com"
}
`, rName, rName)
}
4 changes: 4 additions & 0 deletions website/aws.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2463,6 +2463,10 @@
<a href="/docs/providers/aws/r/transfer_server.html">aws_transfer_server</a>
</li>

<li<%= sidebar_current("docs-aws-resource-transfer-ssh-key") %>>
<a href="/docs/providers/aws/r/transfer_ssh_key.html">aws_transfer_ssh_key</a>
</li>

<li<%= sidebar_current("docs-aws-resource-transfer-user") %>>
<a href="/docs/providers/aws/r/transfer_user.html">aws_transfer_user</a>
</li>
Expand Down
Loading