7
votes

Using Terraform 0.7.7.

I have a simple Terraform file with the following:

provider "aws" {
  access_key = "${var.access_key}"
  secret_key = "${var.secret_key}"
  region     = "${var.region}"
}

resource "aws_instance" "personal" {
  ami           = "${lookup(var.amis, var.region)}"
  instance_type = "t2.micro"
}

resource "aws_eip" "ip" {
  instance = "${aws_instance.personal.id}"
}

resource "aws_key_pair" "personal" {
  key_name = "mschuchard-us-east"
  public_key = "${var.public_key}"
}

Terraform apply yields the following error:

aws_key_pair.personal: Creating...
  fingerprint: "" => "<computed>"
  key_name:    "" => "mschuchard-us-east"
  public_key:  "" => "ssh-rsa pubkey hash mschuchard-us-east"
aws_instance.personal: Creating...
  ami:                      "" => "ami-c481fad3"
  availability_zone:        "" => "<computed>"
  ebs_block_device.#:       "" => "<computed>"
  ephemeral_block_device.#: "" => "<computed>"
  instance_state:           "" => "<computed>"
  instance_type:            "" => "t2.micro"
  key_name:                 "" => "<computed>"
  network_interface_id:     "" => "<computed>"
  placement_group:          "" => "<computed>"
  private_dns:              "" => "<computed>"
  private_ip:               "" => "<computed>"
  public_dns:               "" => "<computed>"
  public_ip:                "" => "<computed>"
  root_block_device.#:      "" => "<computed>"
  security_groups.#:        "" => "<computed>"
  source_dest_check:        "" => "true"
  subnet_id:                "" => "<computed>"
  tenancy:                  "" => "<computed>"
  vpc_security_group_ids.#: "" => "<computed>"
aws_instance.personal: Creation complete
aws_eip.ip: Creating...
  allocation_id:     "" => "<computed>"
  association_id:    "" => "<computed>"
  domain:            "" => "<computed>"
  instance:          "" => "i-0ab94b58b0089697d"
  network_interface: "" => "<computed>"
  private_ip:        "" => "<computed>"
  public_ip:         "" => "<computed>"
  vpc:               "" => "<computed>"
aws_eip.ip: Creation complete
Error applying plan:

1 error(s) occurred:

* aws_key_pair.personal: Error import KeyPair: InvalidKeyPair.Duplicate: The keypair 'mschuchard-us-east' already exists.
status code: 400, request id: 51950b9a-55e8-4901-bf35-4d2be234abbf

The only help I found with googling was to blow away the *.tfstate files, which I tried and that did not help. I can launch an EC2 instance with the gui with this key pair and easily ssh into it, but Terraform is erroring when trying to use the same fully functional keypair.

3

3 Answers

17
votes

The error is telling you that the keypair already exists in your AWS account but Terraform has no knowledge of it in its state files so is attempting to create it each time.

You have two options available to you here. Firstly, you could simply delete it from the AWS account and allow Terraform to upload it and thus allow it to be managed by Terraform and be in its state files.

Alternatively you could use the Terraform import command to import the pre-existing resource into your state file:

terraform import aws_key_pair.personal mschuchard-us-east
4
votes

The error says that key pair already exists in AWS, and it does not say whether it was created using Terraform or using console.

You should see it in AWS console EC2 -> Key Pairs for correct region. You should delete it using console before retrying import it using Terraform.

4
votes

Use the ${uuid()} function to always get a random id for key pairs when generating, the selected/generated UUID makes it into the state file, so you will still be able to delete, but updating will not be possible. Every time you apply your terraform file there will be a new keypair generated...

While it is true that you can not generate a key pair from scratch using the AWS provider, you can generate a new key pair object in AWS using an RSA private key that the TLS provider generates.

resource "aws_key_pair" "test" {
    key_name   = "${uuid()}"
    public_key = "${tls_private_key.t.public_key_openssh}"
}
provider "tls" {}
resource "tls_private_key" "t" {
    algorithm = "RSA"
}
provider "local" {}
resource "local_file" "key" {
    content  = "${tls_private_key.t.private_key_pem}"
    filename = "id_rsa"
    provisioner "local-exec" {
        command = "chmod 600 id_rsa"
    }
}

Use the tls provider to generate a key, and import it as a new object every time. Then export the private key so that you have it for access to the server(s) later on.

It is worthy to note that this breaks one of the paradigms that Terraform is attempting to use (infrastructure as code), but from a practical development standpoint that might be a bit too idealistic... Terraform builds fail midway through and states get invalidated. A better solution might be if the AWS plugin received an "already exists" error it imported automatically, or if that was an optional behavior that could be set.