package cloudstack import ( "errors" "fmt" "os" "time" "github.com/hashicorp/packer/common" "github.com/hashicorp/packer/common/uuid" "github.com/hashicorp/packer/helper/communicator" "github.com/hashicorp/packer/helper/config" "github.com/hashicorp/packer/packer" "github.com/hashicorp/packer/template/interpolate" ) // Config holds all the details needed to configure the builder. type Config struct { common.PackerConfig `mapstructure:",squash"` common.HTTPConfig `mapstructure:",squash"` Comm communicator.Config `mapstructure:",squash"` // The CloudStack API endpoint we will connect to. It can // also be specified via environment variable CLOUDSTACK_API_URL, if set. APIURL string `mapstructure:"api_url" required:"true"` // The API key used to sign all API requests. It can also // be specified via environment variable CLOUDSTACK_API_KEY, if set. APIKey string `mapstructure:"api_key" required:"true"` // The secret key used to sign all API requests. It // can also be specified via environment variable CLOUDSTACK_SECRET_KEY, if // set. SecretKey string `mapstructure:"secret_key" required:"true"` // The time duration to wait for async calls to // finish. Defaults to 30m. AsyncTimeout time.Duration `mapstructure:"async_timeout" required:"false"` // Some cloud providers only allow HTTP GET calls // to their CloudStack API. If using such a provider, you need to set this to // true in order for the provider to only make GET calls and no POST calls. HTTPGetOnly bool `mapstructure:"http_get_only" required:"false"` // Set to true to skip SSL verification. // Defaults to false. SSLNoVerify bool `mapstructure:"ssl_no_verify" required:"false"` // List of CIDR's that will have access to the new // instance. This is needed in order for any provisioners to be able to // connect to the instance. Defaults to [ "0.0.0.0/0" ]. Only required when // use_local_ip_address is false. CIDRList []string `mapstructure:"cidr_list" required:"false"` // If true a temporary security group // will be created which allows traffic towards the instance from the // cidr_list. This option will be ignored if security_groups is also // defined. Requires expunge set to true. Defaults to false. CreateSecurityGroup bool `mapstructure:"create_security_group" required:"false"` // The name or ID of the disk offering used for the // instance. This option is only available (and also required) when using // source_iso. DiskOffering string `mapstructure:"disk_offering" required:"false"` // The size (in GB) of the root disk of the new // instance. This option is only available when using source_template. DiskSize int64 `mapstructure:"disk_size" required:"false"` // Set to true to expunge the instance when it is // destroyed. Defaults to false. Expunge bool `mapstructure:"expunge" required:"false"` // The target hypervisor (e.g. XenServer, KVM) for // the new template. This option is required when using source_iso. Hypervisor string `mapstructure:"hypervisor" required:"false"` // The name of the instance. Defaults to // "packer-UUID" where UUID is dynamically generated. InstanceName string `mapstructure:"instance_name" required:"false"` // The name or ID of the network to connect the instance // to. Network string `mapstructure:"network" required:"true"` // The name or ID of the project to deploy the instance // to. Project string `mapstructure:"project" required:"false"` // The public IP address or it's ID used for // connecting any provisioners to. If not provided, a temporary public IP // address will be associated and released during the Packer run. PublicIPAddress string `mapstructure:"public_ip_address" required:"false"` // The fixed port you want to configure in the port // forwarding rule. Set this attribute if you do not want to use the a random // public port. PublicPort int `mapstructure:"public_port" required:"false"` // A list of security group IDs or // names to associate the instance with. SecurityGroups []string `mapstructure:"security_groups" required:"false"` // The name or ID of the service offering used // for the instance. ServiceOffering string `mapstructure:"service_offering" required:"true"` // Set to true to prevent network // ACLs or firewall rules creation. Defaults to false. PreventFirewallChanges bool `mapstructure:"prevent_firewall_changes" required:"false"` // The name or ID of an ISO that will be mounted // before booting the instance. This option is mutually exclusive with // source_template. When using source_iso, both disk_offering and // hypervisor are required. SourceISO string `mapstructure:"source_iso" required:"true"` // The name or ID of the template used as base // template for the instance. This option is mutually exclusive with // source_iso. SourceTemplate string `mapstructure:"source_template" required:"true"` // The name of the temporary SSH key pair // to generate. By default, Packer generates a name that looks like // packer_, where is a 36 character unique identifier. TemporaryKeypairName string `mapstructure:"temporary_keypair_name" required:"false"` // Set to true to indicate that the // provisioners should connect to the local IP address of the instance. UseLocalIPAddress bool `mapstructure:"use_local_ip_address" required:"false"` // User data to launch with the instance. This is a // template engine see User Data bellow for // more details. Packer will not automatically wait for a user script to // finish before shutting down the instance this must be handled in a // provisioner. UserData string `mapstructure:"user_data" required:"false"` // Path to a file that will be used for the user // data when launching the instance. This file will be parsed as a template // engine see User Data bellow for more // details. UserDataFile string `mapstructure:"user_data_file" required:"false"` // The name or ID of the zone where the instance will be // created. Zone string `mapstructure:"zone" required:"true"` // The name of the new template. Defaults to // "packer-{{timestamp}}" where timestamp will be the current time. TemplateName string `mapstructure:"template_name" required:"false"` // The display text of the new template. // Defaults to the template_name. TemplateDisplayText string `mapstructure:"template_display_text" required:"false"` // The name or ID of the template OS for the new // template that will be created. TemplateOS string `mapstructure:"template_os" required:"true"` // Set to true to indicate that the template // is featured. Defaults to false. TemplateFeatured bool `mapstructure:"template_featured" required:"false"` // Set to true to indicate that the template // is available for all accounts. Defaults to false. TemplatePublic bool `mapstructure:"template_public" required:"false"` // Set to true to indicate the // template should be password enabled. Defaults to false. TemplatePasswordEnabled bool `mapstructure:"template_password_enabled" required:"false"` // Set to true to indicate the template // requires hardware-assisted virtualization. Defaults to false. TemplateRequiresHVM bool `mapstructure:"template_requires_hvm" required:"false"` // Set to true to indicate that the template // contains tools to support dynamic scaling of VM cpu/memory. Defaults to // false. TemplateScalable bool `mapstructure:"template_scalable" required:"false"` TemplateTag string `mapstructure:"template_tag"` Tags map[string]string `mapstructure:"tags"` ctx interpolate.Context } // NewConfig parses and validates the given config. func NewConfig(raws ...interface{}) (*Config, error) { c := new(Config) err := config.Decode(c, &config.DecodeOpts{ Interpolate: true, InterpolateContext: &c.ctx, InterpolateFilter: &interpolate.RenderFilter{ Exclude: []string{ "user_data", }, }, }, raws...) if err != nil { return nil, err } var errs *packer.MultiError // Set some defaults. if c.APIURL == "" { // Default to environment variable for api_url, if it exists c.APIURL = os.Getenv("CLOUDSTACK_API_URL") } if c.APIKey == "" { // Default to environment variable for api_key, if it exists c.APIKey = os.Getenv("CLOUDSTACK_API_KEY") } if c.SecretKey == "" { // Default to environment variable for secret_key, if it exists c.SecretKey = os.Getenv("CLOUDSTACK_SECRET_KEY") } if c.AsyncTimeout == 0 { c.AsyncTimeout = 30 * time.Minute } if len(c.CIDRList) == 0 { c.CIDRList = []string{"0.0.0.0/0"} } if c.InstanceName == "" { c.InstanceName = fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()) } if c.TemplateName == "" { name, err := interpolate.Render("packer-{{timestamp}}", nil) if err != nil { errs = packer.MultiErrorAppend(errs, fmt.Errorf("Unable to parse template name: %s ", err)) } c.TemplateName = name } if c.TemplateDisplayText == "" { c.TemplateDisplayText = c.TemplateName } // If we are not given an explicit keypair, ssh_password or ssh_private_key_file, // then create a temporary one, but only if the temporary_keypair_name has not // been provided. if c.Comm.SSHKeyPairName == "" && c.Comm.SSHTemporaryKeyPairName == "" && c.Comm.SSHPrivateKeyFile == "" && c.Comm.SSHPassword == "" { c.Comm.SSHTemporaryKeyPairName = fmt.Sprintf("packer_%s", uuid.TimeOrderedUUID()) } // Process required parameters. if c.APIURL == "" { errs = packer.MultiErrorAppend(errs, errors.New("a api_url must be specified")) } if c.APIKey == "" { errs = packer.MultiErrorAppend(errs, errors.New("a api_key must be specified")) } if c.SecretKey == "" { errs = packer.MultiErrorAppend(errs, errors.New("a secret_key must be specified")) } if c.Network == "" { errs = packer.MultiErrorAppend(errs, errors.New("a network must be specified")) } if c.CreateSecurityGroup && !c.Expunge { errs = packer.MultiErrorAppend(errs, errors.New("auto creating a temporary security group requires expunge")) } if c.ServiceOffering == "" { errs = packer.MultiErrorAppend(errs, errors.New("a service_offering must be specified")) } if c.SourceISO == "" && c.SourceTemplate == "" { errs = packer.MultiErrorAppend( errs, errors.New("either source_iso or source_template must be specified")) } if c.SourceISO != "" && c.SourceTemplate != "" { errs = packer.MultiErrorAppend( errs, errors.New("only one of source_iso or source_template can be specified")) } if c.SourceISO != "" && c.DiskOffering == "" { errs = packer.MultiErrorAppend( errs, errors.New("a disk_offering must be specified when using source_iso")) } if c.SourceISO != "" && c.Hypervisor == "" { errs = packer.MultiErrorAppend( errs, errors.New("a hypervisor must be specified when using source_iso")) } if c.TemplateOS == "" { errs = packer.MultiErrorAppend(errs, errors.New("a template_os must be specified")) } if c.UserData != "" && c.UserDataFile != "" { errs = packer.MultiErrorAppend( errs, errors.New("only one of user_data or user_data_file can be specified")) } if c.UserDataFile != "" { if _, err := os.Stat(c.UserDataFile); err != nil { errs = packer.MultiErrorAppend( errs, fmt.Errorf("user_data_file not found: %s", c.UserDataFile)) } } if c.Zone == "" { errs = packer.MultiErrorAppend(errs, errors.New("a zone must be specified")) } if es := c.Comm.Prepare(&c.ctx); len(es) > 0 { errs = packer.MultiErrorAppend(errs, es...) } // Check for errors and return if we have any. if errs != nil && len(errs.Errors) > 0 { return nil, errs } return c, nil }