Merge pull request #6374 from rickard-von-essen/aws-filters

builder/amazon: Add suppport for vpc_filter, subnet_filter, and security_group_filter
This commit is contained in:
Megan Marsh 2018-10-18 14:34:02 -07:00 committed by GitHub
commit 57f87f58b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 640 additions and 145 deletions

View File

@ -0,0 +1,17 @@
package common
import (
"github.com/aws/aws-sdk-go/service/ec2"
)
// Build a slice of EC2 (AMI/Subnet/VPC) filter options from the filters provided.
func buildEc2Filters(input map[*string]*string) []*ec2.Filter {
var filters []*ec2.Filter
for k, v := range input {
filters = append(filters, &ec2.Filter{
Name: k,
Values: []*string{v},
})
}
return filters
}

View File

@ -29,33 +29,62 @@ func (d *AmiFilterOptions) NoOwner() bool {
return len(d.Owners) == 0
}
type SubnetFilterOptions struct {
Filters map[*string]*string
MostFree bool `mapstructure:"most_free"`
Random bool `mapstructure:"random"`
}
func (d *SubnetFilterOptions) Empty() bool {
return len(d.Filters) == 0
}
type VpcFilterOptions struct {
Filters map[*string]*string
}
func (d *VpcFilterOptions) Empty() bool {
return len(d.Filters) == 0
}
type SecurityGroupFilterOptions struct {
Filters map[*string]*string
}
func (d *SecurityGroupFilterOptions) Empty() bool {
return len(d.Filters) == 0
}
// RunConfig contains configuration for running an instance from a source
// AMI and details on how to access that launched image.
type RunConfig struct {
AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"`
AvailabilityZone string `mapstructure:"availability_zone"`
BlockDurationMinutes int64 `mapstructure:"block_duration_minutes"`
DisableStopInstance bool `mapstructure:"disable_stop_instance"`
EbsOptimized bool `mapstructure:"ebs_optimized"`
EnableT2Unlimited bool `mapstructure:"enable_t2_unlimited"`
IamInstanceProfile string `mapstructure:"iam_instance_profile"`
InstanceInitiatedShutdownBehavior string `mapstructure:"shutdown_behavior"`
InstanceType string `mapstructure:"instance_type"`
RunTags map[string]string `mapstructure:"run_tags"`
SecurityGroupId string `mapstructure:"security_group_id"`
SecurityGroupIds []string `mapstructure:"security_group_ids"`
SourceAmi string `mapstructure:"source_ami"`
SourceAmiFilter AmiFilterOptions `mapstructure:"source_ami_filter"`
SpotPrice string `mapstructure:"spot_price"`
SpotPriceAutoProduct string `mapstructure:"spot_price_auto_product"`
SpotTags map[string]string `mapstructure:"spot_tags"`
SubnetId string `mapstructure:"subnet_id"`
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
TemporarySGSourceCidr string `mapstructure:"temporary_security_group_source_cidr"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
VpcId string `mapstructure:"vpc_id"`
WindowsPasswordTimeout time.Duration `mapstructure:"windows_password_timeout"`
AssociatePublicIpAddress bool `mapstructure:"associate_public_ip_address"`
AvailabilityZone string `mapstructure:"availability_zone"`
BlockDurationMinutes int64 `mapstructure:"block_duration_minutes"`
DisableStopInstance bool `mapstructure:"disable_stop_instance"`
EbsOptimized bool `mapstructure:"ebs_optimized"`
EnableT2Unlimited bool `mapstructure:"enable_t2_unlimited"`
IamInstanceProfile string `mapstructure:"iam_instance_profile"`
InstanceInitiatedShutdownBehavior string `mapstructure:"shutdown_behavior"`
InstanceType string `mapstructure:"instance_type"`
SecurityGroupFilter SecurityGroupFilterOptions `mapstructure:"security_group_filter"`
RunTags map[string]string `mapstructure:"run_tags"`
SecurityGroupId string `mapstructure:"security_group_id"`
SecurityGroupIds []string `mapstructure:"security_group_ids"`
SourceAmi string `mapstructure:"source_ami"`
SourceAmiFilter AmiFilterOptions `mapstructure:"source_ami_filter"`
SpotPrice string `mapstructure:"spot_price"`
SpotPriceAutoProduct string `mapstructure:"spot_price_auto_product"`
SpotTags map[string]string `mapstructure:"spot_tags"`
SubnetFilter SubnetFilterOptions `mapstructure:"subnet_filter"`
SubnetId string `mapstructure:"subnet_id"`
TemporaryKeyPairName string `mapstructure:"temporary_key_pair_name"`
TemporarySGSourceCidr string `mapstructure:"temporary_security_group_source_cidr"`
UserData string `mapstructure:"user_data"`
UserDataFile string `mapstructure:"user_data_file"`
VpcFilter VpcFilterOptions `mapstructure:"vpc_filter"`
VpcId string `mapstructure:"vpc_id"`
WindowsPasswordTimeout time.Duration `mapstructure:"windows_password_timeout"`
// Communicator settings
Comm communicator.Config `mapstructure:",squash"`

View File

@ -0,0 +1,154 @@
package common
import (
"context"
"fmt"
"log"
"math/rand"
"sort"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/hashicorp/packer/helper/multistep"
"github.com/hashicorp/packer/packer"
)
// StepNetworkInfo queries AWS for information about
// VPC's and Subnets that is used throughout the AMI creation process.
//
// Produces (adding them to the state bag):
// vpc_id string - the VPC ID
// 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
}
type subnetsSort []*ec2.Subnet
func (a subnetsSort) Len() int { return len(a) }
func (a subnetsSort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a subnetsSort) Less(i, j int) bool {
return *a[i].AvailableIpAddressCount < *a[j].AvailableIpAddressCount
}
// Returns the most recent AMI out of a slice of images.
func mostFreeSubnet(subnets []*ec2.Subnet) *ec2.Subnet {
sortedSubnets := subnets
sort.Sort(subnetsSort(sortedSubnets))
return sortedSubnets[len(sortedSubnets)-1]
}
func (s *StepNetworkInfo) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
// VPC
if s.VpcId == "" && !s.VpcFilter.Empty() {
params := &ec2.DescribeVpcsInput{}
params.Filters = buildEc2Filters(s.VpcFilter.Filters)
s.VpcFilter.Filters[aws.String("state")] = aws.String("available")
log.Printf("Using VPC Filters %v", params)
vpcResp, err := ec2conn.DescribeVpcs(params)
if err != nil {
err := fmt.Errorf("Error querying VPCs: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(vpcResp.Vpcs) != 1 {
err := fmt.Errorf("Exactly one VPC should match the filter, but %d VPC's was found matching filters: %v", len(vpcResp.Vpcs), params)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
s.VpcId = *vpcResp.Vpcs[0].VpcId
ui.Message(fmt.Sprintf("Found VPC ID: %s", s.VpcId))
}
// Subnet
if s.SubnetId == "" && !s.SubnetFilter.Empty() {
params := &ec2.DescribeSubnetsInput{}
s.SubnetFilter.Filters[aws.String("state")] = aws.String("available")
if s.VpcId != "" {
s.SubnetFilter.Filters[aws.String("vpc-id")] = &s.VpcId
}
if s.AvailabilityZone != "" {
s.SubnetFilter.Filters[aws.String("availability-zone")] = &s.AvailabilityZone
}
params.Filters = buildEc2Filters(s.SubnetFilter.Filters)
log.Printf("Using Subnet Filters %v", params)
subnetsResp, err := ec2conn.DescribeSubnets(params)
if err != nil {
err := fmt.Errorf("Error querying Subnets: %s", err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(subnetsResp.Subnets) == 0 {
err := fmt.Errorf("No Subnets was found matching filters: %v", params)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if len(subnetsResp.Subnets) > 1 && !s.SubnetFilter.Random && !s.SubnetFilter.MostFree {
err := fmt.Errorf("Your filter matched %d Subnets. Please try a more specific search, or set random or most_free to true.", len(subnetsResp.Subnets))
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
var subnet *ec2.Subnet
switch {
case s.SubnetFilter.MostFree:
subnet = mostFreeSubnet(subnetsResp.Subnets)
case s.SubnetFilter.Random:
subnet = subnetsResp.Subnets[rand.Intn(len(subnetsResp.Subnets))]
default:
subnet = subnetsResp.Subnets[0]
}
s.SubnetId = *subnet.SubnetId
ui.Message(fmt.Sprintf("Found Subnet ID: %s", s.SubnetId))
}
// Try to find AZ and VPC Id from Subnet if they are not yet found/given
if s.SubnetId != "" && (s.AvailabilityZone == "" || s.VpcId == "") {
log.Printf("[INFO] Finding AZ and VpcId for the given subnet '%s'", s.SubnetId)
resp, err := ec2conn.DescribeSubnets(&ec2.DescribeSubnetsInput{SubnetIds: []*string{&s.SubnetId}})
if err != nil {
err := fmt.Errorf("Describing the subnet: %s returned error: %s.", s.SubnetId, err)
state.Put("error", err)
ui.Error(err.Error())
return multistep.ActionHalt
}
if s.AvailabilityZone == "" {
s.AvailabilityZone = *resp.Subnets[0].AvailabilityZone
log.Printf("[INFO] AvailabilityZone found: '%s'", s.AvailabilityZone)
}
if s.VpcId == "" {
s.VpcId = *resp.Subnets[0].VpcId
log.Printf("[INFO] VpcId found: '%s'", s.VpcId)
}
}
state.Put("vpc_id", s.VpcId)
state.Put("availability_zone", s.AvailabilityZone)
state.Put("subnet_id", s.SubnetId)
return multistep.ActionContinue
}
func (s *StepNetworkInfo) Cleanup(multistep.StateBag) {}

View File

@ -20,7 +20,6 @@ import (
type StepRunSourceInstance struct {
AssociatePublicIpAddress bool
AvailabilityZone string
BlockDevices BlockDevices
Comm *communicator.Config
Ctx interpolate.Context
@ -33,7 +32,6 @@ type StepRunSourceInstance struct {
InstanceType string
IsRestricted bool
SourceAMI string
SubnetId string
Tags TagMap
UserData string
UserDataFile string
@ -104,6 +102,7 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
return multistep.ActionHalt
}
az := state.Get("availability_zone").(string)
runOpts := &ec2.RunInstancesInput{
ImageId: &s.SourceAMI,
InstanceType: &s.InstanceType,
@ -112,7 +111,7 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
MinCount: aws.Int64(1),
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile},
BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(),
Placement: &ec2.Placement{AvailabilityZone: &s.AvailabilityZone},
Placement: &ec2.Placement{AvailabilityZone: &az},
EbsOptimized: &s.EbsOptimized,
}
@ -153,18 +152,20 @@ func (s *StepRunSourceInstance) Run(ctx context.Context, state multistep.StateBa
runOpts.KeyName = &s.Comm.SSHKeyPairName
}
if s.SubnetId != "" && s.AssociatePublicIpAddress {
subnetId := state.Get("subnet_id").(string)
if subnetId != "" && s.AssociatePublicIpAddress {
runOpts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{
{
DeviceIndex: aws.Int64(0),
AssociatePublicIpAddress: &s.AssociatePublicIpAddress,
SubnetId: &s.SubnetId,
SubnetId: aws.String(subnetId),
Groups: securityGroupIds,
DeleteOnTermination: aws.Bool(true),
},
}
} else {
runOpts.SubnetId = &s.SubnetId
runOpts.SubnetId = aws.String(subnetId)
runOpts.SecurityGroupIds = securityGroupIds
}

View File

@ -22,7 +22,6 @@ import (
type StepRunSpotInstance struct {
AssociatePublicIpAddress bool
AvailabilityZone string
BlockDevices BlockDevices
BlockDurationMinutes int64
Debug bool
@ -36,7 +35,6 @@ type StepRunSpotInstance struct {
SpotPrice string
SpotPriceProduct string
SpotTags TagMap
SubnetId string
Tags TagMap
VolumeTags TagMap
UserData string
@ -86,7 +84,12 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
}
spotPrice := s.SpotPrice
availabilityZone := s.AvailabilityZone
azConfig := ""
if azRaw, ok := state.GetOk("availability_zone"); ok {
azConfig = azRaw.(string)
}
az := azConfig
if spotPrice == "auto" {
ui.Message(fmt.Sprintf(
"Finding spot price for %s %s...",
@ -97,7 +100,7 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
resp, err := ec2conn.DescribeSpotPriceHistory(&ec2.DescribeSpotPriceHistoryInput{
InstanceTypes: []*string{&s.InstanceType},
ProductDescriptions: []*string{&s.SpotPriceProduct},
AvailabilityZone: &s.AvailabilityZone,
AvailabilityZone: &az,
StartTime: &startTime,
})
if err != nil {
@ -117,8 +120,8 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
}
if price == 0 || current < price {
price = current
if s.AvailabilityZone == "" {
availabilityZone = *history.AvailabilityZone
if azConfig == "" {
az = *history.AvailabilityZone
}
}
}
@ -162,24 +165,26 @@ func (s *StepRunSpotInstance) Run(ctx context.Context, state multistep.StateBag)
UserData: &userData,
IamInstanceProfile: &ec2.IamInstanceProfileSpecification{Name: &s.IamInstanceProfile},
Placement: &ec2.SpotPlacement{
AvailabilityZone: &availabilityZone,
AvailabilityZone: &az,
},
BlockDeviceMappings: s.BlockDevices.BuildLaunchDevices(),
EbsOptimized: &s.EbsOptimized,
}
if s.SubnetId != "" && s.AssociatePublicIpAddress {
subnetId := state.Get("subnet_id").(string)
if subnetId != "" && s.AssociatePublicIpAddress {
runOpts.NetworkInterfaces = []*ec2.InstanceNetworkInterfaceSpecification{
{
DeviceIndex: aws.Int64(0),
AssociatePublicIpAddress: &s.AssociatePublicIpAddress,
SubnetId: &s.SubnetId,
SubnetId: &subnetId,
Groups: securityGroupIds,
DeleteOnTermination: aws.Bool(true),
},
}
} else {
runOpts.SubnetId = &s.SubnetId
runOpts.SubnetId = &subnetId
runOpts.SecurityGroupIds = securityGroupIds
}

View File

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"log"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
@ -17,8 +18,8 @@ import (
type StepSecurityGroup struct {
CommConfig *communicator.Config
SecurityGroupFilter SecurityGroupFilterOptions
SecurityGroupIds []string
VpcId string
TemporarySGSourceCidr string
createdGroupId string
@ -27,6 +28,7 @@ type StepSecurityGroup struct {
func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) multistep.StepAction {
ec2conn := state.Get("ec2").(*ec2.EC2)
ui := state.Get("ui").(packer.Ui)
vpcId := state.Get("vpc_id").(string)
if len(s.SecurityGroupIds) > 0 {
_, err := ec2conn.DescribeSecurityGroups(
@ -45,6 +47,35 @@ func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) mul
return multistep.ActionContinue
}
if !s.SecurityGroupFilter.Empty() {
params := &ec2.DescribeSecurityGroupsInput{}
if vpcId != "" {
s.SecurityGroupFilter.Filters[aws.String("vpc-id")] = &vpcId
}
params.Filters = buildEc2Filters(s.SecurityGroupFilter.Filters)
log.Printf("Using SecurityGroup Filters %v", params)
sgResp, err := ec2conn.DescribeSecurityGroups(params)
if err != nil {
err := fmt.Errorf("Couldn't find security groups for filter: %s", err)
log.Printf("[DEBUG] %s", err.Error())
state.Put("error", err)
return multistep.ActionHalt
}
securityGroupIds := []string{}
for _, sg := range sgResp.SecurityGroups {
securityGroupIds = append(securityGroupIds, *sg.GroupId)
}
ui.Message(fmt.Sprintf("Found Security Group(s): %s", strings.Join(securityGroupIds, ", ")))
state.Put("securityGroupIds", securityGroupIds)
return multistep.ActionContinue
}
port := s.CommConfig.Port()
if port == 0 {
if s.CommConfig.Type != "none" {
@ -60,9 +91,7 @@ func (s *StepSecurityGroup) Run(_ context.Context, state multistep.StateBag) mul
Description: aws.String("Temporary group for Packer"),
}
if s.VpcId != "" {
group.VpcId = &s.VpcId
}
group.VpcId = &vpcId
groupResp, err := ec2conn.CreateSecurityGroup(group)
if err != nil {

View File

@ -25,18 +25,6 @@ type StepSourceAMIInfo struct {
AmiFilters AmiFilterOptions
}
// Build a slice of AMI filter options from the filters provided.
func buildAmiFilters(input map[*string]*string) []*ec2.Filter {
var filters []*ec2.Filter
for k, v := range input {
filters = append(filters, &ec2.Filter{
Name: k,
Values: []*string{v},
})
}
return filters
}
type imageSort []*ec2.Image
func (a imageSort) Len() int { return len(a) }
@ -66,7 +54,7 @@ func (s *StepSourceAMIInfo) Run(_ context.Context, state multistep.StateBag) mul
// We have filters to apply
if len(s.AmiFilters.Filters) > 0 {
params.Filters = buildAmiFilters(s.AmiFilters.Filters)
params.Filters = buildEc2Filters(s.AmiFilters.Filters)
}
if len(s.AmiFilters.Owners) > 0 {
params.Owners = s.AmiFilters.Owners

View File

@ -93,23 +93,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
ec2conn := ec2.New(session)
// If the subnet is specified but not the VpcId or AZ, try to determine them automatically
if b.config.SubnetId != "" && (b.config.AvailabilityZone == "" || b.config.VpcId == "") {
log.Printf("[INFO] Finding AZ and VpcId for the given subnet '%s'", b.config.SubnetId)
resp, err := ec2conn.DescribeSubnets(&ec2.DescribeSubnetsInput{SubnetIds: []*string{&b.config.SubnetId}})
if err != nil {
return nil, err
}
if b.config.AvailabilityZone == "" {
b.config.AvailabilityZone = *resp.Subnets[0].AvailabilityZone
log.Printf("[INFO] AvailabilityZone found: '%s'", b.config.AvailabilityZone)
}
if b.config.VpcId == "" {
b.config.VpcId = *resp.Subnets[0].VpcId
log.Printf("[INFO] VpcId found: '%s'", b.config.VpcId)
}
}
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
@ -123,7 +106,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
if b.config.IsSpotInstance() {
instanceStep = &awscommon.StepRunSpotInstance{
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
BlockDurationMinutes: b.config.BlockDurationMinutes,
Ctx: b.config.ctx,
@ -138,7 +120,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
SpotPrice: b.config.SpotPrice,
SpotPriceProduct: b.config.SpotPriceAutoProduct,
SpotTags: b.config.SpotTags,
SubnetId: b.config.SubnetId,
Tags: b.config.RunTags,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
@ -147,7 +128,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
} else {
instanceStep = &awscommon.StepRunSourceInstance{
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Comm: &b.config.RunConfig.Comm,
Ctx: b.config.ctx,
@ -160,7 +140,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
InstanceType: b.config.InstanceType,
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
SourceAMI: b.config.SourceAmi,
SubnetId: b.config.SubnetId,
Tags: b.config.RunTags,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
@ -181,15 +160,24 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
AmiFilters: b.config.SourceAmiFilter,
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,
},
&awscommon.StepKeyPair{
Debug: b.config.PackerDebug,
Comm: &b.config.RunConfig.Comm,
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
},
&awscommon.StepSecurityGroup{
SecurityGroupFilter: b.config.SecurityGroupFilter,
SecurityGroupIds: b.config.SecurityGroupIds,
CommConfig: &b.config.RunConfig.Comm,
VpcId: b.config.VpcId,
TemporarySGSourceCidr: b.config.TemporarySGSourceCidr,
},
&awscommon.StepCleanupVolumes{

View File

@ -107,23 +107,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
ec2conn := ec2.New(session)
// If the subnet is specified but not the VpcId or AZ, try to determine them automatically
if b.config.SubnetId != "" && (b.config.AvailabilityZone == "" || b.config.VpcId == "") {
log.Printf("[INFO] Finding AZ and VpcId for the given subnet '%s'", b.config.SubnetId)
resp, err := ec2conn.DescribeSubnets(&ec2.DescribeSubnetsInput{SubnetIds: []*string{&b.config.SubnetId}})
if err != nil {
return nil, err
}
if b.config.AvailabilityZone == "" {
b.config.AvailabilityZone = *resp.Subnets[0].AvailabilityZone
log.Printf("[INFO] AvailabilityZone found: '%s'", b.config.AvailabilityZone)
}
if b.config.VpcId == "" {
b.config.VpcId = *resp.Subnets[0].VpcId
log.Printf("[INFO] VpcId found: '%s'", b.config.VpcId)
}
}
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
@ -137,7 +120,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
if b.config.IsSpotInstance() {
instanceStep = &awscommon.StepRunSpotInstance{
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
BlockDurationMinutes: b.config.BlockDurationMinutes,
Ctx: b.config.ctx,
@ -152,7 +134,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
SpotPrice: b.config.SpotPrice,
SpotPriceProduct: b.config.SpotPriceAutoProduct,
SpotTags: b.config.SpotTags,
SubnetId: b.config.SubnetId,
Tags: b.config.RunTags,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
@ -161,7 +142,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
} else {
instanceStep = &awscommon.StepRunSourceInstance{
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Comm: &b.config.RunConfig.Comm,
Ctx: b.config.ctx,
@ -174,7 +154,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
InstanceType: b.config.InstanceType,
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
SourceAMI: b.config.SourceAmi,
SubnetId: b.config.SubnetId,
Tags: b.config.RunTags,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
@ -198,15 +177,24 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
AmiFilters: b.config.SourceAmiFilter,
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,
},
&awscommon.StepKeyPair{
Debug: b.config.PackerDebug,
Comm: &b.config.RunConfig.Comm,
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
},
&awscommon.StepSecurityGroup{
SecurityGroupFilter: b.config.SecurityGroupFilter,
SecurityGroupIds: b.config.SecurityGroupIds,
CommConfig: &b.config.RunConfig.Comm,
VpcId: b.config.VpcId,
TemporarySGSourceCidr: b.config.TemporarySGSourceCidr,
},
&awscommon.StepCleanupVolumes{

View File

@ -92,23 +92,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
ec2conn := ec2.New(session)
// If the subnet is specified but not the VpcId or AZ, try to determine them automatically
if b.config.SubnetId != "" && (b.config.AvailabilityZone == "" || b.config.VpcId == "") {
log.Printf("[INFO] Finding AZ and VpcId for the given subnet '%s'", b.config.SubnetId)
resp, err := ec2conn.DescribeSubnets(&ec2.DescribeSubnetsInput{SubnetIds: []*string{&b.config.SubnetId}})
if err != nil {
return nil, err
}
if b.config.AvailabilityZone == "" {
b.config.AvailabilityZone = *resp.Subnets[0].AvailabilityZone
log.Printf("[INFO] AvailabilityZone found: '%s'", b.config.AvailabilityZone)
}
if b.config.VpcId == "" {
b.config.VpcId = *resp.Subnets[0].VpcId
log.Printf("[INFO] VpcId found: '%s'", b.config.VpcId)
}
}
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
@ -121,7 +104,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
if b.config.IsSpotInstance() {
instanceStep = &awscommon.StepRunSpotInstance{
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.launchBlockDevices,
BlockDurationMinutes: b.config.BlockDurationMinutes,
Ctx: b.config.ctx,
@ -136,7 +118,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
SpotPrice: b.config.SpotPrice,
SpotPriceProduct: b.config.SpotPriceAutoProduct,
SpotTags: b.config.SpotTags,
SubnetId: b.config.SubnetId,
Tags: b.config.RunTags,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
@ -144,7 +125,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
} else {
instanceStep = &awscommon.StepRunSourceInstance{
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.launchBlockDevices,
Comm: &b.config.RunConfig.Comm,
Ctx: b.config.ctx,
@ -157,7 +137,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
InstanceType: b.config.InstanceType,
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
SourceAMI: b.config.SourceAmi,
SubnetId: b.config.SubnetId,
Tags: b.config.RunTags,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
@ -172,15 +151,24 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
EnableAMIENASupport: b.config.AMIENASupport,
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,
},
&awscommon.StepKeyPair{
Debug: b.config.PackerDebug,
Comm: &b.config.RunConfig.Comm,
DebugKeyPath: fmt.Sprintf("ec2_%s.pem", b.config.PackerBuildName),
},
&awscommon.StepSecurityGroup{
SecurityGroupFilter: b.config.SecurityGroupFilter,
SecurityGroupIds: b.config.SecurityGroupIds,
CommConfig: &b.config.RunConfig.Comm,
VpcId: b.config.VpcId,
TemporarySGSourceCidr: b.config.TemporarySGSourceCidr,
},
instanceStep,

View File

@ -177,23 +177,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
}
ec2conn := ec2.New(session)
// If the subnet is specified but not the VpcId or AZ, try to determine them automatically
if b.config.SubnetId != "" && (b.config.AvailabilityZone == "" || b.config.VpcId == "") {
log.Printf("[INFO] Finding AZ and VpcId for the given subnet '%s'", b.config.SubnetId)
resp, err := ec2conn.DescribeSubnets(&ec2.DescribeSubnetsInput{SubnetIds: []*string{&b.config.SubnetId}})
if err != nil {
return nil, err
}
if b.config.AvailabilityZone == "" {
b.config.AvailabilityZone = *resp.Subnets[0].AvailabilityZone
log.Printf("[INFO] AvailabilityZone found: '%s'", b.config.AvailabilityZone)
}
if b.config.VpcId == "" {
b.config.VpcId = *resp.Subnets[0].VpcId
log.Printf("[INFO] VpcId found: '%s'", b.config.VpcId)
}
}
// Setup the state bag and initial state for the steps
state := new(multistep.BasicStateBag)
state.Put("config", &b.config)
@ -207,7 +190,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
if b.config.IsSpotInstance() {
instanceStep = &awscommon.StepRunSpotInstance{
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
BlockDurationMinutes: b.config.BlockDurationMinutes,
Ctx: b.config.ctx,
@ -219,7 +201,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
SourceAMI: b.config.SourceAmi,
SpotPrice: b.config.SpotPrice,
SpotPriceProduct: b.config.SpotPriceAutoProduct,
SubnetId: b.config.SubnetId,
Tags: b.config.RunTags,
SpotTags: b.config.SpotTags,
UserData: b.config.UserData,
@ -228,7 +209,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
} else {
instanceStep = &awscommon.StepRunSourceInstance{
AssociatePublicIpAddress: b.config.AssociatePublicIpAddress,
AvailabilityZone: b.config.AvailabilityZone,
BlockDevices: b.config.BlockDevices,
Comm: &b.config.RunConfig.Comm,
Ctx: b.config.ctx,
@ -239,7 +219,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
InstanceType: b.config.InstanceType,
IsRestricted: b.config.IsChinaCloud() || b.config.IsGovCloud(),
SourceAMI: b.config.SourceAmi,
SubnetId: b.config.SubnetId,
Tags: b.config.RunTags,
UserData: b.config.UserData,
UserDataFile: b.config.UserDataFile,
@ -259,6 +238,15 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
AmiFilters: b.config.SourceAmiFilter,
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,
},
&awscommon.StepKeyPair{
Debug: b.config.PackerDebug,
Comm: &b.config.RunConfig.Comm,
@ -266,8 +254,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe
},
&awscommon.StepSecurityGroup{
CommConfig: &b.config.RunConfig.Comm,
SecurityGroupFilter: b.config.SecurityGroupFilter,
SecurityGroupIds: b.config.SecurityGroupIds,
VpcId: b.config.VpcId,
TemporarySGSourceCidr: b.config.TemporarySGSourceCidr,
},
instanceStep,

View File

@ -278,6 +278,27 @@ builder.
described above. Note that if this is specified, you must omit the
`security_group_id`.
- `security_group_filter` (object) - Filters used to populate the `security_group_ids` field.
Example:
``` json
{
"security_group_filter": {
"filters": {
"tag:Class": "packer"
}
}
}
```
This selects the SG's with tag `Class` with the value `packer`.
- `filters` (map of strings) - filters used to select a `security_group_ids`.
Any filter described in the docs for [DescribeSecurityGroups](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeSecurityGroups.html)
is valid.
`security_group_ids` take precendense over this.
- `temporary_security_group_source_cidr` (string) - An IPv4 CIDR block to be authorized
access to the instance, when packer is creating a temporary security group.
The default is `0.0.0.0/0` (ie, allow any IPv4 source). This is only used
@ -401,6 +422,39 @@ builder.
`subnet-12345def`, where Packer will launch the EC2 instance. This field is
required if you are using an non-default VPC.
- `subnet_filter` (object) - Filters used to populate the `subnet_id` field.
Example:
``` json
{
"subnet_filter": {
"filters": {
"tag:Class": "build"
},
"most_free": true,
"random": false
}
}
```
This selects the Subnet with tag `Class` with the value `build`, which has
the most free IP addresses.
NOTE: This will fail unless *exactly* one Subnet is returned. By using
`most_free` or `random` one will be selected from those matching the filter.
- `filters` (map of strings) - filters used to select a `subnet_id`.
NOTE: This will fail unless *exactly* one Subnet is returned.
Any filter described in the docs for [DescribeSubnets](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeSubnets.html)
is valid.
- `most_free` (boolean) - The Subnet with the most free IPv4 addresses
will be used if multiple Subnets matches the filter.
- `random` (boolean) - A random Subnet will be used if multiple Subnets
matches the filter. `most_free` have precendence over this.
`subnet_id` take precedence over this.
- `tags` (object of key/value strings) - Tags applied to the AMI and
relevant snapshots. This is a
[template engine](/docs/templates/engine.html),
@ -427,6 +481,32 @@ builder.
to be set. If this field is left blank, Packer will try to get the VPC ID from the
`subnet_id`.
- `vpc_filter` (object) - Filters used to populate the `vpc_id` field.
Example:
``` json
{
"vpc_filter": {
"filters": {
"tag:Class": "build",
"isDefault": "false",
"cidr": "/24"
}
}
}
```
This selects the VPC with tag `Class` with the value `build`, which is not the
default VPC, and have a IPv4 CIDR block of `/24`.
NOTE: This will fail unless *exactly* one VPC is returned.
- `filters` (map of strings) - filters used to select a `vpc_id`.
NOTE: This will fail unless *exactly* one VPC is returned.
Any filter described in the docs for [DescribeVpcs](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeVpcs.html)
is valid.
`vpc_id` take precedence over this.
- `windows_password_timeout` (string) - The timeout for waiting for a Windows
password for Windows instances. Defaults to 20 minutes. Example value: `10m`

View File

@ -271,6 +271,27 @@ builder.
described above. Note that if this is specified, you must omit the
`security_group_id`.
- `security_group_filter` (object) - Filters used to populate the `security_group_ids` field.
Example:
``` json
{
"security_group_filter": {
"filters": {
"tag:Class": "packer"
}
}
}
```
This selects the SG's with tag `Class` with the value `packer`.
- `filters` (map of strings) - filters used to select a `security_group_ids`.
Any filter described in the docs for [DescribeSecurityGroups](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeSecurityGroups.html)
is valid.
`security_group_ids` take precendense over this.
- `temporary_security_group_source_cidr` (string) - An IPv4 CIDR block to be authorized
access to the instance, when packer is creating a temporary security group.
The default is `0.0.0.0/0` (ie, allow any IPv4 source). This is only used
@ -395,6 +416,39 @@ builder.
`subnet-12345def`, where Packer will launch the EC2 instance. This field is
required if you are using an non-default VPC.
- `subnet_filter` (object) - Filters used to populate the `subnet_id` field.
Example:
``` json
{
"subnet_filter": {
"filters": {
"tag:Class": "build"
},
"most_free": true,
"random": false
}
}
```
This selects the Subnet with tag `Class` with the value `build`, which has
the most free IP addresses.
NOTE: This will fail unless *exactly* one Subnet is returned. By using
`most_free` or `random` one will be selected from those matching the filter.
- `filters` (map of strings) - filters used to select a `subnet_id`.
NOTE: This will fail unless *exactly* one Subnet is returned.
Any filter described in the docs for [DescribeSubnets](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeSubnets.html)
is valid.
- `most_free` (boolean) - The Subnet with the most free IPv4 addresses
will be used if multiple Subnets matches the filter.
- `random` (boolean) - A random Subnet will be used if multiple Subnets
matches the filter. `most_free` have precendence over this.
`subnet_id` take precedence over this.
- `tags` (object of key/value strings) - Tags applied to the AMI and
relevant snapshots. This is a
[template engine](/docs/templates/engine.html),
@ -420,6 +474,32 @@ builder.
to be set. If this field is left blank, Packer will try to get the VPC ID from the
`subnet_id`.
- `vpc_filter` (object) - Filters used to populate the `vpc_id` field.
Example:
``` json
{
"vpc_filter": {
"filters": {
"tag:Class": "build",
"isDefault": "false",
"cidr": "/24"
}
}
}
```
This selects the VPC with tag `Class` with the value `build`, which is not the
default VPC, and have a IPv4 CIDR block of `/24`.
NOTE: This will fail unless *exactly* one VPC is returned.
- `filters` (map of strings) - filters used to select a `vpc_id`.
NOTE: This will fail unless *exactly* one VPC is returned.
Any filter described in the docs for [DescribeVpcs](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeVpcs.html)
is valid.
`vpc_id` take precedence over this.
- `windows_password_timeout` (string) - The timeout for waiting for a Windows
password for Windows instances. Defaults to 20 minutes. Example value: `10m`

View File

@ -204,6 +204,27 @@ builder.
described above. Note that if this is specified, you must omit the
`security_group_id`.
- `security_group_filter` (object) - Filters used to populate the `security_group_ids` field.
Example:
``` json
{
"security_group_filter": {
"filters": {
"tag:Class": "packer"
}
}
}
```
This selects the SG's with tag `Class` with the value `packer`.
- `filters` (map of strings) - filters used to select a `security_group_ids`.
Any filter described in the docs for [DescribeSecurityGroups](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeSecurityGroups.html)
is valid.
`security_group_ids` take precendense over this.
- `temporary_security_group_source_cidr` (string) - An IPv4 CIDR block to be authorized
access to the instance, when packer is creating a temporary security group.
The default is `0.0.0.0/0` (ie, allow any IPv4 source). This is only used
@ -313,6 +334,39 @@ builder.
`subnet-12345def`, where Packer will launch the EC2 instance. This field is
required if you are using an non-default VPC.
- `subnet_filter` (object) - Filters used to populate the `subnet_id` field.
Example:
``` json
{
"subnet_filter": {
"filters": {
"tag:Class": "build"
},
"most_free": true,
"random": false
}
}
```
This selects the Subnet with tag `Class` with the value `build`, which has
the most free IP addresses.
NOTE: This will fail unless *exactly* one Subnet is returned. By using
`most_free` or `random` one will be selected from those matching the filter.
- `filters` (map of strings) - filters used to select a `subnet_id`.
NOTE: This will fail unless *exactly* one Subnet is returned.
Any filter described in the docs for [DescribeSubnets](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeSubnets.html)
is valid.
- `most_free` (boolean) - The Subnet with the most free IPv4 addresses
will be used if multiple Subnets matches the filter.
- `random` (boolean) - A random Subnet will be used if multiple Subnets
matches the filter. `most_free` have precendence over this.
`subnet_id` take precedence over this.
- `temporary_key_pair_name` (string) - The name of the temporary key pair
to generate. By default, Packer generates a name that looks like
`packer_<UUID>`, where &lt;UUID&gt; is a 36 character unique identifier.
@ -334,6 +388,32 @@ builder.
to be set. If this field is left blank, Packer will try to get the VPC ID from the
`subnet_id`.
- `vpc_filter` (object) - Filters used to populate the `vpc_id` field.
Example:
``` json
{
"vpc_filter": {
"filters": {
"tag:Class": "build",
"isDefault": "false",
"cidr": "/24"
}
}
}
```
This selects the VPC with tag `Class` with the value `build`, which is not the
default VPC, and have a IPv4 CIDR block of `/24`.
NOTE: This will fail unless *exactly* one VPC is returned.
- `filters` (map of strings) - filters used to select a `vpc_id`.
NOTE: This will fail unless *exactly* one VPC is returned.
Any filter described in the docs for [DescribeVpcs](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeVpcs.html)
is valid.
`vpc_id` take precedence over this.
- `windows_password_timeout` (string) - The timeout for waiting for a Windows
password for Windows instances. Defaults to 20 minutes. Example value: `10m`

View File

@ -280,6 +280,27 @@ builder.
described above. Note that if this is specified, you must omit the
`security_group_id`.
- `security_group_filter` (object) - Filters used to populate the `security_group_ids` field.
Example:
``` json
{
"security_group_filter": {
"filters": {
"tag:Class": "packer"
}
}
}
```
This selects the SG's with tag `Class` with the value `packer`.
- `filters` (map of strings) - filters used to select a `security_group_ids`.
Any filter described in the docs for [DescribeSecurityGroups](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeSecurityGroups.html)
is valid.
`security_group_ids` take precendense over this.
- `temporary_security_group_source_cidr` (string) - An IPv4 CIDR block to be authorized
access to the instance, when packer is creating a temporary security group.
The default is `0.0.0.0/0` (ie, allow any IPv4 source). This is only used
@ -397,6 +418,39 @@ builder.
`subnet-12345def`, where Packer will launch the EC2 instance. This field is
required if you are using an non-default VPC.
- `subnet_filter` (object) - Filters used to populate the `subnet_id` field.
Example:
``` json
{
"subnet_filter": {
"filters": {
"tag:Class": "build"
},
"most_free": true,
"random": false
}
}
```
This selects the Subnet with tag `Class` with the value `build`, which has
the most free IP addresses.
NOTE: This will fail unless *exactly* one Subnet is returned. By using
`most_free` or `random` one will be selected from those matching the filter.
- `filters` (map of strings) - filters used to select a `subnet_id`.
NOTE: This will fail unless *exactly* one Subnet is returned.
Any filter described in the docs for [DescribeSubnets](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeSubnets.html)
is valid.
- `most_free` (boolean) - The Subnet with the most free IPv4 addresses
will be used if multiple Subnets matches the filter.
- `random` (boolean) - A random Subnet will be used if multiple Subnets
matches the filter. `most_free` have precendence over this.
`subnet_id` take precedence over this.
- `tags` (object of key/value strings) - Tags applied to the AMI. This is a
[template engine](/docs/templates/engine.html),
see [Build template data](#build-template-data) for more information.
@ -417,6 +471,32 @@ builder.
to be set. If this field is left blank, Packer will try to get the VPC ID from the
`subnet_id`.
- `vpc_filter` (object) - Filters used to populate the `vpc_id` field.
Example:
``` json
{
"vpc_filter": {
"filters": {
"tag:Class": "build",
"isDefault": "false",
"cidr": "/24"
}
}
}
```
This selects the VPC with tag `Class` with the value `build`, which is not the
default VPC, and have a IPv4 CIDR block of `/24`.
NOTE: This will fail unless *exactly* one VPC is returned.
- `filters` (map of strings) - filters used to select a `vpc_id`.
NOTE: This will fail unless *exactly* one VPC is returned.
Any filter described in the docs for [DescribeVpcs](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeVpcs.html)
is valid.
`vpc_id` take precedence over this.
- `x509_upload_path` (string) - The path on the remote machine where the X509
certificate will be uploaded. This path must already exist and be writable.
X509 certificates are uploaded after provisioning is run, so it is perfectly