2020-06-24 08:31:05 -04:00
//go:generate struct-markdown
2019-10-12 04:46:21 -04:00
package common
2019-06-13 03:16:49 -04:00
import (
"fmt"
2020-06-24 08:31:05 -04:00
"os"
2019-10-14 10:43:59 -04:00
"regexp"
2020-12-17 16:29:25 -05:00
"github.com/hashicorp/packer-plugin-sdk/communicator"
"github.com/hashicorp/packer-plugin-sdk/template/interpolate"
"github.com/hashicorp/packer-plugin-sdk/uuid"
2019-06-13 03:16:49 -04:00
)
type RunConfig struct {
2020-06-24 08:31:05 -04:00
// This is the UCloud availability zone where UHost instance is located. such as: `cn-bj2-02`.
// You may refer to [list of availability_zone](https://docs.ucloud.cn/api/summary/regionlist)
Zone string ` mapstructure:"availability_zone" required:"true" `
// This is the ID of base image which you want to create your customized images with.
SourceImageId string ` mapstructure:"source_image_id" required:"true" `
// The type of UHost instance.
// You may refer to [list of instance type](https://docs.ucloud.cn/compute/terraform/specification/instance)
InstanceType string ` mapstructure:"instance_type" required:"true" `
// The name of instance, which contains 1-63 characters and only support Chinese,
// English, numbers, '-', '\_', '.'.
InstanceName string ` mapstructure:"instance_name" required:"false" `
// The type of boot disk associated to UHost instance.
// Possible values are: `cloud_ssd` and `cloud_rssd` for cloud boot disk, `local_normal` and `local_ssd`
// for local boot disk. (Default: `cloud_ssd`). The `cloud_ssd` and `local_ssd` are not fully supported
// by all regions as boot disk type, please proceed to UCloud console for more details.
//
//~> **Note:** It takes around 10 mins for boot disk initialization when `boot_disk_type` is `local_normal` or `local_ssd`.
BootDiskType string ` mapstructure:"boot_disk_type" required:"false" `
// The ID of VPC linked to the UHost instance. If not defined `vpc_id`, the instance will use the default VPC in the current region.
VPCId string ` mapstructure:"vpc_id" required:"false" `
// The ID of subnet under the VPC. If `vpc_id` is defined, the `subnet_id` is mandatory required.
// If `vpc_id` and `subnet_id` are not defined, the instance will use the default subnet in the current region.
SubnetId string ` mapstructure:"subnet_id" required:"false" `
// The ID of the fire wall associated to UHost instance. If `security_group_id` is not defined,
// the instance will use the non-recommended web fire wall, and open port include 22, 3389 by default.
// It is supported by ICMP fire wall protocols.
// You may refer to [security group_id](https://docs.ucloud.cn/network/firewall/firewall).
SecurityGroupId string ` mapstructure:"security_group_id" required:"false" `
// Maximum bandwidth to the elastic public network, measured in Mbps (Mega bit per second). (Default: `10`).
EipBandwidth int ` mapstructure:"eip_bandwidth" required:"false" `
// Elastic IP charge mode. Possible values are: `traffic` as pay by traffic, `bandwidth` as pay by bandwidth,
// `post_accurate_bandwidth` as post pay mode. (Default: `traffic`).
// Note currently default `traffic` eip charge mode not not fully support by all `availability_zone`
// in the `region`, please proceed to [UCloud console](https://console.ucloud.cn/unet/eip/create) for more details.
// You may refer to [eip introduction](https://docs.ucloud.cn/unet/eip/introduction).
EipChargeMode string ` mapstructure:"eip_charge_mode" required:"false" `
// User data to apply when launching the instance.
// Note that you need to be careful about escaping characters due to the templates
// being JSON. It is often more convenient to use user_data_file, instead.
// Packer will not automatically wait for a user script to finish before
// shutting down the instance this must be handled in a provisioner.
// You may refer to [user_data_document](https://docs.ucloud.cn/uhost/guide/metadata/userdata)
UserData string ` mapstructure:"user_data" required:"false" `
// Path to a file that will be used for the user data when launching the instance.
UserDataFile string ` mapstructure:"user_data_file" required:"false" `
// Specifies a minimum CPU platform for the the VM instance. (Default: `Intel/Auto`).
// You may refer to [min_cpu_platform](https://docs.ucloud.cn/uhost/introduction/uhost/type_new)
// - The Intel CPU platform:
// - `Intel/Auto` as the Intel CPU platform version will be selected randomly by system;
// - `Intel/IvyBridge` as Intel V2, the version of Intel CPU platform selected by system will be `Intel/IvyBridge` and above;
// - `Intel/Haswell` as Intel V3, the version of Intel CPU platform selected by system will be `Intel/Haswell` and above;
// - `Intel/Broadwell` as Intel V4, the version of Intel CPU platform selected by system will be `Intel/Broadwell` and above;
// - `Intel/Skylake` as Intel V5, the version of Intel CPU platform selected by system will be `Intel/Skylake` and above;
// - `Intel/Cascadelake` as Intel V6, the version of Intel CPU platform selected by system will be `Intel/Cascadelake`;
// - The AMD CPU platform:
// - `Amd/Auto` as the Amd CPU platform version will be selected randomly by system;
// - `Amd/Epyc2` as the version of Amd CPU platform selected by system will be `Amd/Epyc2` and above;
MinCpuPlatform string ` mapstructure:"min_cpu_platform" required:"false" `
2019-06-13 03:16:49 -04:00
// Communicator settings
2020-06-24 08:31:05 -04:00
Comm communicator . Config ` mapstructure:",squash" `
// If this value is true, packer will connect to the created UHost instance via a private ip
// instead of allocating an EIP (elastic public ip).(Default: `false`).
UseSSHPrivateIp bool ` mapstructure:"use_ssh_private_ip" `
2019-06-13 03:16:49 -04:00
}
var instanceNamePattern = regexp . MustCompile ( ` ^[A-Za-z0-9\p { Han}-_.] { 1,63}$ ` )
func ( c * RunConfig ) Prepare ( ctx * interpolate . Context ) [ ] error {
errs := c . Comm . Prepare ( ctx )
if c . Zone == "" {
errs = append ( errs , fmt . Errorf ( "%q must be set" , "availability_zone" ) )
}
if c . SourceImageId == "" {
errs = append ( errs , fmt . Errorf ( "%q must be set" , "source_image_id" ) )
}
if c . InstanceType == "" {
errs = append ( errs , fmt . Errorf ( "%q must be set" , "instance_type" ) )
2019-10-12 04:46:21 -04:00
} else if _ , err := ParseInstanceType ( c . InstanceType ) ; err != nil {
2019-06-13 03:16:49 -04:00
errs = append ( errs , err )
}
if ( c . VPCId != "" && c . SubnetId == "" ) || ( c . VPCId == "" && c . SubnetId != "" ) {
errs = append ( errs , fmt . Errorf ( "expected both %q and %q to set or not set" , "vpc_id" , "subnet_id" ) )
}
if c . BootDiskType == "" {
c . BootDiskType = "cloud_ssd"
2019-10-12 04:46:21 -04:00
} else if err := CheckStringIn ( c . BootDiskType ,
2020-06-24 08:31:05 -04:00
[ ] string { "local_normal" , "local_ssd" , "cloud_ssd" , "cloud_rssd" } ) ; err != nil {
2019-06-13 03:16:49 -04:00
errs = append ( errs , err )
}
if c . InstanceName == "" {
c . InstanceName = fmt . Sprintf ( "packer-%s" , uuid . TimeOrderedUUID ( ) [ : 8 ] )
} else if ! instanceNamePattern . MatchString ( c . InstanceName ) {
errs = append ( errs , fmt . Errorf ( "expected %q to be 1-63 characters and only support chinese, english, numbers, '-_.', got %q" , "instance_name" , c . InstanceName ) )
}
if c . UseSSHPrivateIp == true && c . VPCId == "" {
errs = append ( errs , fmt . Errorf ( "%q must be set when use_ssh_private_ip is true" , "vpc_id" ) )
}
2019-06-19 09:32:33 -04:00
if c . Comm . SSHPassword != "" && len ( validateInstancePassword ( c . Comm . SSHPassword ) ) != 0 {
for _ , v := range validateInstancePassword ( c . Comm . SSHPassword ) {
errs = append ( errs , v )
}
}
2020-06-24 08:31:05 -04:00
if c . UserData != "" && c . UserDataFile != "" {
errs = append ( errs , fmt . Errorf ( "only one of user_data or user_data_file can be specified" ) )
} else if c . UserDataFile != "" {
if _ , err := os . Stat ( c . UserDataFile ) ; err != nil {
errs = append ( errs , fmt . Errorf ( "user_data_file not found: %s" , c . UserDataFile ) )
}
}
if c . MinCpuPlatform == "" {
c . MinCpuPlatform = "Intel/Auto"
} else if err := CheckStringIn ( c . MinCpuPlatform ,
[ ] string {
"Intel/Auto" ,
"Intel/IvyBridge" ,
"Intel/Haswell" ,
"Intel/Broadwell" ,
"Intel/Skylake" ,
"Intel/Cascadelake" ,
"Amd/Auto" ,
"Amd/Epyc2" ,
} ) ; err != nil {
errs = append ( errs , err )
}
if c . EipChargeMode == "" {
c . EipChargeMode = "traffic"
} else if err := CheckStringIn ( c . EipChargeMode , [ ] string { "traffic" , "bandwidth" , "post_accurate_bandwidth" } ) ; err != nil {
errs = append ( errs , err )
}
if c . EipBandwidth == 0 {
c . EipBandwidth = 10
2019-06-13 03:16:49 -04:00
}
2020-06-24 08:31:05 -04:00
return errs
2019-06-13 03:16:49 -04:00
}
2019-06-19 09:32:33 -04:00
var instancePasswordUpperPattern = regexp . MustCompile ( ` [A-Z] ` )
var instancePasswordLowerPattern = regexp . MustCompile ( ` [a-z] ` )
var instancePasswordNumPattern = regexp . MustCompile ( ` [0-9] ` )
var instancePasswordSpecialPattern = regexp . MustCompile ( ` [ ` + "`" + ` ()~!@#$%^&*-+=_| { }\[\]:;'<>,.?/] ` )
var instancePasswordPattern = regexp . MustCompile ( ` ^[A-Za-z0-9 ` + "`" + ` ()~!@#$%^&*-+=_| { }\[\]:;'<>,.?/] { 8,30}$ ` )
func validateInstancePassword ( password string ) ( errors [ ] error ) {
if ! instancePasswordPattern . MatchString ( password ) {
errors = append ( errors , fmt . Errorf ( "%q is invalid, should have between 8-30 characters and any characters must be legal, got %q" , "ssh_password" , password ) )
}
categoryCount := 0
if instancePasswordUpperPattern . MatchString ( password ) {
categoryCount ++
}
if instancePasswordLowerPattern . MatchString ( password ) {
categoryCount ++
}
if instancePasswordNumPattern . MatchString ( password ) {
categoryCount ++
}
if instancePasswordSpecialPattern . MatchString ( password ) {
categoryCount ++
}
if categoryCount < 2 {
errors = append ( errors , fmt . Errorf ( "%q is invalid, should have least 2 items of capital letters, lower case letters, numbers and special characters, got %q" , "ssh_password" , password ) )
}
return
}