April 30, 2019


Order a Virtual Server

The example can be used to order a public, suspended, transit and dedicated virtual server

Note: See the Parameters struct to know what data you can set at order time.

package main

import (
	"encoding/json"
	"fmt"
	"os"

	"github.com/softlayer/softlayer-go/datatypes"
	"github.com/softlayer/softlayer-go/filter"
	"github.com/softlayer/softlayer-go/services"
	"github.com/softlayer/softlayer-go/session"
	"github.com/softlayer/softlayer-go/sl"
)

// Create the session and services
var sess = session.New()
var packageService = services.GetProductPackageService(sess)
var orderService = services.GetProductOrderService(sess)

// Parameters will help us to reduce the number of argumments in a function
// and not all of them are required. e.g. hostID, reservedCapacityID,sshKeys, etc.
type Parameters struct {
	hostname           string
	domain             string
	pkgName            string
	flavor             string
	hourly             bool
	hostID             int // To order in an specific dedicated host
	reservedCapacityID int // To order in a reerved capacity
	location           string
	items              []string
	publicVlanID       int
	privateVlanID      int
	sshKeys            []int
	postScripts        []string
}

func main() {
	// KeyNames of the items
	items := []string{
		"1_GBPS_PUBLIC_PRIVATE_NETWORK_UPLINKS",
		"BANDWIDTH_0_GB_2", "1_IP_ADDRESS",
		"OS_UBUNTU_16_04_LTS_XENIAL_XERUS_MINIMAL_64_BIT_FOR_VSI",
		"REBOOT_REMOTE_CONSOLE",
		"MONITORING_HOST_PING", "NOTIFICATION_EMAIL_AND_TICKET", "AUTOMATED_NOTIFICATION",
		"UNLIMITED_SSL_VPN_USERS_1_PPTP_VPN_USER_PER_ACCOUNT",
	}
	
	parameters := Parameters{
		hostname:      "myhostname",
		domain:        "domain.com",
		publicVlanID:  2068353,
		privateVlanID: 2068355,
		// CLOUD_SERVER, SUSPEND_CLOUD_SERVER, PUBLIC_CLOUD_SERVER or TRANSIENT_CLOUD_SERVER
		pkgName:     "SUSPEND_CLOUD_SERVER",
		flavor:      "M1_1X8X25", // Remove this if you want to order a CLOUD_SERVER
		location:    "DALLAS13",
		hourly:      true, // Suspended and transient vsis can only be hourly
		items:       items,
		sshKeys:     []int{1253845, 1312903},
		postScripts: []string{"https://xx.xx.xx.xx/folder/file.sh"},
	}

	OrderVirtualServer(parameters)
}

// OrderVirtualServer place/verify the order
func OrderVirtualServer(args Parameters) {

	// Get the packageId and prices
	packageID := GetPackageID(args.pkgName)
	prices := GetPrices(*packageID, args.items)

	// Build the Virtual_Guest array object
	virtualGuests := BuildArrayVirtualGuests(args)

	// Build the Container_Product_Order with the basic required data
	containerOrder := datatypes.Container_Product_Order{
		PackageId:        packageID,
		Location:         sl.String(args.location),
		VirtualGuests:    virtualGuests,
		Prices:           prices,
		UseHourlyPricing: sl.Bool(args.hourly),
	}
	// Add presetId to the Container_Product_Order if a flavor exists
	if args.flavor != "" {
		containerOrder.PresetId = GetPresetID(*packageID, args.flavor)
	}
	// Add sshKeys if Parameters struct has this value set
	if len(args.sshKeys) > 0 {
		containerOrder.SshKeys = []datatypes.Container_Product_Order_SshKeys{
			{SshKeyIds: args.sshKeys},
		}
	}
	// Add post provision scripts if Parameters struct has this value set
	if len(args.postScripts) > 0 {
		containerOrder.ProvisionScripts = args.postScripts
	}

	// Build the order template that will be used to order the virtual server
	orderTemplate := datatypes.Container_Product_Order_Virtual_Guest{
		Container_Product_Order_Hardware_Server: datatypes.Container_Product_Order_Hardware_Server{
			Container_Product_Order: containerOrder,
		},
	}

	// Add the hostId or reserved capacity if they were added in the parameters
	if args.hostID > 0 {
		orderTemplate.HostId = sl.Int(args.hostID)
	}
	if args.reservedCapacityID > 0 {
		orderTemplate.ReservedCapacityId = sl.Int(args.reservedCapacityID)
	}

	// Use verifyOrder() method to check for errors. Replace this with placeOrder() when
	// you are ready to order.
	receipt, err := orderService.VerifyOrder(&orderTemplate)
	if err != nil {
		fmt.Printf("Unable to verify/place the order: %s", err)
	}

	// pretty print
	jsonFormat, jsonErr := json.MarshalIndent(receipt, "", "   ")
	if jsonErr != nil {
		fmt.Println(jsonErr)
		return
	}
	fmt.Println(string(jsonFormat))
}

