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

Support explicit public ip on default vpc #364

Merged
merged 2 commits into from
Apr 18, 2023
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
84 changes: 77 additions & 7 deletions builder/common/step_network_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import (
"math/rand"
"sort"

"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer-plugin-sdk/multistep"
packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
confighelper "github.com/hashicorp/packer-plugin-sdk/template/config"
)

// StepNetworkInfo queries AWS for information about
Expand All @@ -20,13 +22,14 @@ import (
// subnet_id string - the Subnet ID
// availability_zone string - the AZ name
type StepNetworkInfo struct {
VpcId string
VpcFilter VpcFilterOptions
SubnetId string
SubnetFilter SubnetFilterOptions
AvailabilityZone string
SecurityGroupIds []string
SecurityGroupFilter SecurityGroupFilterOptions
VpcId string
VpcFilter VpcFilterOptions
SubnetId string
SubnetFilter SubnetFilterOptions
AssociatePublicIpAddress confighelper.Trilean
AvailabilityZone string
SecurityGroupIds []string
SecurityGroupFilter SecurityGroupFilterOptions
}

type subnetsSort []*ec2.Subnet
Expand Down Expand Up @@ -158,6 +161,73 @@ func (s *StepNetworkInfo) Run(ctx context.Context, state multistep.StateBag) mul
}
}

// No need to handle subnet/VPC defaults if we don't change the behaviour
// of public IP for the VPC/subnet
if s.AssociatePublicIpAddress == confighelper.TriUnset {
state.Put("vpc_id", s.VpcId)
state.Put("availability_zone", s.AvailabilityZone)
state.Put("subnet_id", s.SubnetId)
return multistep.ActionContinue
}

