diff --git a/Makefile b/Makefile index 733f567d3..9dd9528d0 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ test: fmtcheck vet # testacc runs acceptance tests # e.g make testacc TESTARGS="-run TestAccSoftLayerScaleGroup_Basic" testacc: fmtcheck vet - TF_ACC=1 go test $(TEST) -v $(TESTARGS) -timeout 120m + TF_ACC=1 go test $(TEST) -v $(TESTARGS) -timeout 1440m # testrace runs the race checker testrace: fmtcheck vet diff --git a/docs/resources/softlayer_bare_metal.md b/docs/resources/softlayer_bare_metal.md index f13e81fa7..7053d3052 100644 --- a/docs/resources/softlayer_bare_metal.md +++ b/docs/resources/softlayer_bare_metal.md @@ -1,28 +1,156 @@ # `softlayer_bare_metal` -Provides a `bare_metal` resource. This allows bare metals to be created, updated and deleted. +Provides a `bare_metal` resource. This allows bare metals to be created, updated and deleted. `softlayer_bare_metal` resource supports both monthly bare metal servers and hourly bare metal servers. + For more detail on bare metal seves, refer to the [link](https://www.ibm.com/cloud-computing/bluemix/bare-metal-servers) +## Hourly bare metal server +If the `softlayer_bare_metal` resource definition has a `fixed_config_preset` attribute, terraform will create an hourly +bare metal server. The following example creates a new hourly bare metal server. Hardware specifications +are already defined in the `fixed_config_preset` attribute and cannot be modified. + +### Example of an hourly bare metal server ```hcl -# Create a new bare metal -resource "softlayer_bare_metal" "twc_terraform_sample" { - hostname = "twc-terraform-sample-name" - domain = "bar.example.com" +resource "softlayer_bare_metal" "hourly-bm1" { + hostname = "hourly-bm1" + domain = "example.com" + os_reference_code = "UBUNTU_16_64" + datacenter = "dal01" + network_speed = 100 # Optional + hourly_billing = true # Optional + private_network_only = false # Optional + fixed_config_preset = "S1270_8GB_2X1TBSATA_NORAID" +} +``` + +In addition, users can use configure optional attributes such as `user_metadata`, `tags`, and `notes` attributes as follows: + +### Example of additional attributes for the hourly bare metal server +```hcl +resource "softlayer_bare_metal" "hourly-bm1" { + hostname = "hourly-bm1" + domain = "example.com" os_reference_code = "UBUNTU_16_64" datacenter = "dal01" network_speed = 100 # Optional hourly_billing = true # Optional private_network_only = false # Optional - user_metadata = "{\"value\":\"newvalue\"}" # Optional - public_vlan_id = 12345678 # Optional - private_vlan_id = 87654321 # Optional - public_subnet = "50.97.46.160/28" # Optional - private_subnet = "10.56.109.128/26" # Optional fixed_config_preset = "S1270_8GB_2X1TBSATA_NORAID" - image_template_id = 12345 # Optional + + user_metadata = "{\"value\":\"newvalue\"}" # Optional tags = [ "collectd", "mesos-master" ] + notes = "note test" +} +``` + +## Monthly bare metal server +If the `fixed_config_preset` attribute is not configured, terraform will consider it as a monthly bare metal server resource. It provides +options to configure process, memory, network, disk, and RAID. Users also can assign VLANs and subnets for the target monthly bare metal server. To configure the monthly bare +metal server, you need to provide additional attributes such as `package_key_name`, `proecss_key_name`, `disk_key_names`, and `os_key_name`. The following example describes a basic configuration + of the monthly bare metal server. + +### Example of a monthly bare metal server +```hcl +resource "softlayer_bare_metal" "monthly_bm1" { + package_key_name = "DUAL_E52600_V4_12_DRIVES" + process_key_name = "INTEL_INTEL_XEON_E52620_V4_2_10" + memory = 64 + os_key_name = "OS_WINDOWS_2012_R2_FULL_DC_64_BIT_2" + hostname = "cust-bm" + domain = "ms.com" + datacenter = "wdc04" + network_speed = 100 + public_bandwidth = 500 + disk_key_names = [ "HARD_DRIVE_800GB_SSD", "HARD_DRIVE_800GB_SSD", "HARD_DRIVE_800GB_SSD" ] + hourly_billing = false +} +``` + +Users can configure additional options. The following example configures target VLANs, subnets, and a RAID controller. `storage_groups` +configures RAIDs and disk partitioning. +### Example of a monthly bare metal server with additional options +```hcl +resource "softlayer_bare_metal" "monthly_bm1" { + +# Mandatory attributes + package_key_name = "DUAL_E52600_V4_12_DRIVES" + process_key_name = "INTEL_INTEL_XEON_E52620_V4_2_10" + memory = 64 + os_key_name = "OS_WINDOWS_2012_R2_FULL_DC_64_BIT_2" + hostname = "cust-bm" + domain = "ms.com" + datacenter = "wdc04" + network_speed = 100 + public_bandwidth = 500 + disk_key_names = [ "HARD_DRIVE_800GB_SSD", "HARD_DRIVE_800GB_SSD", "HARD_DRIVE_800GB_SSD" ] + hourly_billing = false + +# Optional attributes + private_network_only = false + unbonded_network = true + user_metadata = "{\"value\":\"newvalue\"}" + public_vlan_id = 12345678 + private_vlan_id = 87654321 + public_subnet = "50.97.46.160/28" + private_subnet = "10.56.109.128/26" + tags = [ + "collectd", + "mesos-master" + ] + redundant_power_supply = true + storage_groups = { + # RAID 5 + array_type_id = 3 + # Use three disks + hard_drives = [ 0, 1, 2] + array_size = 1600 + # Basic partition template for windows + partition_template_id = 17 + } +} +``` + +_Please Note_: Monthly bare metal servers does not support `immediate cancellation`. If the monthly bare metal server is deleted by terraform, `anniversary date cancellation` option will be used. + +## Create a bare metal server using quote ID +If users already have a quote id for the bare metal server, they can create a new bare metal server with the quote id. The following example describes a basic configuration for a bare metal server with + quote_id. + +### Example of a quote based ordering +```hcl +# Create a new bare metal +resource "softlayer_bare_metal" "quote_test" { + hostname = "quote-bm-test" + domain = "example.com" + quote_id = 2209349 +} +``` + +Users can use additional options when they create a new bare metal server with `quote_id`. The following example defines target VLANs, subnets, + user metadata, and tags additionally. + +### Example of a quote based ordering with additional options +```hcl +# Create a new bare metal +resource "softlayer_bare_metal" "quote_test" { + +# Mandatory attributes + hostname = "quote-bm-test" + domain = "example.com" + quote_id = 2209349 + +# Optional attributes + user_metadata = "{\"value\":\"newvalue\"}" + public_vlan_id = 12345678 + private_vlan_id = 87654321 + public_subnet = "50.97.46.160/28" + private_subnet = "10.56.109.128/26" + tags = [ + "collectd", + "mesos-master" + ] } ``` @@ -36,20 +164,35 @@ The following arguments are supported: * `domain` | *string* * Domain for the computing instance. * **Required** +* `user_metadata` | *string* + * Arbitrary data to be made available to the computing instance. + * *Optional* +* `notes` | *string* + * A note of up to 1000 characters about the server. + * *Optional* +* `ssh_key_ids` | *array* of numbers + * SSH key _IDs_ to install on the computing instance upon provisioning. + * *Optional* + + **Note:** Don't know the ID(s) for your SSH keys? See [here](https://github.com/softlayer/terraform-provider-softlayer/blob/master/docs/datasources/softlayer_ssh_key.md) for a way to reference your SSH keys by their labels. + +* `post_install_script_uri` | *string* + * As defined in the [SoftLayer_Virtual_Guest_SupplementalCreateObjectOptions](https://sldn.softlayer.com/reference/datatypes/SoftLayer_Virtual_Guest_SupplementalCreateObjectOptions). + * *Optional* +* `tags` | *array* of strings + * Set tags on this bare metal server. The characters permitted are A-Z, 0-9, whitespace, _ (underscore), - (hyphen), . (period), and : (colon). All other characters will be stripped away. + * *Optional* + +**Monthly/Hourly bare metal server attributes** + * `datacenter` | *string* * Specifies which datacenter the instance is to be provisioned in. - * **Required** -* `fixed_config_preset` | *string* - * The configuration preset that the bare metal server will be provisioned with. This governs the type of cpu, number of cores, amount of ram, and hard drives which the bare metal server will have. [Take a look at the available presets](https://api.softlayer.com/rest/v3/SoftLayer_Hardware/getCreateObjectOptions.json) (use your api key as the password), and find the key called _fixedConfigurationPresets_. Under that, the presets will be identified by the *keyName*s. - * **Required** + * It is a mandatory attribute for monthly and hourly bare metal servers. + * *Optional* * `hourly_billing` | *boolean* * Specifies the billing type for the instance. When true the computing instance will be billed on hourly usage, otherwise it will be billed on a monthly basis. * *Default*: true * *Optional* -* `os_reference_code` | *string* - * An operating system reference code that will be used to provision the computing instance. [Get a complete list of the os reference codes available](https://api.softlayer.com/rest/v3/SoftLayer_Virtual_Guest_Block_Device_Template_Group/getVhdImportSoftwareDescriptions.json?objectMask=referenceCode) (use your api key as the password). - * *Optional* - * **Conflicts with** `image_template_id`. * `image_template_id` | *int* * The image template id to be used to provision the computing instance. Note this is not the global identifier (uuid), but the image template group id that should point to a valid global identifier. You can get the image template id by navigating on the portal to _Devices > Manage > Images_, clicking on the desired image, and taking note of the id number in the browser URL location. * *Optional* @@ -65,6 +208,21 @@ The following arguments are supported: * Specifies whether or not the instance only has access to the private network. When true this flag specifies that a compute instance is to only have access to the private network. * *Default*: False * *Optional* + +**Hourly bare metal server only attributes** + +* `fixed_config_preset` | *string* + * The configuration preset that the hourly bare metal server will be provisioned with. This governs the type of cpu, number of cores, amount of ram, and hard drives which the bare metal server will have. [Take a look at the available presets](https://api.softlayer.com/rest/v3/SoftLayer_Hardware/getCreateObjectOptions.json) (use your api key as the password), and find the key called _fixedConfigurationPresets_. Under that, the presets will be identified by the *keyName*s. + * It is a mandatory attribute for hourly bare metal server provisioning. + * *Optional* +* `os_reference_code` | *string* + * An operating system reference code that will be used to provision the computing instance. + * Hourly bare metal server : [Get a complete list of the os reference codes available for hourly bare metal servers](https://api.softlayer.com/rest/v3/SoftLayer_Virtual_Guest_Block_Device_Template_Group/getVhdImportSoftwareDescriptions.json?objectMask=referenceCode) (use your api key as the password). + * *Optional* + * **Conflicts with** `image_template_id`. + +**Monthly / Quote based bare metal server provisioning attributes** + * `public_vlan_id` | *int* * Public VLAN which is to be used for the public network interface of the instance. Accepted values can be found [here](https://control.softlayer.com/network/vlans). Click on the desired VLAN and note the id number in the URL. * *Optional* @@ -77,23 +235,70 @@ The following arguments are supported: * `private_subnet` | *string* * Private subnet which is to be used for the private network interface of the instance. Accepted values are primary private networks and can be found [here](https://control.softlayer.com/network/subnets). * *Optional* -* `user_metadata` | *string* - * Arbitrary data to be made available to the computing instance. + +**Monthly bare metal server only attributes** + +* `package_key_name` | *string* + * Monthly bare metal server's package key name. This attribute is only used when a new monthly bare metal server is created. + * You can find available key names in the [link](https://api.softlayer.com/rest/v3/SoftLayer_Product_Package/getAllObjects?objectFilter={"type":{"keyName":{"operation":"BARE_METAL_CPU"}}}). You need your username and api_key to access to the page. * *Optional* -* `notes` | *string* - * A note of up to 1000 characters about the server. +* `process_key_name` | *string* + * Monthly bare metal server's process key name. This attribute is only used when a new monthly bare metal server is created. + * Note the package key ID from the [link](https://api.softlayer.com/rest/v3/SoftLayer_Product_Package/getAllObjects?objectFilter={"type":{"keyName":{"operation":"BARE_METAL_CPU"}}}) and replace **PACKAGE_ID** in the [link](https://api.softlayer.com/rest/v3/SoftLayer_Product_Package/PACKAGE_ID/getItems?objectMask=mask[prices[id,categories[id,name,categoryCode],capacityRestrictionType,capacityRestrictionMinimum,capacityRestrictionMaximum,locationGroupId]]) to your package key ID. Select a process key name from available process key names. * *Optional* -* `ssh_key_ids` | *array* of numbers - * SSH key _IDs_ to install on the computing instance upon provisioning. +* `disk_key_names` | *list* + * Array of internal disk key names. This attribute is only used when a new monthly bare metal server is created. + * Note the package key ID from the [link](https://api.softlayer.com/rest/v3/SoftLayer_Product_Package/getAllObjects?objectFilter={"type":{"keyName":{"operation":"BARE_METAL_CPU"}}}) and replace **PACKAGE_ID** in the [link](https://api.softlayer.com/rest/v3/SoftLayer_Product_Package/PACKAGE_ID/getItems?objectMask=mask[prices[id,categories[id,name,categoryCode],capacityRestrictionType,capacityRestrictionMinimum,capacityRestrictionMaximum,locationGroupId]]) to your package key ID. Select a disk key name from available disk key names. * *Optional* - - **Note:** Don't know the ID(s) for your SSH keys? See [here](https://github.com/softlayer/terraform-provider-softlayer/blob/master/docs/datasources/softlayer_ssh_key.md) for a way to reference your SSH keys by their labels. - -* `post_install_script_uri` | *string* - * As defined in the [SoftLayer_Virtual_Guest_SupplementalCreateObjectOptions](https://sldn.softlayer.com/reference/datatypes/SoftLayer_Virtual_Guest_SupplementalCreateObjectOptions). +* `os_key_name` | *string* + * An operating system key name that will be used to provision the computing instance. + * Note the package key ID from the [link](https://api.softlayer.com/rest/v3/SoftLayer_Product_Package/getAllObjects?objectFilter={"type":{"keyName":{"operation":"BARE_METAL_CPU"}}}) and replace **PACKAGE_ID** in the [link](https://api.softlayer.com/rest/v3/SoftLayer_Product_Package/PACKAGE_ID/getItems?objectMask=mask[prices[id,categories[id,name,categoryCode],capacityRestrictionType,capacityRestrictionMinimum,capacityRestrictionMaximum,locationGroupId]]) to your package key ID. Select a OS key name from available OS key names. * *Optional* -* `tags` | *array* of strings - * Set tags on this bare metal server. The characters permitted are A-Z, 0-9, whitespace, _ (underscore), - (hyphen), . (period), and : (colon). All other characters will be stripped away. +* `redundant_network` | *boolean* + * If `redundant_network` is `true`, two physical network interfaces will be provided with a bonding configuration. + * *Default*: False + * *Optional* +* `unbonded_network` | *boolean* + * If `unbonded_network` is `true`, two physical network interfaces will be provided. + * *Default*: False + * *Optional* +* `public_bandwidth` | *int* + * Allowed public network traffic(GB) per month. + * `public_bandwidth` can be greater than 0 when `private_network_only` is `false` and the server is a monthly based server. + * *Optional* +* `memory` | *int* + * An amount of memory(GB) for the server. + * *Optional* +* `storage_groups` | *array of storage group objects* + * RAID and partition configuration. Refer to the [link](https://sldn.softlayer.com/blog/hansKristian/Ordering-RAID-through-API) to configure `storage_groups`. + * *Optional* + * Each storage group object has the following sub-attributes: + * `array_type_id` | *int* + * It provides RAID type. You can find `array_type_id` from the [link](https://api.softlayer.com/rest/v3/SoftLayer_Configuration_Storage_Group_Array_Type/getAllObjects). + * *Required* + * `hard_drives` | *array of int* + * Index of hard drives for RAID configuration. The index starts from 0. For example, if you want to use first two hard drives, you will use the following expression: [0,1] + * *Required* + * `array_size` | *int* + * Target RAID disk size in GB unit. + * *Optional* + * `partition_template_id` | *int* + * Partition template id for OS disk. The templates are different based on the target OS. Check your OS with the [link](https://api.softlayer.com/rest/v3/SoftLayer_Hardware_Component_Partition_OperatingSystem/getAllObjects ). Note the id of the OS and + check available partition templates using the URL : https://api.softlayer.com/rest/v3/SoftLayer_Hardware_Component_Partition_OperatingSystem/OS_ID/getPartitionTemplates . Replace `OS_ID` to your OS ID from the URL and find your template id. + * *Optional* + +* `redundant_power_supply` | *boolean* + * If `redundant_power_supply` is true, an additional power supply will be provided. + * *Optional* +* `tcp_monitoring` | *boolean* + * If `tcp_monitoring` is `false`, ping monitoring service will be provided. If `tcp_monitoring` is `true`, ping and tcp monitoring service will be provided. + * *Optional* +**Quote based provisioning only attributes** + +* `quote_id` | *int* + * Create a bare metal server using the quote. + * If quote_id is defined, the terraform uses specifications in the quote to create a bare metal server. + * You can find the quote id by navigating on the portal to _Account > Sales > Quotes_. * *Optional* ## Attributes Reference @@ -102,4 +307,4 @@ The following attributes are exported: * `id` - id of the bare metal. * `public_ipv4_address` - Public IPv4 address of the bare metal server. -* `private_ipv4_address` - Private IPv4 address of the bare metal server. +* `private_ipv4_address` - Private IPv4 address of the bare metal server. \ No newline at end of file diff --git a/softlayer/resource_softlayer_bare_metal.go b/softlayer/resource_softlayer_bare_metal.go index 34c1f2a71..7db80fd5b 100644 --- a/softlayer/resource_softlayer_bare_metal.go +++ b/softlayer/resource_softlayer_bare_metal.go @@ -11,7 +11,10 @@ import ( "github.com/hashicorp/terraform/helper/schema" "github.com/softlayer/softlayer-go/datatypes" "github.com/softlayer/softlayer-go/filter" + "github.com/softlayer/softlayer-go/helpers/location" + "github.com/softlayer/softlayer-go/helpers/product" "github.com/softlayer/softlayer-go/services" + "github.com/softlayer/softlayer-go/session" "github.com/softlayer/softlayer-go/sl" ) @@ -55,11 +58,76 @@ func resourceSoftLayerBareMetal() *schema.Resource { ForceNew: true, }, + "ssh_key_ids": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeInt}, + ForceNew: true, + }, + + "user_metadata": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "notes": { + Type: schema.TypeString, + Optional: true, + }, + + "post_install_script_uri": { + Type: schema.TypeString, + Optional: true, + Default: nil, + ForceNew: true, + DiffSuppressFunc: applyOnce, + }, + + "tags": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + + // Hourly only + "fixed_config_preset": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + DiffSuppressFunc: applyOnce, + }, + + // Hourly only "os_reference_code": { - Type: schema.TypeString, + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"image_template_id"}, + DiffSuppressFunc: applyOnce, + }, + + "image_template_id": { + Type: schema.TypeInt, Optional: true, ForceNew: true, - ConflictsWith: []string{"image_template_id"}, + ConflictsWith: []string{"os_reference_code"}, + }, + + "datacenter": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "network_speed": { + Type: schema.TypeInt, + Optional: true, + Default: 100, + ForceNew: true, }, "hourly_billing": { @@ -76,100 +144,164 @@ func resourceSoftLayerBareMetal() *schema.Resource { ForceNew: true, }, - "datacenter": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + "tcp_monitoring": { + Type: schema.TypeBool, + Optional: true, + Default: false, + ForceNew: true, + DiffSuppressFunc: applyOnce, }, - "public_vlan_id": { - Type: schema.TypeInt, - Optional: true, - ForceNew: true, - Computed: true, + // Monthly only + "package_key_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + DiffSuppressFunc: applyOnce, }, - "public_subnet": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Computed: true, + // Monthly only + "process_key_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + DiffSuppressFunc: applyOnce, }, - "private_vlan_id": { - Type: schema.TypeInt, + // Monthly only + "os_key_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + DiffSuppressFunc: applyOnce, + }, + + // Monthly only + "disk_key_names": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + DiffSuppressFunc: applyOnce, + }, + + // Monthly only + "redundant_network": { + Type: schema.TypeBool, Optional: true, + Default: false, ForceNew: true, - Computed: true, }, - "private_subnet": { - Type: schema.TypeString, + // Monthly only + "unbonded_network": { + Type: schema.TypeBool, Optional: true, + Default: false, ForceNew: true, - Computed: true, }, - "network_speed": { + // Monthly only + "public_bandwidth": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + DiffSuppressFunc: applyOnce, + }, + + // Monthly only + "memory": { Type: schema.TypeInt, Optional: true, - Default: 100, ForceNew: true, - }, - - "public_ipv4_address": { - Type: schema.TypeString, Computed: true, }, - "private_ipv4_address": { - Type: schema.TypeString, + // Monthly only + "redundant_power_supply": { + Type: schema.TypeBool, + Optional: true, Computed: true, }, - "ssh_key_ids": { + // Monthly only + "storage_groups": { Type: schema.TypeList, Optional: true, - Elem: &schema.Schema{Type: schema.TypeInt}, ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "array_type_id": { + Type: schema.TypeInt, + Required: true, + }, + "hard_drives": { + Type: schema.TypeList, + Elem: &schema.Schema{Type: schema.TypeInt}, + Required: true, + }, + "array_size": { + Type: schema.TypeInt, + Optional: true, + }, + "partition_template_id": { + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + DiffSuppressFunc: applyOnce, }, - "user_metadata": { - Type: schema.TypeString, + // Quote based provisioning only + "quote_id": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + DiffSuppressFunc: applyOnce, + }, + + // Quote based provisioning, Monthly + "public_vlan_id": { + Type: schema.TypeInt, Optional: true, ForceNew: true, + Computed: true, }, - "notes": { + // Quote based provisioning, Monthly + "public_subnet": { Type: schema.TypeString, Optional: true, + ForceNew: true, + Computed: true, }, - "post_install_script_uri": { - Type: schema.TypeString, + // Quote based provisioning, Monthly + "private_vlan_id": { + Type: schema.TypeInt, Optional: true, - Default: nil, ForceNew: true, + Computed: true, }, - "fixed_config_preset": { + // Quote based provisioning, Monthly + "private_subnet": { Type: schema.TypeString, - Required: true, + Optional: true, ForceNew: true, + Computed: true, }, - "image_template_id": { - Type: schema.TypeInt, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"os_reference_code"}, + "public_ipv4_address": { + Type: schema.TypeString, + Computed: true, }, - "tags": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, + "private_ipv4_address": { + Type: schema.TypeString, + Computed: true, }, }, } @@ -193,7 +325,6 @@ func getBareMetalOrderFromResourceData(d *schema.ResourceData, meta interface{}) NetworkComponents: []datatypes.Network_Component{networkComponent}, PostInstallScriptUri: sl.String(d.Get("post_install_script_uri").(string)), BareMetalInstanceFlag: sl.Int(1), - FixedConfigurationPreset: &datatypes.Product_Package_Preset{ KeyName: sl.String(d.Get("fixed_config_preset").(string)), }, @@ -259,31 +390,58 @@ func getBareMetalOrderFromResourceData(d *schema.ResourceData, meta interface{}) func resourceSoftLayerBareMetalCreate(d *schema.ResourceData, meta interface{}) error { sess := meta.(ProviderConfig).SoftLayerSession() - hwService := services.GetHardwareService(sess) - orderService := services.GetProductOrderService(sess) + var order datatypes.Container_Product_Order + var err error + quote_id := d.Get("quote_id").(int) + hardware := datatypes.Hardware{ + Hostname: sl.String(d.Get("hostname").(string)), + Domain: sl.String(d.Get("domain").(string)), + } - hardware, err := getBareMetalOrderFromResourceData(d, meta) - if err != nil { - return err + if quote_id > 0 { + // Build a bare metal template from the quote. + order, err = services.GetBillingOrderQuoteService(sess). + Id(quote_id).GetRecalculatedOrderContainer(nil, sl.Bool(false)) + if err != nil { + return fmt.Errorf( + "Encountered problem trying to get the bare metal order template from quote: %s", err) + } + order.Quantity = sl.Int(1) + order.Hardware = make([]datatypes.Hardware, 0, 1) + order.Hardware = append( + order.Hardware, + hardware, + ) + } else if _, ok := d.GetOk("fixed_config_preset"); ok { + // Build an hourly bare metal server template using fixed_config_preset. + hardware, err = getBareMetalOrderFromResourceData(d, meta) + if err != nil { + return err + } + order, err = services.GetHardwareService(sess).GenerateOrderTemplate(&hardware) + if err != nil { + return fmt.Errorf( + "Encountered problem trying to get the bare metal order template: %s", err) + } + } else { + // Build a monthly bare metal server template + order, err = getMonthlyBareMetalOrder(d, meta) + if err != nil { + return fmt.Errorf( + "Encountered problem trying to get the custom bare metal order template: %s", err) + } } - order, err := hwService.GenerateOrderTemplate(&hardware) + order, err = setCommonBareMetalOrderOptions(d, meta, order) if err != nil { return fmt.Errorf( - "Encountered problem trying to get the bare metal order template: %s", err) - } - - // Set image template id if it exists - if rawImageTemplateId, ok := d.GetOk("image_template_id"); ok { - imageTemplateId := rawImageTemplateId.(int) - order.ImageTemplateId = sl.Int(imageTemplateId) + "Encountered problem trying to configure bare metal server options: %s", err) } log.Println("[INFO] Ordering bare metal server") - - _, err = orderService.PlaceOrder(&order, sl.Bool(false)) + _, err = services.GetProductOrderService(sess).PlaceOrder(&order, sl.Bool(false)) if err != nil { - return fmt.Errorf("Error ordering bare metal server: %s", err) + return fmt.Errorf("Error ordering bare metal server: %s\n%+v\n", err, order) } log.Printf("[INFO] Bare Metal Server ID: %s", d.Id()) @@ -330,7 +488,9 @@ func resourceSoftLayerBareMetalRead(d *schema.ResourceData, meta interface{}) er "hourlyBillingFlag," + "datacenter[id,name,longName]," + "primaryNetworkComponent[networkVlan[id,primaryRouter,vlanNumber],maxSpeed]," + - "primaryBackendNetworkComponent[networkVlan[id,primaryRouter,vlanNumber],maxSpeed]", + "primaryBackendNetworkComponent[networkVlan[id,primaryRouter,vlanNumber],maxSpeed,redundancyEnabledFlag]," + + "memoryCapacity,powerSupplyCount," + + "operatingSystem[softwareLicense[softwareDescription[referenceCode]]]", ).GetObject() if err != nil { @@ -367,6 +527,40 @@ func resourceSoftLayerBareMetalRead(d *schema.ResourceData, meta interface{}) er } d.Set("notes", sl.Get(result.Notes, nil)) + d.Set("memory", *result.MemoryCapacity) + d.Set("redundant_power_supply", false) + + if *result.PowerSupplyCount == 2 { + d.Set("redundant_power_supply", true) + } + + d.Set("redundant_network", false) + d.Set("unbonded_network", false) + + backendNetworkComponent, err := service.Filter( + filter.Build( + filter.Path("backendNetworkComponents.status").Eq("ACTIVE"), + ), + ).Id(id).GetBackendNetworkComponents() + + if err != nil { + return fmt.Errorf("Error retrieving bare metal server network: %s", err) + } + + if len(backendNetworkComponent) > 2 && result.PrimaryBackendNetworkComponent != nil { + if *result.PrimaryBackendNetworkComponent.RedundancyEnabledFlag { + d.Set("redundant_network", true) + } else { + d.Set("unbonded_network", true) + } + } + + if result.OperatingSystem != nil && + result.OperatingSystem.SoftwareLicense != nil && + result.OperatingSystem.SoftwareLicense.SoftwareDescription != nil && + result.OperatingSystem.SoftwareLicense.SoftwareDescription.ReferenceCode != nil { + d.Set("os_reference_code", *result.OperatingSystem.SoftwareLicense.SoftwareDescription.ReferenceCode) + } tagReferences := result.TagReferences tagReferencesLen := len(tagReferences) @@ -428,9 +622,10 @@ func resourceSoftLayerBareMetalDelete(d *schema.ResourceData, meta interface{}) return fmt.Errorf("Error getting billing item for bare metal server: %s", err) } + // Monthly bare metal servers only support an anniversary date cancellation option. billingItemService := services.GetBillingItemService(sess) _, err = billingItemService.Id(*billingItem.Id).CancelItem( - sl.Bool(true), sl.Bool(true), sl.String("No longer required"), sl.String("Please cancel this server"), + sl.Bool(d.Get("hourly_billing").(bool)), sl.Bool(true), sl.String("No longer required"), sl.String("Please cancel this server"), ) if err != nil { return fmt.Errorf("Error canceling the bare metal server (%d): %s", id, err) @@ -487,9 +682,10 @@ func waitForBareMetalProvision(d *datatypes.Hardware, meta interface{}) (interfa return bms[0], "provisioned", nil } }, - Timeout: 4 * time.Hour, - Delay: 30 * time.Second, - MinTimeout: 2 * time.Minute, + Timeout: 24 * time.Hour, + Delay: 10 * time.Second, + MinTimeout: 1 * time.Minute, + NotFoundChecks: 24 * 60, } return stateConf.WaitForState() @@ -514,9 +710,10 @@ func waitForNoBareMetalActiveTransactions(id int, meta interface{}) (interface{} return bm, "active", nil } }, - Timeout: 4 * time.Hour, - Delay: 5 * time.Second, - MinTimeout: 1 * time.Minute, + Timeout: 24 * time.Hour, + Delay: 10 * time.Second, + MinTimeout: 1 * time.Minute, + NotFoundChecks: 24 * 60, } return stateConf.WaitForState() @@ -551,3 +748,405 @@ func setHardwareNotes(id int, d *schema.ResourceData, meta interface{}) error { return nil } + +// Returns a price from an item list. +// Example usage : getItemPriceId(items, 'server', 'INTEL_XEON_2690_2_60') +func getItemPriceId(items []datatypes.Product_Item, categoryCode string, keyName string) (datatypes.Product_Item_Price, error) { + availableItems := "" + for _, item := range items { + for _, itemCategory := range item.Categories { + if *itemCategory.CategoryCode == categoryCode { + availableItems = availableItems + *item.KeyName + " ( " + *item.Description + " ) , " + if *item.KeyName == keyName { + for _, price := range item.Prices { + if price.LocationGroupId == nil { + return datatypes.Product_Item_Price{Id: price.Id}, nil + } + } + } + } + } + } + return datatypes.Product_Item_Price{}, + fmt.Errorf("Could not find the matching item with categorycode %s and keyName %s. Available item(s) is(are) %s", categoryCode, keyName, availableItems) +} + +func getMonthlyBareMetalOrder(d *schema.ResourceData, meta interface{}) (datatypes.Container_Product_Order, error) { + sess := meta.(ProviderConfig).SoftLayerSession() + // Validate attributes for monthly bare metal server ordering. + if d.Get("hourly_billing").(bool) { + return datatypes.Container_Product_Order{}, fmt.Errorf("Monthly bare metal server only supports monthly billing.") + } + + model, ok := d.GetOk("package_key_name") + if !ok { + return datatypes.Container_Product_Order{}, fmt.Errorf("The attribute 'package_key_name' is not defined.") + } + + datacenter, ok := d.GetOk("datacenter") + if !ok { + return datatypes.Container_Product_Order{}, fmt.Errorf("The attribute 'datacenter' is not defined.") + } + + osKeyName, ok := d.GetOk("os_key_name") + if !ok { + return datatypes.Container_Product_Order{}, fmt.Errorf("The attribute 'os_key_name' is not defined.") + } + + dc, err := location.GetDatacenterByName(sess, datacenter.(string), "id") + if err != nil { + return datatypes.Container_Product_Order{}, err + } + + // 1. Find a package id using monthly bare metal package key name. + pkg, err := getPackageByModel(sess, model.(string)) + if err != nil { + return datatypes.Container_Product_Order{}, err + } + + if pkg.Id == nil { + return datatypes.Container_Product_Order{}, err + } + + // 2. Get all prices for the package + items, err := product.GetPackageProducts(sess, *pkg.Id, "id,categories,capacity,description,units,keyName,prices[id,categories[id,name,categoryCode]]") + if err != nil { + return datatypes.Container_Product_Order{}, err + } + + // 3. Build price items + server, err := getItemPriceId(items, "server", d.Get("process_key_name").(string)) + if err != nil { + return datatypes.Container_Product_Order{}, err + } + os, err := getItemPriceId(items, "os", osKeyName.(string)) + if err != nil { + return datatypes.Container_Product_Order{}, err + } + + ram, err := findMemoryItemPriceId(items, d) + if err != nil { + return datatypes.Container_Product_Order{}, err + } + + portSpeed, err := findNetworkItemPriceId(items, d) + if err != nil { + return datatypes.Container_Product_Order{}, err + } + + monitoring, err := getItemPriceId(items, "monitoring", "MONITORING_HOST_PING") + if err != nil { + return datatypes.Container_Product_Order{}, err + } + if d.Get("tcp_monitoring").(bool) { + monitoring, err = getItemPriceId(items, "monitoring", "MONITORING_HOST_PING_AND_TCP_SERVICE") + if err != nil { + return datatypes.Container_Product_Order{}, err + } + } + + // Other common default options + priIpAddress, err := getItemPriceId(items, "pri_ip_addresses", "1_IP_ADDRESS") + if err != nil { + return datatypes.Container_Product_Order{}, err + } + remoteManagement, err := getItemPriceId(items, "remote_management", "REBOOT_KVM_OVER_IP") + if err != nil { + return datatypes.Container_Product_Order{}, err + } + vpnManagement, err := getItemPriceId(items, "vpn_management", "UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT") + if err != nil { + return datatypes.Container_Product_Order{}, err + } + + notification, err := getItemPriceId(items, "notification", "NOTIFICATION_EMAIL_AND_TICKET") + if err != nil { + return datatypes.Container_Product_Order{}, err + } + response, err := getItemPriceId(items, "response", "AUTOMATED_NOTIFICATION") + if err != nil { + return datatypes.Container_Product_Order{}, err + } + vulnerabilityScanner, err := getItemPriceId(items, "vulnerability_scanner", "NESSUS_VULNERABILITY_ASSESSMENT_REPORTING") + if err != nil { + return datatypes.Container_Product_Order{}, err + } + + // Define an order object using basic paramters. + order := datatypes.Container_Product_Order{ + Quantity: sl.Int(1), + Hardware: []datatypes.Hardware{{ + Hostname: sl.String(d.Get("hostname").(string)), + Domain: sl.String(d.Get("domain").(string)), + }, + }, + Location: sl.String(strconv.Itoa(*dc.Id)), + PackageId: pkg.Id, + Prices: []datatypes.Product_Item_Price{ + server, + os, + ram, + portSpeed, + priIpAddress, + remoteManagement, + vpnManagement, + monitoring, + notification, + response, + vulnerabilityScanner, + }, + } + + // Add optional price ids. + // Add public bandwidth + if publicBandwidth, ok := d.GetOk("public_bandwidth"); ok { + publicBandwidthStr := "BANDWIDTH_" + strconv.Itoa(publicBandwidth.(int)) + "_GB" + bandwidth, err := getItemPriceId(items, "bandwidth", publicBandwidthStr) + if err != nil { + return datatypes.Container_Product_Order{}, err + } + order.Prices = append(order.Prices, bandwidth) + } + + // Add prices of disks. + disks := d.Get("disk_key_names").([]interface{}) + diskLen := len(disks) + if diskLen > 0 { + for i, disk := range disks { + diskPrice, err := getItemPriceId(items, "disk"+strconv.Itoa(i), disk.(string)) + if err != nil { + return datatypes.Container_Product_Order{}, err + } + order.Prices = append(order.Prices, diskPrice) + } + } + + // Add redundant power supply + if d.Get("redundant_power_supply").(bool) { + powerSupply, err := getItemPriceId(items, "power_supply", "REDUNDANT_POWER_SUPPLY") + if err != nil { + return datatypes.Container_Product_Order{}, err + } + order.Prices = append(order.Prices, powerSupply) + } + + // Add storage_groups for RAID configuration + diskController, err := getItemPriceId(items, "disk_controller", "DISK_CONTROLLER_NONRAID") + if err != nil { + return datatypes.Container_Product_Order{}, err + } + + if _, ok := d.GetOk("storage_groups"); ok { + order.StorageGroups = getStorageGroupsFromResourceData(d) + diskController, err = getItemPriceId(items, "disk_controller", "DISK_CONTROLLER_RAID") + if err != nil { + return datatypes.Container_Product_Order{}, err + } + } + order.Prices = append(order.Prices, diskController) + + return order, nil +} + +// Set common parameters for server ordering. +func setCommonBareMetalOrderOptions(d *schema.ResourceData, meta interface{}, order datatypes.Container_Product_Order) (datatypes.Container_Product_Order, error) { + public_vlan_id := d.Get("public_vlan_id").(int) + + if public_vlan_id > 0 { + order.Hardware[0].PrimaryNetworkComponent = &datatypes.Network_Component{ + NetworkVlan: &datatypes.Network_Vlan{Id: sl.Int(public_vlan_id)}, + } + } + + private_vlan_id := d.Get("private_vlan_id").(int) + if private_vlan_id > 0 { + order.Hardware[0].PrimaryBackendNetworkComponent = &datatypes.Network_Component{ + NetworkVlan: &datatypes.Network_Vlan{Id: sl.Int(private_vlan_id)}, + } + } + + if public_subnet, ok := d.GetOk("public_subnet"); ok { + subnet := public_subnet.(string) + subnetId, err := getSubnetId(subnet, meta) + if err != nil { + return datatypes.Container_Product_Order{}, fmt.Errorf("Error determining id for subnet %s: %s", subnet, err) + } + + order.Hardware[0].PrimaryNetworkComponent.NetworkVlan.PrimarySubnetId = sl.Int(subnetId) + } + + if private_subnet, ok := d.GetOk("private_subnet"); ok { + subnet := private_subnet.(string) + subnetId, err := getSubnetId(subnet, meta) + if err != nil { + return datatypes.Container_Product_Order{}, fmt.Errorf("Error determining id for subnet %s: %s", subnet, err) + } + + order.Hardware[0].PrimaryBackendNetworkComponent.NetworkVlan.PrimarySubnetId = sl.Int(subnetId) + } + + if userMetadata, ok := d.GetOk("user_metadata"); ok { + order.Hardware[0].UserData = []datatypes.Hardware_Attribute{ + {Value: sl.String(userMetadata.(string))}, + } + } + + // Get configured ssh_keys + ssh_key_ids := d.Get("ssh_key_ids").([]interface{}) + if len(ssh_key_ids) > 0 { + order.Hardware[0].SshKeys = make([]datatypes.Security_Ssh_Key, 0, len(ssh_key_ids)) + for _, ssh_key_id := range ssh_key_ids { + order.Hardware[0].SshKeys = append(order.Hardware[0].SshKeys, datatypes.Security_Ssh_Key{ + Id: sl.Int(ssh_key_id.(int)), + }) + } + } + + // Set image template id if it exists + if rawImageTemplateId, ok := d.GetOk("image_template_id"); ok { + imageTemplateId := rawImageTemplateId.(int) + order.ImageTemplateId = sl.Int(imageTemplateId) + } + + return order, nil +} + +// Find price item using network options +func findNetworkItemPriceId(items []datatypes.Product_Item, d *schema.ResourceData) (datatypes.Product_Item_Price, error) { + networkSpeed := d.Get("network_speed").(int) + redundantNetwork := d.Get("redundant_network").(bool) + unbondedNetwork := d.Get("unbonded_network").(bool) + privateNetworkOnly := d.Get("private_network_only").(bool) + + networkSpeedStr := "_MBPS_" + redundantNetworkStr := "" + unbondedNetworkStr := "" + + if networkSpeed < 1000 { + networkSpeedStr = strconv.Itoa(networkSpeed) + networkSpeedStr + } else { + networkSpeedStr = strconv.Itoa(networkSpeed/1000) + "_GBPS" + } + if redundantNetwork { + redundantNetworkStr = "_REDUNDANT" + } + + if unbondedNetwork { + unbondedNetworkStr = "_UNBONDED" + } + + for _, item := range items { + for _, itemCategory := range item.Categories { + if *itemCategory.CategoryCode == "port_speed" && + strings.HasPrefix(*item.KeyName, networkSpeedStr) && + strings.Contains(*item.KeyName, redundantNetworkStr) && + strings.Contains(*item.KeyName, unbondedNetworkStr) { + if (privateNetworkOnly && strings.Contains(*item.KeyName, "_PUBLIC_PRIVATE")) || + (!privateNetworkOnly && !strings.Contains(*item.KeyName, "_PUBLIC_PRIVATE")) || + (!unbondedNetwork && strings.Contains(*item.KeyName, "_UNBONDED")) || + !redundantNetwork && strings.Contains(*item.KeyName, "_REDUNDANT") { + break + } + for _, price := range item.Prices { + if price.LocationGroupId == nil { + return datatypes.Product_Item_Price{Id: price.Id}, nil + } + } + } + } + } + return datatypes.Product_Item_Price{}, + fmt.Errorf("Could not find the network with %s, %s, %s, and private_network_only = %t", + networkSpeedStr, redundantNetworkStr, unbondedNetworkStr, privateNetworkOnly) +} + +// Find memory price item using memory size. +func findMemoryItemPriceId(items []datatypes.Product_Item, d *schema.ResourceData) (datatypes.Product_Item_Price, error) { + memory := d.Get("memory").(int) + memoryStr := "RAM_" + strconv.Itoa(memory) + "_GB" + availableMemories := "" + + for _, item := range items { + for _, itemCategory := range item.Categories { + if *itemCategory.CategoryCode == "ram" { + availableMemories = availableMemories + *item.KeyName + "(" + *item.Description + ")" + ", " + if strings.HasPrefix(*item.KeyName, memoryStr) { + for _, price := range item.Prices { + if price.LocationGroupId == nil { + return datatypes.Product_Item_Price{Id: price.Id}, nil + } + } + } + } + } + } + + return datatypes.Product_Item_Price{}, + fmt.Errorf("Could not find the price item for %d GB memory. Available items are %s", memory, availableMemories) +} + +// Find a bare metal package object using a package key name +func getPackageByModel(sess *session.Session, model string) (datatypes.Product_Package, error) { + objectMask := "id,keyName,name,description,isActive,type[keyName]" + service := services.GetProductPackageService(sess) + availableModels := "" + + // Get package id + packages, err := service.Mask(objectMask). + Filter( + filter.Build( + filter.Path("type.keyName").Eq("BARE_METAL_CPU"), + ), + ).GetAllObjects() + if err != nil { + return datatypes.Product_Package{}, err + } + + for _, pkg := range packages { + availableModels = availableModels + *pkg.KeyName + if pkg.Description != nil { + availableModels = availableModels + " ( " + *pkg.Description + " ), " + } else { + availableModels = availableModels + ", " + } + if *pkg.KeyName == model { + return pkg, nil + } + } + + return datatypes.Product_Package{}, fmt.Errorf("No custom bare metal package key name for %s. Available package key name(s) is(are) %s", model, availableModels) +} + +func getStorageGroupsFromResourceData(d *schema.ResourceData) []datatypes.Container_Product_Order_Storage_Group { + storageGroupLists := d.Get("storage_groups").([]interface{}) + storageGroups := make([]datatypes.Container_Product_Order_Storage_Group, 0) + + for _, storageGroupList := range storageGroupLists { + storageGroup := storageGroupList.(map[string]interface{}) + var storageGroupObj datatypes.Container_Product_Order_Storage_Group + storageGroupObj.ArrayTypeId = sl.Int(storageGroup["array_type_id"].(int)) + hardDrives := storageGroup["hard_drives"].([]interface{}) + storageGroupObj.HardDrives = make([]int, 0, len(hardDrives)) + for _, hardDrive := range hardDrives { + storageGroupObj.HardDrives = append(storageGroupObj.HardDrives, hardDrive.(int)) + } + arraySize := storageGroup["array_size"].(int) + if arraySize > 0 { + storageGroupObj.ArraySize = sl.Float(float64(arraySize)) + } + partitionTemplateId := storageGroup["partition_template_id"].(int) + if partitionTemplateId > 0 { + storageGroupObj.PartitionTemplateId = sl.Int(partitionTemplateId) + } + storageGroups = append(storageGroups, storageGroupObj) + } + return storageGroups +} + +// Use this function for attributes which only should be applied in resource creation time. +func applyOnce(k, o, n string, d *schema.ResourceData) bool { + if len(d.Id()) == 0 { + return false + } + return true +} diff --git a/softlayer/resource_softlayer_bare_metal_test.go b/softlayer/resource_softlayer_bare_metal_test.go index 46a01fbba..7ca601057 100644 --- a/softlayer/resource_softlayer_bare_metal_test.go +++ b/softlayer/resource_softlayer_bare_metal_test.go @@ -66,6 +66,60 @@ func TestAccSoftLayerBareMetal_Basic(t *testing.T) { }) } +func TestAccSoftLayerBareMetalQuote_Basic(t *testing.T) { + var bareMetal datatypes.Hardware + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckSoftLayerBareMetalDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckSoftLayerBareMetalQuoteConfig_basic, + Destroy: false, + Check: resource.ComposeTestCheckFunc( + testAccCheckSoftLayerBareMetalExists("softlayer_bare_metal.terraform-acceptance-test-2", &bareMetal), + resource.TestCheckResourceAttr( + "softlayer_bare_metal.terraform-acceptance-test-2", "hostname", "terraform-test2"), + resource.TestCheckResourceAttr( + "softlayer_bare_metal.terraform-acceptance-test-2", "domain", "bar.example.com"), + resource.TestCheckResourceAttr( + "softlayer_bare_metal.terraform-acceptance-test-2", "user_metadata", "{\"value\":\"newvalue\"}"), + CheckStringSet( + "softlayer_bare_metal.terraform-acceptance-test-2", + "tags", []string{"collectd"}, + ), + ), + }, + }, + }) +} + +func TestAccSoftLayerBareMetalCustom_Basic(t *testing.T) { + var bareMetal datatypes.Hardware + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckSoftLayerBareMetalDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckSoftLayerBareMetalCustom_basic, + Destroy: false, + Check: resource.ComposeTestCheckFunc( + testAccCheckSoftLayerBareMetalExists("softlayer_bare_metal.terraform-acceptance-test-3", &bareMetal), + resource.TestCheckResourceAttr( + "softlayer_bare_metal.terraform-acceptance-test-3", "memory", "32"), + resource.TestCheckResourceAttr( + "softlayer_bare_metal.terraform-acceptance-test-3", "network_speed", "1000"), + resource.TestCheckResourceAttr( + "softlayer_bare_metal.terraform-acceptance-test-3", "public_bandwidth", "500"), + ), + }, + }, + }) +} + func testAccCheckSoftLayerBareMetalDestroy(s *terraform.State) error { service := services.GetHardwareService(testAccProvider.Meta().(ProviderConfig).SoftLayerSession()) @@ -156,3 +210,30 @@ resource "softlayer_bare_metal" "terraform-acceptance-test-1" { tags = ["mesos-master"] } ` + +const testAccCheckSoftLayerBareMetalQuoteConfig_basic = ` +resource "softlayer_bare_metal" "terraform-acceptance-test-2" { + hostname = "terraform-test2" + domain = "bar.example.com" + user_metadata = "{\"value\":\"newvalue\"}" + quote_id = 2179879 + tags = ["collectd"] +} +` + +const testAccCheckSoftLayerBareMetalCustom_basic = ` +resource "softlayer_bare_metal" "terraform-acceptance-test-3" { + package_key_name = "2U_DUAL_E52600_12_DRIVES" + process_key_name = "INTEL_DUAL_INTEL_XEON_E52620_2_00" + memory = 32 + os_reference_code = "OS_WINDOWS_2012_R2_FULL_DC_64_BIT_2" + hostname = "cust-bm" + domain = "example.com" + datacenter = "dal05" + network_speed = 1000 + public_bandwidth = 500 + disk_key_names = [ "HARD_DRIVE_1_00_TB_SATA_2", "HARD_DRIVE_1_00_TB_SATA_2" ] + hourly_billing = false + redundant_power_supply = true +} +`