Example #1
0
func main() {
	var hwGroup = flag.String("g", "", "UUID or name (if unique) of the HW group to add this server to")
	var location = flag.String("l", "", "Data centre alias (to resolve group and/or network ID)")
	var srcServer = flag.String("src", "", "The name of a source-server, or a template, to create from")
	var srcPass = flag.String("srcPass", "", "When cloning from a source-server, use this password")
	var seed = flag.String("s", "AUTO", "The seed for the server name")
	var desc = flag.String("t", "", "Description of the server")

	var net = flag.String("net", "", "ID or name of the Network to use")
	var primDNS = flag.String("dns1", "8.8.8.8", "Primary DNS to use")
	var secDNS = flag.String("dns2", "8.8.4.4", "Secondary DNS to use")
	var password = flag.String("pass", "", "Desired password. Leave blank to auto-generate")

	var extraDrv = flag.Int("drive", 0, "Extra storage (in GB) to add to server as a raw disk")
	var numCpu = flag.Int("cpu", 1, "Number of Cpus to use")
	var memGB = flag.Int("memory", 4, "Amount of memory in GB")
	var serverType = flag.String("type", "standard", "The type of server to create (standard, hyperscale, or bareMetal)")
	var ttl = flag.Duration("ttl", 0, "Time span (counting from time of creation) until server gets deleted")

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "usage: %s [options]\n", path.Base(os.Args[0]))
		flag.PrintDefaults()
	}

	flag.Parse()
	if *hwGroup == "" {
		flag.Usage()
		os.Exit(0)
	}

	client, err := clcv2.NewCLIClient()
	if err != nil {
		log.Fatalf(err.Error())
	}

	/* hwGroup may be hex uuid or group name */
	if _, err := hex.DecodeString(*hwGroup); err != nil {
		log.Printf("Resolving ID of Hardware Group %q ...", *hwGroup)

		if group, err := client.GetGroupByName(*hwGroup, *location); err != nil {
			log.Fatalf("failed to resolve group name %q: %s", *hwGroup, err)
		} else if group == nil {
			log.Fatalf("no group named %q was found in %s", *hwGroup, *location)
		} else {
			*hwGroup = group.Id
		}
	}

	/* net is supposed to be a (hex) ID, but allow network names, too */
	if *net != "" {
		if _, err := hex.DecodeString(*net); err == nil {
			/* already looks like a HEX ID */
		} else if *location == "" {
			log.Fatalf("Need a location argument (-l) if not using a network ID (%s)", *net)
		} else {
			log.Printf("resolving network id of %q ...", *net)

			if netw, err := client.GetNetworkIdByName(*net, *location); err != nil {
				log.Fatalf("failed to resolve network name %q: %s", *net, err)
			} else if netw == nil {
				log.Fatalf("No network named %q was found in %s", *net, *location)
			} else {
				*net = netw.Id
			}
		}
	}

	req := clcv2.CreateServerReq{
		// Name of the server to create. Alphanumeric characters and dashes only.
		Name: *seed,

		// User-defined description of this server
		Description: *desc,

		// ID of the parent HW group.
		GroupId: *hwGroup,

		// ID of the server to use a source. May be the ID of a srcServer, or when cloning, an existing server ID.
		SourceServerId: *srcServer,

		// The primary DNS to set on the server
		PrimaryDns: *primDNS,

		// The secondary DNS to set on the server
		SecondaryDns: *secDNS,

		// ID of the network to which to deploy the server.
		NetworkId: *net,

		// Password of administrator or root user on server.
		Password: *password,

		// Password of the source server, used only when creating a clone from an existing server.
		SourceServerPassword: *srcPass,

		// Number of processors to configure the server with (1-16)
		Cpu: *numCpu,

		// Number of GB of memory to configure the server with (1-128)
		MemoryGB: *memGB,

		// Whether to create a 'standard', 'hyperscale', or 'bareMetal' server
		Type: *serverType,

		// FIXME: the following are not populated in this request:
		// - IpAddress
		// - IsManagedOs
		// - IsManagedBackup
		// - AntiAffinityPolicyId
		// - CpuAutoscalePolicyId
		// - CustomFields
		// - Packages
		//
		// The following items relevant specific to bare-metal servers are also ignored:
		// - ConfigurationId
		// - OsType
	}

	if *extraDrv != 0 {
		req.AdditionalDisks = append(req.AdditionalDisks,
			clcv2.ServerAdditionalDisk{SizeGB: uint32(*extraDrv), Type: "raw"})
	}

	/* Date/time that the server should be deleted. */
	if *ttl != 0 {
		req.Ttl = new(time.Time)
		*req.Ttl = time.Now().Add(*ttl)
	}

	// The CreateServer request resolves the server name at the end.
	// This second call can fail at the remote end; it does not mean that
	// the server has not been created yet.
	url, reqID, err := client.CreateServer(&req)
	if err != nil {
		log.Fatalf("failed to create server: %s", err)
	}
	log.Printf("Status Id: %s", reqID)
	client.PollStatus(reqID, 5*time.Second)

	// Print details after job completes
	server, err := client.GetServerByURI(url)
	if err != nil {
		log.Fatalf("failed to query server details at %s: %s", url, err)
	}
	showServer(client, server)
}
Example #2
0
func main() {
	var net = flag.String("net", "", "ID or name of the Network to use (if different from source)")
	var hwGroup = flag.String("g", "", "UUID or name (if unique) of the HW group to clone this server into")
	var primDNS = flag.String("dns1", "8.8.8.8", "Primary DNS to use")
	var secDNS = flag.String("dns2", "8.8.4.4", "Secondary DNS to use")
	var numCpu = flag.Int("cpu", 0, "Number of Cpus to use (if different from source VM)")
	var memGB = flag.Int("memory", 0, "Amount of memory in GB (if different from source VM")
	var seed = flag.String("name", "", "The 4-6 character seed for the name of the cloned server")
	var desc = flag.String("desc", "", "Description of the cloned server")
	var ttl = flag.Duration("ttl", 0, "Time span (counting from time of creation) until server gets deleted")
	var extraDrv = flag.Int("drive", 0, "Extra storage (in GB) to add to server as a raw disk")
	var wasStopped bool
	var maxAttempts = 1
	var url, reqID string

	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "usage: %s [options] <Source-Server-Name>\n", path.Base(os.Args[0]))
		flag.PrintDefaults()
	}

	flag.Parse()
	if flag.NArg() != 1 {
		flag.Usage()
		os.Exit(0)
	}

	client, err := clcv2.NewCLIClient()
	if err != nil {
		exit.Fatal(err.Error())
	}

	// First get the details of the source server
	log.Printf("Obtaining details of source server %s ...", flag.Arg(0))
	src, err := client.GetServer(flag.Arg(0))
	if err != nil {
		exit.Fatalf("failed to list details of source server %q: %s", flag.Arg(0), err)
	}

	if wasStopped = src.Details.PowerState == "stopped"; wasStopped {
		// The source server must be powered on
		log.Printf("%s is powered-off - powering on ...", src.Name)
		reqID, err := client.PowerOnServer(src.Name)
		if err != nil {
			exit.Fatalf("failed to power on source server %s: %s", src.Name, err)
		}
		log.Printf("Waiting for %s to power on (status ID: %s) ...", src.Name, reqID)
		if _, err = client.AwaitCompletion(reqID); err != nil {
			exit.Fatalf("failed to await completion of %s: %s", reqID, err)
		}
		// When the server is being powered on, it can take up to 5 minutes until
		// the backend is able to clone it; it requires the server to be fully booted.
		maxAttempts = 5
		time.Sleep(1 * time.Minute)
	}

	// We need the credentials, too
	log.Printf("Obtaining %s credentials ...", src.Name)
	credentials, err := client.GetServerCredentials(src.Name)
	if err != nil {
		exit.Fatalf("failed to obtain the credentials of server %q: %s", src.Name, err)
	}

	req := clcv2.CreateServerReq{
		Name:                 *seed,
		Cpu:                  src.Details.Cpu,
		MemoryGB:             src.Details.MemoryMb >> 10,
		GroupId:              src.GroupId,
		SourceServerId:       src.Name,
		PrimaryDns:           *primDNS,
		SecondaryDns:         *secDNS,
		Password:             credentials.Password,
		SourceServerPassword: credentials.Password,

		Type: src.Type,
	}

	if *seed == "" {
		if l := len(src.Name); l > 10 { // use same naming as original by default
			req.Name = src.Name[7 : l-1]
		} else {
			req.Name = "CLONE"
		}
	}

	if *numCpu != 0 {
		req.Cpu = *numCpu
	}
	if *memGB != 0 {
		req.MemoryGB = *memGB
	}

	if *desc != "" {
		req.Description = *desc
	} else if src.Description == "" {
		req.Description = fmt.Sprintf("Clone of %s", src.Name)
	} else {
		req.Description = fmt.Sprintf("%s (cloned from %s)", src.Description, src.Name)
	}

	if *extraDrv != 0 {
		req.AdditionalDisks = append(req.AdditionalDisks,
			clcv2.ServerAdditionalDisk{SizeGB: uint32(*extraDrv), Type: "raw"})
	}
	if *ttl != 0 { /* Date/time that the server should be deleted. */
		req.Ttl = new(time.Time)
		*req.Ttl = time.Now().Add(*ttl)
	}

	/* hwGroup may be hex uuid or group name */
	if *hwGroup != "" {
		req.GroupId = *hwGroup

		if _, err := hex.DecodeString(*hwGroup); err != nil {
			log.Printf("Resolving ID of Hardware Group %q in %s ...", *hwGroup, src.LocationId)

			if group, err := client.GetGroupByName(*hwGroup, src.LocationId); err != nil {
				exit.Fatalf("failed to resolve group name %q: %s", *hwGroup, err)
			} else if group == nil {
				exit.Errorf("No group named %q was found in %s", *hwGroup, src.LocationId)
			} else {
				req.GroupId = group.Id
			}
		}
	}

	/* net is supposed to be a (hex) ID, but allow network names, too */
	if *net == "" {
		log.Printf("Determining network ID used by %s ...", src.Name)

		if nets, err := client.GetServerNets(src); err != nil {
			exit.Fatalf("failed to query networks of %s: %s", src.Name, err)
		} else if len(nets) == 0 {
			// No network information found for the server, even though it has an IP.
			// This can happen when the server is owned by a sub-account, and uses a
			// network that is owned by the parent account. In this case, the sub-account
			// is prevented from querying details of the parent account, due to insufficient
			// permission.
			log.Printf("Unable to determine network details - querying %s deployable networks ...", src.LocationId)
			capa, err := client.GetDeploymentCapabilities(src.LocationId)
			if err != nil {
				exit.Fatalf("failed to determine %s Deployment Capabilities: %s", src.LocationId, err)
			}
			fmt.Println("Please specify the network ID for the clone manually via -net, using this information:")
			table := tablewriter.NewWriter(os.Stdout)
			table.SetAutoFormatHeaders(false)
			table.SetAlignment(tablewriter.ALIGN_LEFT)
			table.SetAutoWrapText(false)

			table.SetHeader([]string{"Name", "Type", "Account", "Network ID"})
			for _, net := range capa.DeployableNetworks {
				table.Append([]string{net.Name, net.Type, net.AccountID, net.NetworkId})
			}

			table.Render()
			os.Exit(0)
		} else if len(nets) != 1 {
			// FIXME: print server networks
			exit.Errorf("please specify which network to use (%s uses %d)", src.Name, len(nets))
		} else {
			req.NetworkId = nets[0].Id
		}
	} else if _, err := hex.DecodeString(*net); err != nil { // not a HEX ID, treat as group name
		log.Printf("Resolving network ID of %q in %s ...", *net, src.LocationId)

		if netw, err := client.GetNetworkIdByName(*net, src.LocationId); err != nil {
			exit.Fatalf("failed to resolve network name %q: %s", *net, err)
		} else if netw == nil {
			exit.Fatalf("unable to resolve network name %q in %s - maybe use hex ID?", *net, src.LocationId)
		} else {
			req.NetworkId = netw.Id
		}
	} else { // HEX ID, use directoy
		req.NetworkId = *net
	}

	log.Printf("Cloning %s ...", src.Name)
	for i := 1; ; i++ {
		url, reqID, err = client.CreateServer(&req)
		if err == nil || i == maxAttempts || strings.Index(err.Error(), "body.sourceServerId") > 0 {
			break
		}
		log.Printf("attempt %d/%d failed (%s) - retrying ...", i, maxAttempts, strings.TrimSpace(err.Error()))
		time.Sleep(1 * time.Minute)
	}
	if err != nil {
		exit.Fatalf("failed to create server: %s", err)
	}

	log.Printf("Status Id: %s\n", reqID)
	status, err := client.PollStatus(reqID, 5*time.Second)
	if err != nil {
		exit.Fatalf("failed to poll %s status: %s", reqID, err)
	}

	server, err := client.GetServerByURI(url)
	if err != nil {
		log.Fatalf("failed to query server details at %s: %s", url, err)
	} else if status == clcv2.Failed {
		exit.Fatalf("failed to clone %s (will show up as 'under construction')", server.Name)
	}
	log.Printf("New server name: %s\n", server.Name)
	log.Printf("Server Password: \"%s\"\n", credentials.Password)
}