// Get endpoint in config file and its status func getStatus(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 0) if err != nil { return err } client.Esxclient, err = client.GetClient(c) if err != nil { return err } status, err := client.Esxclient.Status.Get() if err != nil { return err } if !utils.NeedsFormatting(c) { err = printStatus(status) } else { utils.FormatObject(status, w, c) } if err != nil { return err } return nil }
// Set host's availability zone with the specified host ID, returns an error if one occurred func setHostAvailabilityZone(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 2) if err != nil { return err } id := c.Args().First() availabilityZoneId := c.Args()[1] client.Esxclient, err = client.GetClient(c) if err != nil { return err } setAvailabilityZoneSpec := photon.HostSetAvailabilityZoneOperation{} setAvailabilityZoneSpec.AvailabilityZoneId = availabilityZoneId setTask, err := client.Esxclient.Hosts.SetAvailabilityZone(id, &setAvailabilityZoneSpec) if err != nil { return err } id, err = waitOnTaskOperation(setTask.ID, c) if err != nil { return err } if utils.NeedsFormatting(c) { host, err := client.Esxclient.Hosts.Get(id) if err != nil { return err } utils.FormatObject(host, w, c) } return nil }
// Retrieves availability zone against specified id. func showAvailabilityZone(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 1) if err != nil { return err } id := c.Args().First() client.Esxclient, err = client.GetClient(c) if err != nil { return err } zone, err := client.Esxclient.AvailabilityZones.Get(id) if err != nil { return err } if c.GlobalIsSet("non-interactive") { fmt.Printf("%s\t%s\t%s\t%s\n", zone.ID, zone.Name, zone.Kind, zone.State) } else if utils.NeedsFormatting(c) { utils.FormatObject(zone, w, c) } else { fmt.Printf("AvailabilityZone ID: %s\n", zone.ID) fmt.Printf(" Name: %s\n", zone.Name) fmt.Printf(" Kind: %s\n", zone.Kind) fmt.Printf(" State: %s\n", zone.State) } return nil }
func showPhysicalNetwork(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 1) if err != nil { return err } id := c.Args().First() client.Esxclient, err = client.GetClient(c) if err != nil { return err } network, err := client.Esxclient.Subnets.Get(id) if err != nil { return err } if c.GlobalIsSet("non-interactive") { portGroups := getCommaSeparatedStringFromStringArray(network.PortGroups) fmt.Printf("%s\t%s\t%s\t%s\t%s\t%t\n", network.ID, network.Name, network.State, portGroups, network.Description, network.IsDefault) } else if utils.NeedsFormatting(c) { utils.FormatObject(network, w, c) } else { fmt.Printf("Network ID: %s\n", network.ID) fmt.Printf(" Name: %s\n", network.Name) fmt.Printf(" State: %s\n", network.State) fmt.Printf(" Description: %s\n", network.Description) fmt.Printf(" Port Groups: %s\n", network.PortGroups) fmt.Printf(" Is Default: %t\n", network.IsDefault) } return nil }
// Sends a "resize cluster" request to the API client based on the cli.Context // Returns an error if one occurred func resizeCluster(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 2) if err != nil { return err } cluster_id := c.Args()[0] worker_count_string := c.Args()[1] worker_count, err := strconv.Atoi(worker_count_string) wait_for_ready := c.IsSet("wait-for-ready") if len(cluster_id) == 0 || err != nil || worker_count <= 0 { return fmt.Errorf("Provide a valid cluster ID and worker count") } client.Esxclient, err = client.GetClient(c) if err != nil { return err } if !c.GlobalIsSet("non-interactive") { fmt.Printf("\nResizing cluster %s to worker count %d\n", cluster_id, worker_count) } if confirmed(c) { resizeSpec := photon.ClusterResizeOperation{} resizeSpec.NewWorkerCount = worker_count resizeTask, err := client.Esxclient.Clusters.Resize(cluster_id, &resizeSpec) if err != nil { return err } _, err = waitOnTaskOperation(resizeTask.ID, c) if err != nil { return err } if wait_for_ready { cluster, err := waitForCluster(cluster_id) if err != nil { return err } if utils.NeedsFormatting(c) { utils.FormatObject(cluster, w, c) } else { fmt.Printf("Cluster %s is ready\n", cluster.ID) } } else { fmt.Println("Note: A background task is running to gradually resize the cluster to its target capacity.") fmt.Printf("You may continue to use the cluster. You can run 'cluster show %s'\n", resizeTask.Entity.ID) fmt.Println("to see the state of the cluster. If the resize operation is still in progress, the cluster state") fmt.Println("will show as RESIZING. Once the cluster is resized, the cluster state will show as READY.") } } else { fmt.Println("Cancelled") } return nil }
// Show project info with the specified project id, returns an error if one occurred func showProject(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 1) if err != nil { return err } id := c.Args().First() client.Esxclient, err = client.GetClient(c) if err != nil { return err } project, err := client.Esxclient.Projects.Get(id) if err != nil { return err } if c.GlobalIsSet("non-interactive") { securityGroups := []string{} for _, s := range project.SecurityGroups { securityGroups = append(securityGroups, fmt.Sprintf("%s:%t", s.Name, s.Inherited)) } scriptSecurityGroups := strings.Join(securityGroups, ",") limits := quotaLineItemListToString(project.ResourceTicket.Limits) usages := quotaLineItemListToString(project.ResourceTicket.Usage) fmt.Printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\n", project.ID, project.Name, project.ResourceTicket.TenantTicketID, project.ResourceTicket.TenantTicketName, limits, usages, scriptSecurityGroups) } else if utils.NeedsFormatting(c) { utils.FormatObject(project, w, c) } else { w := new(tabwriter.Writer) w.Init(os.Stdout, 4, 4, 2, ' ', 0) fmt.Fprintf(w, "Project ID: %s\n", project.ID) fmt.Fprintf(w, " Name: %s\n", project.Name) fmt.Fprintf(w, " TenantTicketID: %s\n", project.ResourceTicket.TenantTicketID) fmt.Fprintf(w, " TenantTicketName: %s\n", project.ResourceTicket.TenantTicketName) fmt.Fprintf(w, " Limits:\n") for _, l := range project.ResourceTicket.Limits { fmt.Fprintf(w, " %s\t%g\t%s\n", l.Key, l.Value, l.Unit) } fmt.Fprintf(w, " Usage:\n") for _, u := range project.ResourceTicket.Usage { fmt.Fprintf(w, " %s\t%g\t%s\n", u.Key, u.Value, u.Unit) } if len(project.SecurityGroups) != 0 { fmt.Fprintf(w, " SecurityGroups:\n") for _, s := range project.SecurityGroups { fmt.Fprintf(w, " %s\t%t\n", s.Name, s.Inherited) } } err = w.Flush() if err != nil { return err } } return nil }
func formatHelper(c *cli.Context, w io.Writer, client *photon.Client, id string) error { if utils.NeedsFormatting(c) { vm, err := client.VMs.Get(id) if err != nil { return err } utils.FormatObject(vm, w, c) } return nil }
func deploymentJsonHelper(c *cli.Context, id string, client *photon.Client) error { if utils.NeedsFormatting(c) { deployment, err := client.Deployments.Get(id) if err != nil { return err } utils.FormatObject(deployment, os.Stdout, c) } return nil }
// Shows an image based on id func showImage(c *cli.Context, w io.Writer) error { id := c.Args().First() if !c.GlobalIsSet("non-interactive") { var err error id, err = askForInput("Image id: ", id) if err != nil { return err } } if len(id) == 0 { return fmt.Errorf("Please provide image id") } var err error client.Esxclient, err = client.GetClient(c) if err != nil { return err } image, err := client.Esxclient.Images.Get(id) if err != nil { return err } if c.GlobalIsSet("non-interactive") { settings := []string{} for _, setting := range image.Settings { settings = append(settings, fmt.Sprintf("%s:%s", setting.Name, setting.DefaultValue)) } scriptSettings := strings.Join(settings, ",") fmt.Printf("%s\t%s\t%s\t%d\t%s\t%s\t%s\t%s\n", image.ID, image.Name, image.State, image.Size, image.ReplicationType, image.ReplicationProgress, image.SeedingProgress, scriptSettings) } else if utils.NeedsFormatting(c) { utils.FormatObject(image, w, c) } else { fmt.Printf("Image ID: %s\n", image.ID) fmt.Printf(" Name: %s\n", image.Name) fmt.Printf(" State: %s\n", image.State) fmt.Printf(" Size: %d Byte(s)\n", image.Size) fmt.Printf(" Image Replication Type: %s\n", image.ReplicationType) fmt.Printf(" Image Replication Progress: %s\n", image.ReplicationProgress) fmt.Printf(" Image Seeding Progress: %s\n", image.SeedingProgress) fmt.Printf(" Settings: \n") for _, setting := range image.Settings { fmt.Printf(" %s : %s\n", setting.Name, setting.DefaultValue) } } return nil }
// Sends a show resource-ticket task to client based on the cli.Context // Returns an error if one occurred func showResourceTicket(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 1) if err != nil { return err } name := c.Args().First() tenantName := c.String("tenant") client.Esxclient, err = client.GetClient(c) if err != nil { return err } tenant, err := verifyTenant(tenantName) if err != nil { return err } rt, err := findResourceTicket(tenant.ID, name) if err != nil { return err } if c.GlobalIsSet("non-interactive") { usage := quotaLineItemListToString(rt.Usage) limits := quotaLineItemListToString(rt.Limits) fmt.Printf("%s\t%s\t%s\t%s\n", rt.Name, rt.ID, limits, usage) } else if utils.NeedsFormatting(c) { utils.FormatObject(rt, w, c) } else { w := new(tabwriter.Writer) w.Init(os.Stdout, 4, 4, 2, ' ', 0) fmt.Fprintf(w, "ID\tName\tLimit\tUsage\n") for i := 0; i < len(rt.Limits); i++ { if i == 0 { fmt.Fprintf(w, "%s\t%s\t%s %g %s\t%s %g %s\n", rt.ID, rt.Name, rt.Limits[i].Key, rt.Limits[i].Value, rt.Limits[i].Unit, rt.Usage[i].Key, rt.Usage[i].Value, rt.Usage[i].Unit) } else { fmt.Fprintf(w, "\t\t%s %g %s\t%s %g %s\n", rt.Limits[i].Key, rt.Limits[i].Value, rt.Limits[i].Unit, rt.Usage[i].Key, rt.Usage[i].Value, rt.Usage[i].Unit) } } err := w.Flush() if err != nil { return err } } return nil }
// Sends a create availability-zone task to client based on the cli.Context // Returns an error if one occurred func createAvailabilityZone(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 0) if err != nil { return err } name := c.String("name") if !c.GlobalIsSet("non-interactive") { var err error name, err = askForInput("AvailabilityZone name: ", name) if err != nil { return err } } if len(name) == 0 { return fmt.Errorf("Please provide availability zone name") } azSpec := &photon.AvailabilityZoneCreateSpec{ Name: name, } client.Esxclient, err = client.GetClient(c) if err != nil { return err } createTask, err := client.Esxclient.AvailabilityZones.Create(azSpec) if err != nil { return err } id, err := waitOnTaskOperation(createTask.ID, c) if err != nil { return err } if utils.NeedsFormatting(c) { zone, err := client.Esxclient.AvailabilityZones.Get(id) if err != nil { return err } utils.FormatObject(zone, w, c) } return nil }
func setDefaultNetwork(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 1) if err != nil { return err } id := c.Args().First() client.Esxclient, err = client.GetClient(c) if err != nil { return err } sdnEnabled, err := isSoftwareDefinedNetwork(c) if err != nil { log.Fatal("Error: ", err) } var task *photon.Task if sdnEnabled { task, err = client.Esxclient.VirtualSubnets.SetDefault(id) } else { task, err = client.Esxclient.Subnets.SetDefault(id) } if err != nil { return err } if confirmed(c) { id, err := waitOnTaskOperation(task.ID, c) if err != nil { return err } if utils.NeedsFormatting(c) { network, err := client.Esxclient.Subnets.Get(id) if err != nil { return err } utils.FormatObject(network, w, c) } } else { fmt.Println("OK. Canceled") } return nil }
func getVMMksTicket(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 1) if err != nil { return err } id := c.Args().First() client.Esxclient, err = client.GetClient(c) if err != nil { return err } task, err := client.Esxclient.VMs.GetMKSTicket(id) if err != nil { return err } if c.GlobalIsSet("non-interactive") { task, err := client.Esxclient.Tasks.Wait(task.ID) if err != nil { return err } mksTicket := task.ResourceProperties.(map[string]interface{}) fmt.Printf("%s\t%v\n", task.Entity.ID, mksTicket["ticket"]) } else if utils.NeedsFormatting(c) { task, err := client.Esxclient.Tasks.Wait(task.ID) if err != nil { return err } mksTicket := task.ResourceProperties.(map[string]interface{}) utils.FormatObject(mksTicket, w, c) } else { task, err = pollTask(task.ID) if err != nil { return err } mksTicket := task.ResourceProperties.(map[string]interface{}) fmt.Printf("VM ID: %s \nMks ticket ID is %v\n", task.Entity.ID, mksTicket["ticket"]) } return nil }
// Sends a show disk task to client based on the cli.Context // Returns an error if one occurred func showDisk(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 1) if err != nil { return err } id := c.Args().First() client.Esxclient, err = client.GetClient(c) if err != nil { return err } disk, err := client.Esxclient.Disks.Get(id) if err != nil { return err } if c.GlobalIsSet("non-interactive") { tag := strings.Trim(fmt.Sprint(disk.Tags), "[]") scriptTag := strings.Replace(tag, " ", ",", -1) vms := strings.Trim(fmt.Sprint(disk.VMs), "[]") scriptVMs := strings.Replace(vms, " ", ",", -1) fmt.Printf("%s\t%s\t%s\t%s\t%s\t%d\t%s\t%s\t%s\n", disk.ID, disk.Name, disk.State, disk.Kind, disk.Flavor, disk.CapacityGB, disk.Datastore, scriptTag, scriptVMs) } else if utils.NeedsFormatting(c) { utils.FormatObject(disk, w, c) } else { fmt.Println("Disk ID: ", disk.ID) fmt.Println(" Name: ", disk.Name) fmt.Println(" Kind: ", disk.Kind) fmt.Println(" Flavor: ", disk.Flavor) fmt.Println(" CapacityGB: ", disk.CapacityGB) fmt.Println(" State: ", disk.State) fmt.Println(" Datastore: ", disk.Datastore) fmt.Println(" Tags: ", disk.Tags) fmt.Println(" VMs: ", disk.VMs) } return nil }
func showVirtualNetwork(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 1) if err != nil { return err } id := c.Args().First() client.Esxclient, err = client.GetClient(c) if err != nil { return err } network, err := client.Esxclient.VirtualSubnets.Get(id) if err != nil { return err } if c.GlobalIsSet("non-interactive") { fmt.Printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", network.ID, network.Name, network.State, network.Description, network.RoutingType, network.IsDefault, network.Cidr, network.LowIpDynamic, network.HighIpDynamic, network.LowIpStatic, network.HighIpStatic, network.ReservedIpList) } else if utils.NeedsFormatting(c) { utils.FormatObject(network, w, c) } else { fmt.Printf("Network ID: %s\n", network.ID) fmt.Printf(" Name: %s\n", network.Name) fmt.Printf(" State: %s\n", network.State) fmt.Printf(" Description: %s\n", network.Description) fmt.Printf(" Routing Type: %s\n", network.RoutingType) fmt.Printf(" Is Default: %s\n", network.IsDefault) fmt.Printf(" CIDR: %s\n", network.Cidr) fmt.Printf(" Start Dynamic IP: %s\n", network.LowIpDynamic) fmt.Printf(" End Dynamic IP: %s\n", network.HighIpDynamic) fmt.Printf(" Start Static IP: %s\n", network.LowIpStatic) fmt.Printf(" End Static IP: %s\n", network.HighIpStatic) fmt.Printf(" Reserved IP List: %s\n", network.ReservedIpList) } return nil }
// Show host info with the specified host ID, returns an error if one occurred func showHost(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 1) if err != nil { return err } id := c.Args().First() client.Esxclient, err = client.GetClient(c) if err != nil { return err } host, err := client.Esxclient.Hosts.Get(id) if err != nil { return err } if c.GlobalIsSet("non-interactive") { tag := strings.Trim(fmt.Sprint(host.Tags), "[]") scriptTag := strings.Replace(tag, " ", ",", -1) metadata := strings.Trim(strings.TrimLeft(fmt.Sprint(host.Metadata), "map"), "[]") scriptMetadata := strings.Replace(metadata, " ", ",", -1) fmt.Printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", host.ID, host.Username, host.Address, scriptTag, host.State, scriptMetadata, host.AvailabilityZone, host.EsxVersion) } else if utils.NeedsFormatting(c) { host.Password = "" utils.FormatObject(host, w, c) } else { fmt.Println("Host ID: ", host.ID) fmt.Println(" Username: "******" IP: ", host.Address) fmt.Println(" Tags: ", host.Tags) fmt.Println(" State: ", host.State) fmt.Println(" Metadata: ", host.Metadata) fmt.Println(" AvailabilityZone: ", host.AvailabilityZone) fmt.Println(" Version: ", host.EsxVersion) } return nil }
// Shows information about Photon Controller: version, etc. func showInfo(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 0) if err != nil { return err } client.Esxclient, err = client.GetClient(c) if err != nil { return err } info, err := client.Esxclient.Info.Get() if utils.NeedsFormatting(c) { utils.FormatObject(info, w, c) } else { fmt.Printf("Version: '%s'\n", info.FullVersion) fmt.Printf("Network Type: '%s'\n", info.NetworkType) } return nil }
func showTenant(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 1) if err != nil { return err } id := c.Args().First() client.Esxclient, err = client.GetClient(c) if err != nil { return err } tenant, err := client.Esxclient.Tenants.Get(id) if err != nil { return err } if c.GlobalIsSet("non-interactive") { securityGroups := []string{} for _, s := range tenant.SecurityGroups { securityGroups = append(securityGroups, fmt.Sprintf("%s:%t", s.Name, s.Inherited)) } scriptSecurityGroups := strings.Join(securityGroups, ",") fmt.Printf("%s\t%s\t%s\n", tenant.ID, tenant.Name, scriptSecurityGroups) } else if utils.NeedsFormatting(c) { utils.FormatObject(tenant, w, c) } else { fmt.Println("Tenant ID: ", tenant.ID) fmt.Println(" Name: ", tenant.Name) for i, s := range tenant.SecurityGroups { fmt.Printf(" SecurityGroups %d:\n", i+1) fmt.Println(" Name: ", s.Name) fmt.Println(" Inherited: ", s.Inherited) } } return nil }
// Outputs the set tenant otherwise informs user it is not set func getTenant(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 0) if err != nil { return err } config, err := cf.LoadConfig() if err != nil { return err } tenant := config.Tenant if tenant == nil { fmt.Printf("No tenant selected\n") } else { if c.GlobalIsSet("non-interactive") { fmt.Printf("%s\t%s\n", tenant.ID, tenant.Name) } else if utils.NeedsFormatting(c) { utils.FormatObject(tenant, w, c) } else { fmt.Printf("Current tenant is '%s' with ID %s\n", tenant.Name, tenant.ID) } } return nil }
// Retrieves information about a flavor func showFlavor(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 1) if err != nil { return err } id := c.Args().First() client.Esxclient, err = client.GetClient(c) if err != nil { return err } flavor, err := client.Esxclient.Flavors.Get(id) if err != nil { return err } if c.GlobalIsSet("non-interactive") { costs := quotaLineItemListToString(flavor.Cost) fmt.Printf("%s\t%s\t%s\t%s\t%s\n", flavor.ID, flavor.Name, flavor.Kind, costs, flavor.State) } else if utils.NeedsFormatting(c) { utils.FormatObject(flavor, w, c) } else { costList := []string{} for _, cost := range flavor.Cost { costList = append(costList, fmt.Sprintf("%s %g %s", cost.Key, cost.Value, cost.Unit)) } fmt.Printf("Flavor ID: %s\n", flavor.ID) fmt.Printf(" Name: %s\n", flavor.Name) fmt.Printf(" Kind: %s\n", flavor.Kind) fmt.Printf(" Cost: %s\n", costList) fmt.Printf(" State: %s\n", flavor.State) } return nil }
// Sends a get project task to client based on the config file // Returns an error if one occurred func getProject(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 0) if err != nil { return err } config, err := cf.LoadConfig() if err != nil { return err } project := config.Project if project == nil { return fmt.Errorf("Error: No Project selected\n") } if c.GlobalIsSet("non-interactive") { fmt.Printf("%s\t%s\n", project.ID, project.Name) } else if utils.NeedsFormatting(c) { utils.FormatObject(project, w, c) } else { fmt.Printf("Current project is '%s' with ID %s\n", project.ID, project.Name) } return nil }
// Create an image func createImage(c *cli.Context, w io.Writer) error { if len(c.Args()) > 1 { return fmt.Errorf("Unknown argument: %v", c.Args()[1:]) } filePath := c.Args().First() name := c.String("name") replicationType := c.String("image_replication") if !c.GlobalIsSet("non-interactive") { var err error filePath, err = askForInput("Image path: ", filePath) if err != nil { return err } } if len(filePath) == 0 { return fmt.Errorf("Please provide image path") } filePath, err := filepath.Abs(filePath) if err != nil { return err } _, err = os.Stat(filePath) if err != nil { return fmt.Errorf("No such image file at that path") } if !c.GlobalIsSet("non-interactive") { defaultName := path.Base(filePath) name, err = askForInput("Image name (default: "+defaultName+"): ", name) if err != nil { return err } if len(name) == 0 { name = defaultName } defaultReplication := "EAGER" replicationType, err = askForInput("Image replication type (default: "+defaultReplication+"): ", replicationType) if err != nil { return err } if len(replicationType) == 0 { replicationType = defaultReplication } } file, err := os.Open(filePath) if err != nil { return err } client.Esxclient, err = client.GetClient(c) if err != nil { return err } options := &photon.ImageCreateOptions{ ReplicationType: replicationType, } if len(replicationType) == 0 { options = nil } uploadTask, err := client.Esxclient.Images.Create(file, name, options) if err != nil { return err } imageID, err := waitOnTaskOperation(uploadTask.ID, c) if err != nil { return err } err = file.Close() if err != nil { return err } if utils.NeedsFormatting(c) { image, err := client.Esxclient.Images.Get(imageID) if err != nil { return err } utils.FormatObject(image, w, c) } return nil }
// Sends a create host task to client based on the cli.Context // Returns an error if one occurred func createHost(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 0) if err != nil { return err } username := c.String("username") password := c.String("password") address := c.String("address") availabilityZone := c.String("availability_zone") tags := c.String("tag") metadata := c.String("metadata") deploymentID := c.String("deployment_id") deploymentID, err = getDeploymentId(c) if err != nil { return err } if !c.GlobalIsSet("non-interactive") { var err error username, err = askForInput("Username: "******"Password: "******"\n") } address, err = askForInput("Host Address: ", address) if err != nil { return err } tags, err = askForInput("Host Tags (Options [CLOUD,MGMT]. ',' separated): ", tags) if err != nil { return err } metadata, err = askForInput("Host Metadata ({'key':'value'}. required by host of 'MGMT' tag): ", metadata) if err != nil { return err } } hostSpec := photon.HostCreateSpec{} hostSpec.Username = username hostSpec.Password = password hostSpec.Address = address hostSpec.AvailabilityZone = availabilityZone hostSpec.Tags = regexp.MustCompile(`\s*,\s*`).Split(tags, -1) if len(metadata) == 0 { hostSpec.Metadata = map[string]string{} } else { var data map[string]string err := json.Unmarshal([]byte(metadata), &data) if err != nil { return err } hostSpec.Metadata = data } client.Esxclient, err = client.GetClient(c) if err != nil { return err } createTask, err := client.Esxclient.Hosts.Create(&hostSpec, deploymentID) if err != nil { return err } id, err := waitOnTaskOperation(createTask.ID, c) if err != nil { return err } if utils.NeedsFormatting(c) { host, err := client.Esxclient.Hosts.Get(id) if err != nil { return err } utils.FormatObject(host, w, c) } return nil }
// Retrieves information about a deployment func showDeployment(c *cli.Context, w io.Writer) error { id, err := getDeploymentId(c) if err != nil { return err } client.Esxclient, err = client.GetClient(c) if err != nil { return err } deployment, err := client.Esxclient.Deployments.Get(id) if err != nil { return err } vms, err := client.Esxclient.Deployments.GetVms(id) if err != nil { return err } var data []VM_NetworkIPs for _, vm := range vms.Items { networks, err := getVMNetworks(vm.ID, c) if err != nil { return err } ipAddr := "N/A" for _, nt := range networks { network := nt.(map[string]interface{}) if len(network) != 0 && network["network"] != nil { if val, ok := network["ipAddress"]; ok && val != nil { ipAddr = val.(string) break } } } data = append(data, VM_NetworkIPs{vm, ipAddr}) } if utils.NeedsFormatting(c) { utils.FormatObject(deployment, w, c) } else if c.GlobalIsSet("non-interactive") { imageDataStores := getCommaSeparatedStringFromStringArray(deployment.ImageDatastores) securityGroups := getCommaSeparatedStringFromStringArray(deployment.Auth.SecurityGroups) fmt.Printf("%s\t%s\t%s\t%t\t%s\t%s\t%t\t%s\n", deployment.ID, deployment.State, imageDataStores, deployment.UseImageDatastoreForVms, deployment.SyslogEndpoint, deployment.NTPEndpoint, deployment.LoadBalancerEnabled, deployment.LoadBalancerAddress) fmt.Printf("%t\t%s\t%s\t%d\t%s\n", deployment.Auth.Enabled, deployment.Auth.Endpoint, deployment.Auth.Tenant, deployment.Auth.Port, securityGroups) } else { syslogEndpoint := deployment.SyslogEndpoint if len(deployment.SyslogEndpoint) == 0 { syslogEndpoint = "-" } ntpEndpoint := deployment.NTPEndpoint if len(deployment.NTPEndpoint) == 0 { ntpEndpoint = "-" } fmt.Printf("\n") fmt.Printf("Deployment ID: %s\n", deployment.ID) fmt.Printf(" State: %s\n", deployment.State) fmt.Printf("\n Image Datastores: %s\n", deployment.ImageDatastores) fmt.Printf(" Use image datastore for vms: %t\n", deployment.UseImageDatastoreForVms) fmt.Printf("\n Syslog Endpoint: %s\n", syslogEndpoint) fmt.Printf(" Ntp Endpoint: %s\n", ntpEndpoint) fmt.Printf("\n LoadBalancer:\n") fmt.Printf(" Enabled: %t\n", deployment.LoadBalancerEnabled) if deployment.LoadBalancerEnabled { fmt.Printf(" Address: %s\n", deployment.LoadBalancerAddress) } fmt.Printf("\n Auth:\n") fmt.Printf(" Enabled: %t\n", deployment.Auth.Enabled) if deployment.Auth.Enabled { fmt.Printf(" Endpoint: %s\n", deployment.Auth.Endpoint) fmt.Printf(" Tenant: %s\n", deployment.Auth.Tenant) fmt.Printf(" Port: %d\n", deployment.Auth.Port) fmt.Printf(" Securitygroups: %v\n", deployment.Auth.SecurityGroups) } } if deployment.Stats != nil { stats := deployment.Stats if c.GlobalIsSet("non-interactive") { fmt.Printf("%t\t%s\t%d\n", stats.Enabled, stats.StoreEndpoint, stats.StorePort) } else if !utils.NeedsFormatting(c) { fmt.Printf("\n Stats:\n") fmt.Printf(" Enabled: %t\n", stats.Enabled) if stats.Enabled { fmt.Printf(" Store Endpoint: %s\n", stats.StoreEndpoint) fmt.Printf(" Store Port: %d\n", stats.StorePort) } } } else { if c.GlobalIsSet("non-interactive") { fmt.Printf("\n") } } if deployment.Migration != nil { migration := deployment.Migration if c.GlobalIsSet("non-interactive") { fmt.Printf("%d\t%d\t%d\t%d\t%d\n", migration.CompletedDataMigrationCycles, migration.DataMigrationCycleProgress, migration.DataMigrationCycleSize, migration.VibsUploaded, migration.VibsUploading+migration.VibsUploaded) } else if !utils.NeedsFormatting(c) { fmt.Printf("\n Migration status:\n") fmt.Printf(" Completed data migration cycles: %d\n", migration.CompletedDataMigrationCycles) fmt.Printf(" Current data migration cycles progress: %d / %d\n", migration.DataMigrationCycleProgress, migration.DataMigrationCycleSize) fmt.Printf(" VIB upload progress: %d / %d\n", migration.VibsUploaded, migration.VibsUploading+migration.VibsUploaded) } } else { if c.GlobalIsSet("non-interactive") { fmt.Printf("\n") } } if deployment.ClusterConfigurations != nil && len(deployment.ClusterConfigurations) != 0 { if c.GlobalIsSet("non-interactive") { clusterConfigurations := []string{} for _, c := range deployment.ClusterConfigurations { clusterConfigurations = append(clusterConfigurations, fmt.Sprintf("%s\t%s", c.Type, c.ImageID)) } scriptClusterConfigurations := strings.Join(clusterConfigurations, ",") fmt.Printf("%s\n", scriptClusterConfigurations) } else if !utils.NeedsFormatting(c) { fmt.Println("\n Cluster Configurations:") for i, c := range deployment.ClusterConfigurations { fmt.Printf(" ClusterConfiguration %d:\n", i+1) fmt.Println(" Kind: ", c.Kind) fmt.Println(" Type: ", c.Type) fmt.Println(" ImageID: ", c.ImageID) } } } else { if c.GlobalIsSet("non-interactive") { fmt.Printf("\n") } else if !utils.NeedsFormatting(c) { fmt.Println("\n Cluster Configurations:") fmt.Printf(" No cluster is supported") } } if !utils.NeedsFormatting(c) { err = displayDeploymentSummary(data, c.GlobalIsSet("non-interactive")) if err != nil { return err } } return nil }
// Sends a create project task to client based on the cli.Context // Returns an error if one occurred func createProject(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 0) if err != nil { return err } tenantName := c.String("tenant") rtName := c.String("resource-ticket") name := c.String("name") limits := c.String("limits") percent := c.Float64("percent") securityGroups := c.String("security-groups") client.Esxclient, err = client.GetClient(c) if err != nil { return err } tenant, err := verifyTenant(tenantName) if err != nil { return err } var limitsList []photon.QuotaLineItem if c.IsSet("limits") && c.IsSet("percent") { return fmt.Errorf("Error: Can only specify one of '--limits' or '--percent'") } if c.IsSet("limits") { limitsList, err = parseLimitsListFromFlag(limits) if err != nil { return err } } if c.IsSet("percent") { limitsList = []photon.QuotaLineItem{ {Key: "subdivide.percent", Value: percent, Unit: "COUNT"}} } if !c.GlobalIsSet("non-interactive") { name, err = askForInput("Project name: ", name) if err != nil { return err } rtName, err = askForInput("Resource-ticket name: ", rtName) if err != nil { return err } limitsList, err = askForLimitList(limitsList) if err != nil { return err } } projectSpec := photon.ProjectCreateSpec{} projectSpec.Name = name projectSpec.ResourceTicket = photon.ResourceTicketReservation{Name: rtName, Limits: limitsList} if !c.GlobalIsSet("non-interactive") { fmt.Printf("\nTenant name: %s\n", tenant.Name) fmt.Printf("Resource ticket name: %s\n", rtName) fmt.Printf("Creating project name: %s\n\n", name) fmt.Println("Please make sure limits below are correct:") for i, l := range limitsList { fmt.Printf("%d: %s, %g, %s\n", i+1, l.Key, l.Value, l.Unit) } } if confirmed(c) { if len(securityGroups) > 0 { projectSpec.SecurityGroups = regexp.MustCompile(`\s*,\s*`).Split(securityGroups, -1) } createTask, err := client.Esxclient.Tenants.CreateProject(tenant.ID, &projectSpec) if err != nil { return err } id, err := waitOnTaskOperation(createTask.ID, c) if err != nil { return err } if utils.NeedsFormatting(c) { project, err := client.Esxclient.Projects.Get(id) if err != nil { return err } utils.FormatObject(project, w, c) } } else { fmt.Println("OK. Canceled") } return nil }
// Sends a show VM task to client based on the cli.Context // Returns an error if one occurred func showVM(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 1) if err != nil { return err } id := c.Args().First() client.Esxclient, err = client.GetClient(c) if err != nil { return err } vm, err := client.Esxclient.VMs.Get(id) if err != nil { return err } var networks []interface{} if vm.State != "ERROR" { networks, err = getVMNetworks(id, c) if err != nil { return err } } if c.GlobalIsSet("non-interactive") { tag := strings.Trim(fmt.Sprint(vm.Tags), "[]") scriptTag := strings.Replace(tag, " ", ",", -1) metadata := strings.Trim(strings.TrimLeft(fmt.Sprint(vm.Metadata), "map"), "[]") scriptMetadata := strings.Replace(metadata, " ", ",", -1) disks := []string{} for _, d := range vm.AttachedDisks { disks = append(disks, fmt.Sprintf("%s\t%s\t%s\t%s\t%d\t%t", d.ID, d.Name, d.Kind, d.Flavor, d.CapacityGB, d.BootDisk)) } scriptDisks := strings.Join(disks, ",") iso := []string{} for _, i := range vm.AttachedISOs { iso = append(iso, fmt.Sprintf("%s\t%s\t%s\t%d", i.ID, i.Name, i.Kind, i.Size)) } scriptIso := strings.Join(iso, ",") fmt.Printf("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", vm.ID, vm.Name, vm.State, vm.Flavor, vm.SourceImageID, vm.Host, vm.Datastore, scriptMetadata, scriptTag, vm.FloatingIp) fmt.Printf("%s\n", scriptDisks) fmt.Printf("%s\n", scriptIso) err = printVMNetworks(networks, c.GlobalIsSet("non-interactive")) if err != nil { return err } } else if utils.NeedsFormatting(c) { utils.FormatObject(vm, w, c) } else { fmt.Println("VM ID: ", vm.ID) fmt.Println(" Name: ", vm.Name) fmt.Println(" State: ", vm.State) fmt.Println(" Floating IP: ", vm.FloatingIp) fmt.Println(" Flavor: ", vm.Flavor) fmt.Println(" Source Image:", vm.SourceImageID) fmt.Println(" Host: ", vm.Host) fmt.Println(" Datastore: ", vm.Datastore) fmt.Println(" Metadata: ", vm.Metadata) fmt.Println(" Disks: ") for i, d := range vm.AttachedDisks { fmt.Printf(" Disk %d:\n", i+1) fmt.Println(" ID: ", d.ID) fmt.Println(" Name: ", d.Name) fmt.Println(" Kind: ", d.Kind) fmt.Println(" Flavor: ", d.Flavor) fmt.Println(" Capacity: ", d.CapacityGB) fmt.Println(" Boot: ", d.BootDisk) } for i, iso := range vm.AttachedISOs { fmt.Printf(" ISO %d:\n", i+1) fmt.Println(" Name: ", iso.Name) fmt.Println(" Size: ", iso.Size) } for i, nt := range networks { network := nt.(map[string]interface{}) fmt.Printf(" Networks: %d\n", i+1) networkName := "" ipAddr := "" if val, ok := network["network"]; ok && val != nil { networkName = val.(string) } if val, ok := network["ipAddress"]; ok && val != nil { ipAddr = val.(string) } fmt.Println(" Name: ", networkName) fmt.Println(" IP Address: ", ipAddr) } for i, tag := range vm.Tags { fmt.Printf(" Tag %d:\n", i+1) fmt.Println(" Tag Info: ", tag) } } return nil }
// Sends a "create cluster" request to the API client based on the cli.Context // Returns an error if one occurred func createCluster(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 0) if err != nil { return err } tenantName := c.String("tenant") projectName := c.String("project") name := c.String("name") cluster_type := c.String("type") vm_flavor := c.String("vm_flavor") disk_flavor := c.String("disk_flavor") network_id := c.String("network_id") worker_count := c.Int("worker_count") dns := c.String("dns") gateway := c.String("gateway") netmask := c.String("netmask") master_ip := c.String("master-ip") container_network := c.String("container-network") etcd1 := c.String("etcd1") etcd2 := c.String("etcd2") etcd3 := c.String("etcd3") batch_size := c.Int("batchSize") ssh_key := c.String("ssh-key") ca_cert := c.String("registry-ca-cert") admin_password := c.String("admin-password") if admin_password != "" { result := validateHarborPassword(admin_password) if result != true { return fmt.Errorf("The Harbor password is invalid. It should have at least 7 characters " + "with 1 lowercase letter, 1 capital letter and 1 numeric character.") } } wait_for_ready := c.IsSet("wait-for-ready") const DEFAULT_WORKER_COUNT = 1 client.Esxclient, err = client.GetClient(c) if err != nil { return err } tenant, err := verifyTenant(tenantName) if err != nil { return err } project, err := verifyProject(tenant.ID, projectName) if err != nil { return err } if !c.GlobalIsSet("non-interactive") { name, err = askForInput("Cluster name: ", name) if err != nil { return err } cluster_type, err = askForInput("Cluster type: ", cluster_type) if err != nil { return err } if worker_count == 0 && cluster_type != "HARBOR" { worker_count_string, err := askForInput("Worker count: ", "") if err != nil { return err } worker_count, err = strconv.Atoi(worker_count_string) if err != nil { return fmt.Errorf("Please supply a valid worker count") } } } if len(name) == 0 || len(cluster_type) == 0 { return fmt.Errorf("Provide a valid cluster name and type") } if worker_count == 0 && cluster_type != "HARBOR" { worker_count = DEFAULT_WORKER_COUNT } if !c.GlobalIsSet("non-interactive") { dns, err = askForInput("Cluster DNS server: ", dns) if err != nil { return err } gateway, err = askForInput("Cluster network gateway: ", gateway) if err != nil { return err } netmask, err = askForInput("Cluster network netmask: ", netmask) if err != nil { return err } ssh_key, err = askForInput("Cluster ssh key file path (leave blank for none): ", ssh_key) if err != nil { return err } } if len(dns) == 0 || len(gateway) == 0 || len(netmask) == 0 { return fmt.Errorf("Provide a valid DNS, gateway, and netmask") } extended_properties := make(map[string]string) extended_properties[photon.ExtendedPropertyDNS] = dns extended_properties[photon.ExtendedPropertyGateway] = gateway extended_properties[photon.ExtendedPropertyNetMask] = netmask if len(ssh_key) != 0 { ssh_key_content, err := readSSHKey(ssh_key) if err == nil { extended_properties[photon.ExtendedPropertySSHKey] = ssh_key_content } else { return err } } if len(ca_cert) != 0 { ca_cert_content, err := readCACert(ca_cert) if err == nil { extended_properties[photon.ExtendedPropertyRegistryCACert] = ca_cert_content } else { return err } } cluster_type = strings.ToUpper(cluster_type) switch cluster_type { case "KUBERNETES": if !c.GlobalIsSet("non-interactive") { master_ip, err = askForInput("Kubernetes master static IP address: ", master_ip) if err != nil { return err } container_network, err = askForInput("Kubernetes worker network ID: ", container_network) if err != nil { return err } etcd1, err = askForInput("etcd server 1 static IP address: ", etcd1) if err != nil { return err } etcd2, err = askForInput("etcd server 2 static IP address (leave blank for none): ", etcd2) if err != nil { return err } if len(etcd2) != 0 { etcd3, err = askForInput("etcd server 3 static IP address (leave blank for none): ", etcd3) if err != nil { return err } } } extended_properties[photon.ExtendedPropertyMasterIP] = master_ip extended_properties[photon.ExtendedPropertyContainerNetwork] = container_network extended_properties[photon.ExtendedPropertyETCDIP1] = etcd1 if len(etcd2) != 0 { extended_properties[photon.ExtendedPropertyETCDIP2] = etcd2 if len(etcd3) != 0 { extended_properties[photon.ExtendedPropertyETCDIP3] = etcd3 } } case "HARBOR": if !c.GlobalIsSet("non-interactive") { master_ip, err = askForInput("Harbor master static IP address: ", master_ip) if err != nil { return err } if len(admin_password) == 0 { fmt.Printf("Harbor registry admin password: "******"The Harbor password is invalid. It should have at least 7 " + "characters with 1 lowercase letter, 1 capital letter and 1 numeric " + "character.") } fmt.Printf("\n") } } extended_properties[photon.ExtendedPropertyMasterIP] = master_ip extended_properties[photon.ExtendedPropertyAdminPassword] = admin_password default: return fmt.Errorf("Unsupported cluster type: %s", cluster_type) } clusterSpec := photon.ClusterCreateSpec{} clusterSpec.Name = name clusterSpec.Type = cluster_type clusterSpec.VMFlavor = vm_flavor clusterSpec.DiskFlavor = disk_flavor clusterSpec.NetworkID = network_id clusterSpec.WorkerCount = worker_count clusterSpec.BatchSizeWorker = batch_size clusterSpec.ExtendedProperties = extended_properties if !c.GlobalIsSet("non-interactive") { fmt.Printf("\n") fmt.Printf("Creating cluster: %s (%s)\n", clusterSpec.Name, clusterSpec.Type) if len(clusterSpec.VMFlavor) != 0 { fmt.Printf(" VM flavor: %s\n", clusterSpec.VMFlavor) } if len(clusterSpec.DiskFlavor) != 0 { fmt.Printf(" Disk flavor: %s\n", clusterSpec.DiskFlavor) } if clusterSpec.Type != "HARBOR" { fmt.Printf(" Worker count: %d\n", clusterSpec.WorkerCount) } if clusterSpec.BatchSizeWorker != 0 { fmt.Printf(" Batch size: %d\n", clusterSpec.BatchSizeWorker) } fmt.Printf("\n") } if confirmed(c) { createTask, err := client.Esxclient.Projects.CreateCluster(project.ID, &clusterSpec) if err != nil { return err } _, err = waitOnTaskOperation(createTask.ID, c) if err != nil { return err } if wait_for_ready { if !utils.NeedsFormatting(c) { fmt.Printf("Waiting for cluster %s to become ready\n", createTask.Entity.ID) } cluster, err := waitForCluster(createTask.Entity.ID) if err != nil { return err } if utils.NeedsFormatting(c) { utils.FormatObject(cluster, w, c) } else { fmt.Printf("Cluster %s is ready\n", cluster.ID) } } else { fmt.Println("Note: the cluster has been created with minimal resources. You can use the cluster now.") fmt.Println("A background task is running to gradually expand the cluster to its target capacity.") fmt.Printf("You can run 'cluster show %s' to see the state of the cluster.\n", createTask.Entity.ID) } } else { fmt.Println("Cancelled") } return nil }
func createVirtualNetwork(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 0) if err != nil { return err } name := c.String("name") description := c.String("description") routingType := c.String("routingType") sizeStr := c.String("size") staticIpSizeStr := c.String("staticIpSize") projectId := c.String("projectId") if len(projectId) == 0 { tenant, err := verifyTenant("") if err != nil { return err } project, err := verifyProject(tenant.ID, "") if err != nil { return err } projectId = project.ID } if !c.GlobalIsSet("non-interactive") { name, err = askForInput("Network name: ", name) if err != nil { return err } description, err = askForInput("Description of network: ", description) if err != nil { return err } routingType, err = askForInput("Routing type of network: ", routingType) if err != nil { return err } projectId, err = askForInput("Project ID that network belongs to: ", projectId) if err != nil { return err } sizeStr, err = askForInput("Size of IP pool of the network (must be power of 2, at least 8): ", sizeStr) if err != nil { return err } staticIpSizeStr, err = askForInput("Size of the static IP pool (must be less than size of IP pool): ", staticIpSizeStr) if err != nil { return err } } if len(name) == 0 { return fmt.Errorf("Please provide network name") } if routingType != ROUTED && routingType != ISOLATED { return fmt.Errorf("Please choose the correct routing type for network (ROUTED or ISOLATED)") } size, err := strconv.Atoi(sizeStr) if err != nil { return err } if size < 8 { return fmt.Errorf("Network size must be at least 8") } staticIpSize, err := strconv.Atoi(staticIpSizeStr) if err != nil { return err } createSpec := &photon.VirtualSubnetCreateSpec{ Name: name, Description: description, RoutingType: routingType, Size: size, ReservedStaticIpSize: staticIpSize, } client.Esxclient, err = client.GetClient(c) if err != nil { return err } task, err := client.Esxclient.VirtualSubnets.Create(projectId, createSpec) if err != nil { return err } id, err := waitOnTaskOperation(task.ID, c) if err != nil { return err } if utils.NeedsFormatting(c) { network, err := client.Esxclient.Subnets.Get(id) if err != nil { return err } utils.FormatObject(network, w, c) } return nil }
// Sends a "show cluster" request to the API client based on the cli.Context // Returns an error if one occurred func showCluster(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 1) if err != nil { return err } id := c.Args().First() client.Esxclient, err = client.GetClient(c) if err != nil { return err } cluster, err := client.Esxclient.Clusters.Get(id) if err != nil { return err } vms, err := client.Esxclient.Clusters.GetVMs(id) if err != nil { return err } var master_vms []photon.VM for _, vm := range vms.Items { for _, tag := range vm.Tags { if strings.Count(tag, ":") == 2 && !strings.Contains(strings.ToLower(tag), "worker") { master_vms = append(master_vms, vm) break } } } if c.GlobalIsSet("non-interactive") { extendedProperties := strings.Trim(strings.TrimLeft(fmt.Sprint(cluster.ExtendedProperties), "map"), "[]") if cluster.ErrorReason != "" { fmt.Printf("%s\t%s\t%s\t%s\t%d\t%s\t%s\n", cluster.ID, cluster.Name, cluster.State, cluster.Type, cluster.WorkerCount, cluster.ErrorReason, extendedProperties) } else { fmt.Printf("%s\t%s\t%s\t%s\t%d\t%s\n", cluster.ID, cluster.Name, cluster.State, cluster.Type, cluster.WorkerCount, extendedProperties) } } else if utils.NeedsFormatting(c) { utils.FormatObject(cluster, w, c) } else { fmt.Println("Cluster ID: ", cluster.ID) fmt.Println(" Name: ", cluster.Name) fmt.Println(" State: ", cluster.State) fmt.Println(" Type: ", cluster.Type) fmt.Println(" Worker count: ", cluster.WorkerCount) if cluster.ErrorReason != "" { fmt.Println(" Error Reason: ", cluster.ErrorReason) } fmt.Println(" Extended Properties: ", cluster.ExtendedProperties) fmt.Println() } err = printClusterVMs(master_vms, w, c) if err != nil { return err } return nil }
func createPhysicalNetwork(c *cli.Context, w io.Writer) error { err := checkArgCount(c, 0) if err != nil { return err } name := c.String("name") description := c.String("description") portGroups := c.String("portgroups") if !c.GlobalIsSet("non-interactive") { name, err = askForInput("Network name: ", name) if err != nil { return err } description, err = askForInput("Description of network: ", description) if err != nil { return err } portGroups, err = askForInput("PortGroups of network: ", portGroups) if err != nil { return err } } if len(name) == 0 { return fmt.Errorf("Please provide network name") } if len(portGroups) == 0 { return fmt.Errorf("Please provide portgroups") } portGroupList := regexp.MustCompile(`\s*,\s*`).Split(portGroups, -1) createSpec := &photon.SubnetCreateSpec{ Name: name, Description: description, PortGroups: portGroupList, } client.Esxclient, err = client.GetClient(c) if err != nil { return err } task, err := client.Esxclient.Subnets.Create(createSpec) if err != nil { return err } id, err := waitOnTaskOperation(task.ID, c) if err != nil { return err } if utils.NeedsFormatting(c) { network, err := client.Esxclient.Subnets.Get(id) if err != nil { return err } utils.FormatObject(network, w, c) } return nil }