// GetPackageID returns the package id
func GetPackageID(keyName string) *int {
	objFilter := filter.Path("keyName").Eq(keyName).Build()
	packages, err := packageService.Mask("mask[id]").Filter(objFilter).GetAllObjects()
	if err != nil {
		fmt.Printf("Package %s doesn't exists\n", keyName)
		os.Exit(1)
	}
	return packages[0].Id
}

// GetPresetID returns the presetID of a flavor
func GetPresetID(packageID int, flavor string) *int {
	objFilter := filter.Path("keyName").Eq(flavor).Build()
	presets, err := packageService.Filter(objFilter).Id(packageID).GetActivePresets()
	if err != nil {
		fmt.Printf("Flavor %s doesn't exists\n", flavor)
		os.Exit(1)
	}
	return presets[0].Id
}

// GetPrices returns the list of prices
func GetPrices(packageID int, itemKeyNames []string) []datatypes.Product_Item_Price {
	objMask := "mask[id,itemCategory,keyName,prices[categories]]"

	items, err := packageService.Mask(objMask).Id(packageID).GetItems()
	if err != nil {
		fmt.Printf("Unable to retrieve items: %s\n", err)
		os.Exit(1)
	}

	prices := make([]datatypes.Product_Item_Price, len(itemKeyNames))

	for idx, itemKeyName := range itemKeyNames {
		prices[idx].Id, err = GetPriceIDIfMatch(itemKeyName, items)
		if err != nil {
			fmt.Println(err)
			os.Exit(1)
		}
	}

	return prices
}

// GetPriceIDIfMatch returns a priceID or error if keyName doesnt exists
func GetPriceIDIfMatch(keyName string, items []datatypes.Product_Item) (priceID *int, err error) {
	var matchingItem datatypes.Product_Item
	// find the item keyname
	for _, item := range items {
		if keyName == *item.KeyName {
			matchingItem = item
			break
		}
	}
	// returns error if keyName doesn't exists in the items list
	if matchingItem.KeyName == nil {
		return nil, fmt.Errorf("The item %s doesn't exists in the package", keyName)
	}
	// find the standard priceID
	for _, price := range matchingItem.Prices {
		if price.LocationGroupId == nil {
			priceID = price.Id
			break
		}
	}

	return
}

// BuildArrayVirtualGuests builds the esqueleton of Virtual_Guest array
func BuildArrayVirtualGuests(args Parameters) []datatypes.Virtual_Guest {
	virtualGuests := []datatypes.Virtual_Guest{
		{
			Hostname: sl.String(args.hostname),
			Domain:   sl.String(args.domain),
		},
	}

	if args.privateVlanID > 0 {
		virtualGuests[0].PrimaryBackendNetworkComponent = &datatypes.Virtual_Guest_Network_Component{
			NetworkVlan: &datatypes.Network_Vlan{
				Id: sl.Int(args.privateVlanID),
			},
		}
	}

	if args.publicVlanID > 0 {
		virtualGuests[0].PrimaryNetworkComponent = &datatypes.Virtual_Guest_Network_Component{
			NetworkVlan: &datatypes.Network_Vlan{
				Id: sl.Int(args.publicVlanID),
			},
		}
	}
	return virtualGuests
}