ui.Say(fmt.Sprintf("Setting public IP address to %t on instance without a subnet ID",
*s.AssociatePublicIpAddress.ToBoolPointer()))
if s.VpcId == "" {
ui.Say("No VPC ID provided, Packer will choose one from the provided or default VPC")
vpcs, err := ec2conn.DescribeVpcs(&ec2.DescribeVpcsInput{
Filters: []*ec2.Filter{
{
Name: aws.String("is-default"),
Values: []*string{aws.String("true")},
},
},
})
if err != nil {
err := fmt.Errorf("Failed to describe VPCs: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}

if len(vpcs.Vpcs) != 1 {
err := fmt.Errorf("No default VPC found, please set one up for associating a public IP address to the instance")
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
defaultVPC := vpcs.Vpcs[0]

s.VpcId = *defaultVPC.VpcId
}

var err error

ui.Say(fmt.Sprintf("Inferring subnet from the selected VPC %q", s.VpcId))
params := &ec2.DescribeSubnetsInput{}
filters := map[string]string{
"vpc-id": s.VpcId,
"state": "available",
}
params.Filters, err = buildEc2Filters(filters)
if err != nil {
err := fmt.Errorf("Failed to prepare subnet filters: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
subnets, err := ec2conn.DescribeSubnets(params)
if err != nil {
err := fmt.Errorf("Failed to describe subnets: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}

subnet := mostFreeSubnet(subnets.Subnets)
s.SubnetId = *subnet.SubnetId

ui.Say(fmt.Sprintf("Set subnet as %q", s.SubnetId))

state.Put("vpc_id", s.VpcId)
state.Put("availability_zone", s.AvailabilityZone)
state.Put("subnet_id", s.SubnetId)
Expand Down
3 changes: 3 additions & 0 deletions builder/common/step_run_source_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
subnetId := state.Get("subnet_id").(string)

if subnetId != "" && s.AssociatePublicIpAddress != confighelper.TriUnset {
ui.Say(fmt.Sprintf("changing public IP address config to %t for instance on subnet %q",
*s.AssociatePublicIpAddress.ToBoolPointer(),
subnetId))
runOpts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{
{
DeviceIndex: aws.Int64(0),
Expand Down
5 changes: 5 additions & 0 deletions builder/common/step_run_spot_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ func (s *StepRunSpotInstance) CreateTemplateData(userData *string, az string,

}

ui := state.Get("ui").(packersdk.Ui)

iamInstanceProfile := aws.String(state.Get("iamInstanceProfile").(string))

// Create a launch template.
Expand Down Expand Up @@ -142,6 +144,9 @@ func (s *StepRunSpotInstance) CreateTemplateData(userData *string, az string,
SubnetId: aws.String(subnetId),
}
if s.AssociatePublicIpAddress != confighelper.TriUnset {
ui.Say(fmt.Sprintf("changing public IP address config to %t for instance on subnet %q",
*s.AssociatePublicIpAddress.ToBoolPointer(),
subnetId))
networkInterface.SetAssociatePublicIpAddress(*s.AssociatePublicIpAddress.ToBoolPointer())
}
templateData.SetNetworkInterfaces([]*ec2.LaunchTemplateInstanceNetworkInterfaceSpecificationRequest{&networkInterface})
Expand Down
15 changes: 8 additions & 7 deletions builder/ebs/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,13 +297,14 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
AMIVirtType: b.config.AMIVirtType,
},
&awscommon.StepNetworkInfo{
VpcId: b.config.VpcId,
VpcFilter: b.config.VpcFilter,
SecurityGroupIds: b.config.SecurityGroupIds,
SecurityGroupFilter: b.config.SecurityGroupFilter,
SubnetId: b.config.SubnetId,
SubnetFilter: b.config.SubnetFilter,
AvailabilityZone: b.config.AvailabilityZone,
VpcId: b.config.VpcId,
VpcFilter: b.config.VpcFilter,
SecurityGroupIds: b.config.SecurityGroupIds,
SecurityGroupFilter: b.config.SecurityGroupFilter,
SubnetId: b.config.SubnetId,
SubnetFilter: b.config.SubnetFilter,
AvailabilityZone: b.config.AvailabilityZone,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
},
&awscommon.StepKeyPair{
Debug: b.config.PackerDebug,
Expand Down
135 changes: 127 additions & 8 deletions builder/ebs/builder_acc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -908,14 +908,6 @@ func TestAccBuilder_EnableUnlimitedCredits_withSpotInstances(t *testing.T) {
return fmt.Errorf("Bad exit code. Logfile: %s", logfile)
}

logs, err := os.ReadFile(logfile)
if err != nil {
return fmt.Errorf("couldn't read logs from logfile %s: %s", logfile, err)
}
if !strings.Contains(string(logs), "Uploading SSH public key") {
return fmt.Errorf("SSH key was not uploaded, but should have been")
}

return nil
},
}
Expand Down Expand Up @@ -1220,6 +1212,85 @@ func TestAccBuilder_EBSWithSSHPassword_NoTempKeyCreated(t *testing.T) {
acctest.TestPlugin(t, testcase)
}

func TestAccBuilder_SpotInstanceWithPublicIPAddressExplicitelySet(t *testing.T) {
nonSpotInstance := amazon_acc.AMIHelper{
Region: "us-east-1",
Name: fmt.Sprintf("packer-ebs-explicit-public-ip-%d", time.Now().Unix()),
}

spotInstance := amazon_acc.AMIHelper{
Region: "us-east-1",
Name: fmt.Sprintf("packer-ebs-spot-explicit-public-ip-%d", time.Now().Unix()),
}
tests := []struct {
name string
IPVal bool
amiSetup amazon_acc.AMIHelper
template string
expectErr bool
}{
{
"Spot instance, with public IP explicitely set",
true,
spotInstance,
testSetupPublicIPWithoutVPCOrSubnetOnSpotInstance,
false,
},
{
"Spot instance, with public IP explicitely unset",
false,
spotInstance,
testSetupPublicIPWithoutVPCOrSubnetOnSpotInstance,
true, // We expect an error without a public IP since no outbound connections work in this case, so SSM doesn't work with the current config
},
{
"Non-Spot instance, with public IP explicitely set",
true,
nonSpotInstance,
testSetupPublicIPWithoutVPCOrSubnet,
false,
},
{
"Non-Spot instance, with public IP explicitely unset",
false,
nonSpotInstance,
testSetupPublicIPWithoutVPCOrSubnet,
true, // We expect an error without a public IP since no outbound connections work in this case, so SSM doesn't work with the current config
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
testcase := &acctest.PluginTestCase{
Name: tt.name,
Template: fmt.Sprintf(tt.template, tt.amiSetup.Name, tt.IPVal),
Check: func(buildCommand *exec.Cmd, logfile string) error {
if (buildCommand.ProcessState.ExitCode() != 0) != tt.expectErr {
return fmt.Errorf("Bad exit code, expected %t error, got %d. Logfile: %s",
tt.expectErr,
buildCommand.ProcessState.ExitCode(),
logfile)
}

logs, err := os.ReadFile(logfile)
if err != nil {
return fmt.Errorf("couldn't read logs from logfile %s: %s", logfile, err)
}

expectMsg := fmt.Sprintf("changing public IP address config to %t for instance on subnet", tt.IPVal)

if !strings.Contains(string(logs), expectMsg) {
return fmt.Errorf("did not change the public IP setting for the instance")
}

return nil
},
}
acctest.TestPlugin(t, testcase)
})
}
}

const testBuilderAccBasic = `
{
"builders": [{
Expand Down Expand Up @@ -1549,6 +1620,54 @@ build {
}
`

const testSetupPublicIPWithoutVPCOrSubnet = `
source "amazon-ebs" "test_build" {
region = "us-east-1"
ami_name = "%s"
source_ami = "ami-06e46074ae430fba6" # Amazon Linux 2023 x86-64
instance_type = "t2.micro"
communicator = "ssh"
ssh_username = "ec2-user"
ssh_timeout = "45s"
associate_public_ip_address = %t
skip_create_ami = true
}

build {
sources = ["amazon-ebs.test_build"]
}
`

const testSetupPublicIPWithoutVPCOrSubnetOnSpotInstance = `
source "amazon-ebs" "test" {
region = "us-east-1"
spot_price = "auto"
source_ami = "ami-06e46074ae430fba6" # Amazon Linux 2023 x86-64
instance_type = "t2.micro"
ssh_username = "ec2-user"
ssh_timeout = "45s"
ami_name = "%s"
skip_create_ami = true
associate_public_ip_address = %t
temporary_iam_instance_profile_policy_document {
Version = "2012-10-17"
Statement {
Effect = "Allow"
Action = [
"ec2:GetDefaultCreditSpecification",
"ec2:DescribeInstanceTypeOfferings",
"ec2:DescribeInstanceCreditSpecifications"
]
Resource = ["*"]
}
}
}

build {
sources = ["source.amazon-ebs.test"]
}
`

const testWindowsFastBoot = `
source "amazon-ebs" "windows-fastboot" {
ami_name = "%s"
Expand Down
15 changes: 8 additions & 7 deletions builder/ebssurrogate/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,13 +325,14 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
AMIVirtType: b.config.AMIVirtType,
},
&awscommon.StepNetworkInfo{
VpcId: b.config.VpcId,
VpcFilter: b.config.VpcFilter,
SecurityGroupIds: b.config.SecurityGroupIds,
SecurityGroupFilter: b.config.SecurityGroupFilter,
SubnetId: b.config.SubnetId,
SubnetFilter: b.config.SubnetFilter,
AvailabilityZone: b.config.AvailabilityZone,
VpcId: b.config.VpcId,
VpcFilter: b.config.VpcFilter,
SecurityGroupIds: b.config.SecurityGroupIds,
SecurityGroupFilter: b.config.SecurityGroupFilter,
SubnetId: b.config.SubnetId,
SubnetFilter: b.config.SubnetFilter,
AvailabilityZone: b.config.AvailabilityZone,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
},
&awscommon.StepKeyPair{
Debug: b.config.PackerDebug,
Expand Down
15 changes: 8 additions & 7 deletions builder/ebsvolume/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,13 +272,14 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
AmiFilters: b.config.SourceAmiFilter,
},
&awscommon.StepNetworkInfo{
VpcId: b.config.VpcId,
VpcFilter: b.config.VpcFilter,
SecurityGroupIds: b.config.SecurityGroupIds,
SecurityGroupFilter: b.config.SecurityGroupFilter,
SubnetId: b.config.SubnetId,
SubnetFilter: b.config.SubnetFilter,
AvailabilityZone: b.config.AvailabilityZone,
VpcId: b.config.VpcId,
VpcFilter: b.config.VpcFilter,
SecurityGroupIds: b.config.SecurityGroupIds,
SecurityGroupFilter: b.config.SecurityGroupFilter,
SubnetId: b.config.SubnetId,
SubnetFilter: b.config.SubnetFilter,
AvailabilityZone: b.config.AvailabilityZone,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
},
&awscommon.StepKeyPair{
Debug: b.config.PackerDebug,
Expand Down
15 changes: 8 additions & 7 deletions builder/instance/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -342,13 +342,14 @@ func (b *Builder) Run(ctx context.Context, ui packersdk.Ui, hook packersdk.Hook)
AMIVirtType: b.config.AMIVirtType,
},
&awscommon.StepNetworkInfo{
VpcId: b.config.VpcId,
VpcFilter: b.config.VpcFilter,
SecurityGroupIds: b.config.SecurityGroupIds,
SecurityGroupFilter: b.config.SecurityGroupFilter,
SubnetId: b.config.SubnetId,
SubnetFilter: b.config.SubnetFilter,
AvailabilityZone: b.config.AvailabilityZone,
VpcId: b.config.VpcId,
VpcFilter: b.config.VpcFilter,
SecurityGroupIds: b.config.SecurityGroupIds,
SecurityGroupFilter: b.config.SecurityGroupFilter,
SubnetId: b.config.SubnetId,
SubnetFilter: b.config.SubnetFilter,
AvailabilityZone: b.config.AvailabilityZone,
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
},
&awscommon.StepKeyPair{
Debug: b.config.PackerDebug,
Expand Down