Skip to content

add elastic_ip_allocation_id support to EBS builders#654

Open
tjnicholas wants to merge 1 commit into
hashicorp:mainfrom
tjnicholas:byo-eip
Open

add elastic_ip_allocation_id support to EBS builders#654
tjnicholas wants to merge 1 commit into
hashicorp:mainfrom
tjnicholas:byo-eip

Conversation

@tjnicholas

@tjnicholas tjnicholas commented Mar 18, 2026

Copy link
Copy Markdown

Description

Introduces a new elastic_ip_allocation_id string field on RunConfig that allows users to associate a pre-existing Elastic IP with the Packer build instance immediately after launch.

This is useful when the build host, or other build time dependency, needs to be reachable from a known, stable public IP — for example, to satisfy firewall allowlists without relying on an auto-assigned address.

Implementation details:

  • Adds StepAssociateEIP to the multistep pipeline in both the ebs and ebssurrogate builders, positioned after instance launch and before password/SSH steps so the EIP is in place before any connection is attempted.
  • On Run, the step calls AssociateAddress then re-describes the instance and writes the refreshed object back to the state bag, ensuring SSHHost picks up the EIP as PublicIpAddress.
  • On Cleanup, the step calls DisassociateAddress (but does not release the EIP — it belongs to the caller).
  • Extends the Ec2Client interface with AssociateAddress and DisassociateAddress; the real *ec2.Client already satisfies both.
  • Validates that the value matches ^eipalloc-[0-9a-f]+$ and that it is not combined with associate_public_ip_address, which would be redundant and ambiguous.

Resolved Issues

This addresses the AWS portion of #36 , but not the Azure portion.
Closes #558, or rather enables a useful answer.

Rollback Plan

If a change needs to be reverted, we will roll out an update to the code within 7 days.

Introduces a new `elastic_ip_allocation_id` string field on RunConfig
that allows users to associate a pre-existing Elastic IP with the Packer
build instance immediately after launch. This is useful when the build
host, or other build time dependency, needs to be reachable from a known,
stable public IP — for example, to satisfy firewall allowlists without
relying on an auto-assigned address.

Implementation details:
- Adds `StepAssociateEIP` to the multistep pipeline in both the `ebs`
  and `ebssurrogate` builders, positioned after instance launch and
  before password/SSH steps so the EIP is in place before any connection
  is attempted.
- On Run, the step calls AssociateAddress then re-describes the instance
  and writes the refreshed object back to the state bag, ensuring
  SSHHost picks up the EIP as PublicIpAddress.
- On Cleanup, the step calls DisassociateAddress (but does not release
  the EIP — it belongs to the caller).
- Extends the Ec2Client interface with AssociateAddress and
  DisassociateAddress; the real *ec2.Client already satisfies both.
- Validates that the value matches `^eipalloc-[0-9a-f]+$` and that it
  is not combined with associate_public_ip_address, which would be
  redundant and ambiguous.
- Regenerates HCL2 spec files for both builders.
@tjnicholas tjnicholas requested a review from a team as a code owner March 18, 2026 02:05
@hashicorp-cla-app

hashicorp-cla-app Bot commented Mar 18, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@hashicorp-cla-app

Copy link
Copy Markdown

CLA assistant check

Thank you for your submission! We require that all contributors sign our Contributor License Agreement ("CLA") before we can accept the contribution. Read and sign the agreement

Learn more about why HashiCorp requires a CLA and what the CLA includes

Have you signed the CLA already but the status is still pending? Recheck it.

@tjnicholas

tjnicholas commented Mar 18, 2026

Copy link
Copy Markdown
Author

I've tested that this change functions as I expect using a basic packer config. The specified EIP is associated and disassociated as expected.

I do want to note that this change was developed using Claude. I'm not a Go programmer so do take it with an appropriate grain of salt.

data "amazon-ami" "autogenerated_1" {
  filters = {
    architecture        = "x86_64"
    name                = "amzn2-ami-hvm-2.0.*"
    root-device-type    = "ebs"
    virtualization-type = "hvm"
  }
  most_recent = true
  owners      = ["137112412989"] # amazon
  region      = "ap-southeast-2"
}
source "amazon-ebs" "eip_test" {
  region        = "ap-southeast-2"
  instance_type = "t3.micro"
  source_ami    = "${data.amazon-ami.autogenerated_1.id}"
  ssh_username  = "ec2-user"
  ami_name      = "packer-eip-test-{{timestamp}}"
  elastic_ip_allocation_id = "eipalloc-0123456789abcdefg"
  subnet_id    = "subnet-0123456789abcdefg"
  temporary_security_group_source_public_ip = true
}
build {
  sources = ["source.amazon-ebs.eip_test"]
}

Comment thread common/run_config.go
}

if c.ElasticIpAllocationId != "" {
reEipAllocId := regexp.MustCompile(`^eipalloc-[0-9a-f]+$`)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I note that other resource specifiers aren't doing string format validation. Is it preferred to let any value through here, allowing the platform (AWS) to produce an error if required?

@tjnicholas

Copy link
Copy Markdown
Author

Anything I can add to this to make merging more attractive? I wonder, for example, if it would be better to be able to specify a list of eip IDs. That would make it easier to have multiple packer runs happen in parallel.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds support for attaching a pre-existing AWS Elastic IP (via allocation ID) to the temporary build instance used by the amazon-ebs and amazon-ebssurrogate builders, so build-time dependencies can allowlist a stable public IP.

Changes:

  • Introduces elastic_ip_allocation_id on common.RunConfig with validation and a conflict check against associate_public_ip_address.
  • Adds a new multistep step (StepAssociateEIP) to associate/disassociate the EIP around the build instance lifecycle.
  • Extends the clients.Ec2Client interface and updates docs + unit tests for the new behavior.

Reviewed changes

Copilot reviewed 8 out of 10 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
docs-partials/common/RunConfig-not-required.mdx Documents the new elastic_ip_allocation_id option and its constraints/permissions.
common/step_associate_eip.go Adds the new multistep step that associates/disassociates an EIP and refreshes instance state.
common/step_associate_eip_test.go Unit tests covering StepAssociateEIP success/failure/cleanup paths.
common/run_config.go Adds ElasticIpAllocationId to RunConfig and validates format + conflict with associate_public_ip_address.
common/run_config_test.go Adds tests for valid/invalid/conflicting elastic_ip_allocation_id values.
common/clients/ec2_client.go Extends Ec2Client with AssociateAddress/DisassociateAddress.
builder/ebssurrogate/builder.hcl2spec.go Exposes elastic_ip_allocation_id in the HCL2 spec for ebssurrogate.
builder/ebssurrogate/builder.go Inserts StepAssociateEIP into the ebssurrogate build pipeline after instance launch.
builder/ebs/builder.hcl2spec.go Exposes elastic_ip_allocation_id in the HCL2 spec for ebs.
builder/ebs/builder.go Inserts StepAssociateEIP into the ebs build pipeline after instance launch.
Files not reviewed (2)
  • builder/ebs/builder.hcl2spec.go: Language not supported
  • builder/ebssurrogate/builder.hcl2spec.go: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +51 to +56
if err != nil || len(descResp.Reservations) == 0 || len(descResp.Reservations[0].Instances) == 0 {
err = fmt.Errorf("error refreshing instance after EIP association: %w", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
AssociationId: aws.String(s.associationId),
})
if err != nil {
ui.Error(fmt.Sprintf("Error disassociating EIP %s: %s", s.associationId, err))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reserved Private IP for AWS Instance in Packer.

2 participants