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) }
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) }