// formatConfigTabular writes a tabular summary of config information. func formatConfigTabular(writer io.Writer, value interface{}) error { configValues, ok := value.(config.ConfigValues) if !ok { return errors.Errorf("expected value of type %T, got %T", configValues, value) } tw := output.TabWriter(writer) w := output.Wrapper{tw} var valueNames []string for name := range configValues { valueNames = append(valueNames, name) } sort.Strings(valueNames) w.Println("Attribute", "From", "Value") for _, name := range valueNames { info := configValues[name] out := &bytes.Buffer{} err := cmd.FormatYaml(out, info.Value) if err != nil { return errors.Annotatef(err, "formatting value for %q", name) } // Some attribute values have a newline appended // which makes the output messy. valString := strings.TrimSuffix(out.String(), "\n") w.Println(name, info.Source, valString) } tw.Flush() return nil }
func formatServiceDetailTabular(writer io.Writer, resources FormattedServiceDetails) { // note that the unit resource can be a zero value here, to indicate that // the unit has not downloaded that resource yet. fmt.Fprintln(writer, "[Units]") sort.Sort(byUnitID(resources.Resources)) // To format things into columns. tw := output.TabWriter(writer) // Write the header. fmt.Fprintln(tw, "Unit\tResource\tRevision\tExpected") for _, r := range resources.Resources { fmt.Fprintf(tw, "%v\t%v\t%v\t%v\n", r.unitNumber, r.Expected.Name, r.Unit.combinedRevision, r.revProgress, ) } tw.Flush() writeUpdates(resources.Updates, writer, tw) }
// FormatTabular writes a tabular summary of payloads. func FormatTabular(writer io.Writer, value interface{}) error { payloads, valueConverted := value.([]FormattedPayload) if !valueConverted { return errors.Errorf("expected value of type %T, got %T", payloads, value) } // TODO(ericsnow) sort the rows first? tw := output.TabWriter(writer) // Write the header. fmt.Fprintln(tw, tabularSection) fmt.Fprintln(tw, tabularHeader) // Print each payload to its own row. for _, payload := range payloads { // tabularColumns must be kept in sync with these. fmt.Fprintf(tw, tabularRow+"\n", payload.Unit, payload.Machine, payload.Class, payload.Status, payload.Type, payload.ID, strings.Join(payload.Labels, " "), ) } tw.Flush() return nil }
func (c *listCommand) formatModelUsers(writer io.Writer, value interface{}) error { users, ok := value.(map[string]common.ModelUserInfo) if !ok { return errors.Errorf("expected value of type %T, got %T", users, value) } modelUsers := set.NewStrings() for name := range users { modelUsers.Add(name) } tw := output.TabWriter(writer) w := output.Wrapper{tw} w.Println("Name", "Display name", "Access", "Last connection") for _, name := range modelUsers.SortedValues() { user := users[name] var highlight *ansiterm.Context userName := name if c.isLoggedInUser(name) { userName += "*" highlight = output.CurrentHighlight } w.PrintColor(highlight, userName) w.Println(user.DisplayName, user.Access, user.LastConnection) } tw.Flush() return nil }
func (c *listCommand) formatControllerUsers(writer io.Writer, value interface{}) error { users, valueConverted := value.([]UserInfo) if !valueConverted { return errors.Errorf("expected value of type %T, got %T", users, value) } tw := output.TabWriter(writer) w := output.Wrapper{tw} w.Println("Controller: " + c.ControllerName()) w.Println() w.Println("Name", "Display name", "Access", "Date created", "Last connection") for _, user := range users { conn := user.LastConnection if user.Disabled { conn += " (disabled)" } var highlight *ansiterm.Context userName := user.Username if c.isLoggedInUser(user.Username) { userName += "*" highlight = output.CurrentHighlight } w.PrintColor(highlight, userName) w.Println(user.DisplayName, user.Access, user.DateCreated, conn) } tw.Flush() return nil }
// FormatCharmTabular returns a tabular summary of charm resources. func FormatCharmTabular(writer io.Writer, value interface{}) error { resources, valueConverted := value.([]FormattedCharmResource) if !valueConverted { return errors.Errorf("expected value of type %T, got %T", resources, value) } // TODO(ericsnow) sort the rows first? // To format things into columns. tw := output.TabWriter(writer) // Write the header. // We do not print a section label. fmt.Fprintln(tw, "Resource\tRevision") // Print each info to its own row. for _, res := range resources { // the column headers must be kept in sync with these. fmt.Fprintf(tw, "%s\t%d\n", res.Name, res.Revision, ) } tw.Flush() return nil }
// formatPoolsTabular returns a tabular summary of pool instances. func formatPoolsTabular(writer io.Writer, pools map[string]PoolInfo) { tw := output.TabWriter(writer) print := func(values ...string) { fmt.Fprintln(tw, strings.Join(values, "\t")) } print("Name", "Provider", "Attrs") poolNames := make([]string, 0, len(pools)) for name := range pools { poolNames = append(poolNames, name) } sort.Strings(poolNames) for _, name := range poolNames { pool := pools[name] // order by key for deterministic return keys := make([]string, 0, len(pool.Attrs)) for key := range pool.Attrs { keys = append(keys, key) } sort.Strings(keys) attrs := make([]string, len(pool.Attrs)) for i, key := range keys { attrs[i] = fmt.Sprintf("%v=%v", key, pool.Attrs[key]) } print(name, pool.Provider, strings.Join(attrs, " ")) } tw.Flush() }
func formatRegionsTabular(writer io.Writer, regions yaml.MapSlice) error { tw := output.TabWriter(writer) w := output.Wrapper{tw} for _, r := range regions { w.Println(r.Key) } tw.Flush() return nil }
func newSummaryFormatter(writer io.Writer) *summaryFormatter { f := &summaryFormatter{ ipAddrs: make([]net.IPNet, 0), netStrings: make([]string, 0), openPorts: set.NewStrings(), stateToUnit: make(map[status.Status]int), } f.tw = output.TabWriter(writer) return f }
// formatCloudsTabular writes a tabular summary of cloud information. func formatCloudsTabular(writer io.Writer, value interface{}) error { clouds, ok := value.(*cloudList) if !ok { return errors.Errorf("expected value of type %T, got %T", clouds, value) } tw := output.TabWriter(writer) p := func(values ...string) { text := strings.Join(values, "\t") fmt.Fprintln(tw, text) } p("CLOUD\tTYPE\tREGIONS") cloudNamesSorted := func(someClouds map[string]*cloudDetails) []string { // For tabular we'll sort alphabetically, user clouds last. var names []string for name, _ := range someClouds { names = append(names, name) } sort.Strings(names) return names } printClouds := func(someClouds map[string]*cloudDetails) { cloudNames := cloudNamesSorted(someClouds) for _, name := range cloudNames { info := someClouds[name] var regions []string for _, region := range info.Regions { regions = append(regions, fmt.Sprint(region.Key)) } // TODO(wallyworld) - we should be smarter about handling // long region text, for now we'll display the first 7 as // that covers all clouds except AWS and Azure and will // prevent wrapping on a reasonable terminal width. regionCount := len(regions) if regionCount > 7 { regionCount = 7 } regionText := strings.Join(regions[:regionCount], ", ") if len(regions) > 7 { regionText = regionText + " ..." } p(name, info.CloudType, regionText) } } printClouds(clouds.public) printClouds(clouds.builtin) printClouds(clouds.personal) tw.Flush() return nil }
// formatVolumeListTabular returns a tabular summary of volume instances. func formatVolumeListTabular(writer io.Writer, infos map[string]VolumeInfo) error { tw := output.TabWriter(writer) print := func(values ...string) { fmt.Fprintln(tw, strings.Join(values, "\t")) } print("Machine", "Unit", "Storage", "Id", "Provider Id", "Device", "Size", "State", "Message") volumeAttachmentInfos := make(volumeAttachmentInfos, 0, len(infos)) for volumeId, info := range infos { volumeAttachmentInfo := volumeAttachmentInfo{ VolumeId: volumeId, VolumeInfo: info, } if info.Attachments == nil { volumeAttachmentInfos = append(volumeAttachmentInfos, volumeAttachmentInfo) continue } // Each unit attachment must have a corresponding volume // attachment. Enumerate each of the volume attachments, // and locate the corresponding unit attachment if any. // Each volume attachment has at most one corresponding // unit attachment. for machineId, machineInfo := range info.Attachments.Machines { volumeAttachmentInfo := volumeAttachmentInfo volumeAttachmentInfo.MachineId = machineId volumeAttachmentInfo.MachineVolumeAttachment = machineInfo for unitId, unitInfo := range info.Attachments.Units { if unitInfo.MachineId == machineId { volumeAttachmentInfo.UnitId = unitId volumeAttachmentInfo.UnitStorageAttachment = unitInfo break } } volumeAttachmentInfos = append(volumeAttachmentInfos, volumeAttachmentInfo) } } sort.Sort(volumeAttachmentInfos) for _, info := range volumeAttachmentInfos { var size string if info.Size > 0 { size = humanize.IBytes(info.Size * humanize.MiByte) } print( info.MachineId, info.UnitId, info.Storage, info.VolumeId, info.ProviderVolumeId, info.DeviceName, size, string(info.Status.Current), info.Status.Message, ) } return tw.Flush() }
// formatFilesystemListTabular writes a tabular summary of filesystem instances. func formatFilesystemListTabular(writer io.Writer, infos map[string]FilesystemInfo) error { tw := output.TabWriter(writer) print := func(values ...string) { fmt.Fprintln(tw, strings.Join(values, "\t")) } print("MACHINE", "UNIT", "STORAGE", "ID", "VOLUME", "PROVIDER-ID", "MOUNTPOINT", "SIZE", "STATE", "MESSAGE") filesystemAttachmentInfos := make(filesystemAttachmentInfos, 0, len(infos)) for filesystemId, info := range infos { filesystemAttachmentInfo := filesystemAttachmentInfo{ FilesystemId: filesystemId, FilesystemInfo: info, } if info.Attachments == nil { filesystemAttachmentInfos = append(filesystemAttachmentInfos, filesystemAttachmentInfo) continue } // Each unit attachment must have a corresponding filesystem // attachment. Enumerate each of the filesystem attachments, // and locate the corresponding unit attachment if any. // Each filesystem attachment has at most one corresponding // unit attachment. for machineId, machineInfo := range info.Attachments.Machines { filesystemAttachmentInfo := filesystemAttachmentInfo filesystemAttachmentInfo.MachineId = machineId filesystemAttachmentInfo.MachineFilesystemAttachment = machineInfo for unitId, unitInfo := range info.Attachments.Units { if unitInfo.MachineId == machineId { filesystemAttachmentInfo.UnitId = unitId filesystemAttachmentInfo.UnitStorageAttachment = unitInfo break } } filesystemAttachmentInfos = append(filesystemAttachmentInfos, filesystemAttachmentInfo) } } sort.Sort(filesystemAttachmentInfos) for _, info := range filesystemAttachmentInfos { var size string if info.Size > 0 { size = humanize.IBytes(info.Size * humanize.MiByte) } print( info.MachineId, info.UnitId, info.Storage, info.FilesystemId, info.Volume, info.ProviderFilesystemId, info.MountPoint, size, string(info.Status.Current), info.Status.Message, ) } return tw.Flush() }
// formatMetadataTabular writes a tabular summary of cloud image metadata. func formatMetadataTabular(writer io.Writer, metadata []MetadataInfo) { tw := output.TabWriter(writer) print := func(values ...string) { fmt.Fprintln(tw, strings.Join(values, "\t")) } print("Source", "Series", "Arch", "Region", "Image id", "Stream", "Virt Type", "Storage Type") for _, m := range metadata { print(m.Source, m.Series, m.Arch, m.Region, m.ImageId, m.Stream, m.VirtType, m.RootStorageType) } tw.Flush() }
// formatConfigTabular writes a tabular summary of default config information. func formatDefaultConfigTabular(writer io.Writer, value interface{}) error { defaultValues, ok := value.(config.ModelDefaultAttributes) if !ok { return errors.Errorf("expected value of type %T, got %T", defaultValues, value) } tw := output.TabWriter(writer) w := output.Wrapper{tw} p := func(name string, value config.AttributeDefaultValues) { var c, d interface{} switch value.Default { case nil: d = "-" case "": d = `""` default: d = value.Default } switch value.Controller { case nil: c = "-" case "": c = `""` default: c = value.Controller } w.Println(name, d, c) for _, region := range value.Regions { w.Println(" "+region.Name, region.Value, "-") } } var valueNames []string for name := range defaultValues { valueNames = append(valueNames, name) } sort.Strings(valueNames) w.Println("Attribute", "Default", "Controller") for _, name := range valueNames { info := defaultValues[name] out := &bytes.Buffer{} err := cmd.FormatYaml(out, info) if err != nil { return errors.Annotatef(err, "formatting value for %q", name) } p(name, info) } tw.Flush() return nil }
// formatMetadataTabular writes a tabular summary of cloud image metadata. func formatMetadataTabular(writer io.Writer, metadata []MetadataInfo) { tw := output.TabWriter(writer) print := func(values ...string) { fmt.Fprintln(tw, strings.Join(values, "\t")) } print("SOURCE", "SERIES", "ARCH", "REGION", "IMAGE-ID", "STREAM", "VIRT-TYPE", "STORAGE-TYPE") for _, m := range metadata { print(m.Source, m.Series, m.Arch, m.Region, m.ImageId, m.Stream, m.VirtType, m.RootStorageType) } tw.Flush() }
// FormatMachineTabular writes a tabular summary of machine func FormatMachineTabular(writer io.Writer, forceColor bool, value interface{}) error { fs, valueConverted := value.(formattedMachineStatus) if !valueConverted { return errors.Errorf("expected value of type %T, got %T", fs, value) } tw := output.TabWriter(writer) if forceColor { tw.SetColorCapable(forceColor) } printMachines(tw, fs.Machines) tw.Flush() return nil }
// printTabular prints the list of actions in tabular format func (c *listCommand) printTabular(writer io.Writer, value interface{}) error { list, ok := value.([]listOutput) if !ok { return errors.New("unexpected value") } tw := output.TabWriter(writer) fmt.Fprintf(tw, "%s\t%s\n", "Action", "Description") for _, value := range list { fmt.Fprintf(tw, "%s\t%s\n", value.action, strings.TrimSpace(value.description)) } tw.Flush() return nil }
// formatCloudsTabular writes a tabular summary of cloud information. func formatCloudsTabular(writer io.Writer, value interface{}) error { clouds, ok := value.(*cloudList) if !ok { return errors.Errorf("expected value of type %T, got %T", clouds, value) } tw := output.TabWriter(writer) w := output.Wrapper{tw} w.Println("Cloud", "Regions", "Default", "Type", "Description") w.SetColumnAlignRight(1) cloudNamesSorted := func(someClouds map[string]*cloudDetails) []string { // For tabular we'll sort alphabetically, user clouds last. var names []string for name, _ := range someClouds { names = append(names, name) } sort.Strings(names) return names } printClouds := func(someClouds map[string]*cloudDetails, color *ansiterm.Context) { cloudNames := cloudNamesSorted(someClouds) for _, name := range cloudNames { info := someClouds[name] defaultRegion := "" if len(info.Regions) > 0 { defaultRegion = info.RegionsMap[info.Regions[0].Key.(string)].Name } description := info.CloudDescription if len(description) > 40 { description = description[:39] } w.PrintColor(color, name) w.Println(len(info.Regions), defaultRegion, info.CloudType, description) } } printClouds(clouds.public, nil) printClouds(clouds.builtin, nil) printClouds(clouds.personal, ansiterm.Foreground(ansiterm.BrightBlue)) w.Println("\nTry 'list-regions <cloud>' to see available regions.") w.Println("'show-cloud <cloud>' or 'regions --format yaml <cloud>' can be used to see region endpoints.") w.Println("'add-cloud' can add private clouds or private infrastructure.") w.Println("Update the known public clouds with 'update-clouds'.") tw.Flush() return nil }
func formatWhoAmITabular(writer io.Writer, value interface{}) error { details, ok := value.(whoAmI) if !ok { return errors.Errorf("expected value of type %T, got %T", details, value) } tw := output.TabWriter(writer) fmt.Fprintf(tw, "Controller:\t%s\n", details.ControllerName) modelName := details.ModelName if modelName == "" { modelName = "<no-current-model>" } fmt.Fprintf(tw, "Model:\t%s\n", modelName) fmt.Fprintf(tw, "User:\t%s", details.UserName) return tw.Flush() }
// FormatTabularBlockedModels writes out tabular format for blocked models. // This method is exported as it is also used by destroy-model. func FormatTabularBlockedModels(writer io.Writer, value interface{}) error { models, ok := value.([]modelBlockInfo) if !ok { return errors.Errorf("expected value of type %T, got %T", models, value) } tw := output.TabWriter(writer) w := output.Wrapper{tw} w.Println("Name", "Model UUID", "Owner", "Disabled commands") for _, model := range models { w.Println(model.Name, model.UUID, model.Owner, strings.Join(model.CommandSets, ", ")) } tw.Flush() return nil }
// formatCredentialsTabular writes a tabular summary of cloud information. func formatCredentialsTabular(writer io.Writer, value interface{}) error { credentials, ok := value.(credentialsMap) if !ok { return errors.Errorf("expected value of type %T, got %T", credentials, value) } if len(credentials.Credentials) == 0 { fmt.Fprintln(writer, "No credentials to display.") return nil } // For tabular we'll sort alphabetically by cloud, and then by credential name. var cloudNames []string for name := range credentials.Credentials { cloudNames = append(cloudNames, name) } sort.Strings(cloudNames) tw := output.TabWriter(writer) p := func(values ...string) { text := strings.Join(values, "\t") fmt.Fprintln(tw, text) } p("CLOUD\tCREDENTIALS") for _, cloudName := range cloudNames { var haveDefault bool var credentialNames []string credentials := credentials.Credentials[cloudName] for credentialName := range credentials.Credentials { if credentialName == credentials.DefaultCredential { credentialNames = append([]string{credentialName + "*"}, credentialNames...) haveDefault = true } else { credentialNames = append(credentialNames, credentialName) } } if haveDefault { sort.Strings(credentialNames[1:]) } else { sort.Strings(credentialNames) } p(cloudName, strings.Join(credentialNames, ", ")) } tw.Flush() return nil }
// printTabular prints the list of spaces in tabular format func (c *listCommand) printTabular(writer io.Writer, value interface{}) error { tw := output.TabWriter(writer) if c.Short { list, ok := value.(formattedShortList) if !ok { return errors.New("unexpected value") } fmt.Fprintln(tw, "Space") spaces := list.Spaces sort.Strings(spaces) for _, space := range spaces { fmt.Fprintf(tw, "%v\n", space) } } else { list, ok := value.(formattedList) if !ok { return errors.New("unexpected value") } fmt.Fprintf(tw, "%s\t%s\n", "Space", "Subnets") spaces := []string{} for name, _ := range list.Spaces { spaces = append(spaces, name) } sort.Strings(spaces) for _, name := range spaces { subnets := list.Spaces[name] fmt.Fprintf(tw, "%s", name) if len(subnets) == 0 { fmt.Fprintf(tw, "\n") continue } cidrs := []string{} for subnet, _ := range subnets { cidrs = append(cidrs, subnet) } sort.Strings(cidrs) for _, cidr := range cidrs { fmt.Fprintf(tw, "\t%v\n", cidr) } } } tw.Flush() return nil }
// formatSummary returns a summary of available plans. func formatSummary(writer io.Writer, value interface{}) error { plans, ok := value.([]plan) if !ok { return errors.Errorf("expected value of type %T, got %T", plans, value) } tw := output.TabWriter(writer) p := func(values ...interface{}) { for _, v := range values { fmt.Fprintf(tw, "%s\t", v) } fmt.Fprintln(tw) } p("Plan", "Price") for _, plan := range plans { p(plan.URL, plan.Price) } tw.Flush() return nil }
// formatBlocks writes block list representation. func formatBlocks(writer io.Writer, value interface{}) error { blocks, ok := value.([]BlockInfo) if !ok { return errors.Errorf("expected value of type %T, got %T", blocks, value) } if len(blocks) == 0 { fmt.Fprintf(writer, "No commands are currently disabled.") return nil } tw := output.TabWriter(writer) w := output.Wrapper{tw} w.Println("Disabled commands", "Message") for _, info := range blocks { w.Println(info.Commands, info.Message) } tw.Flush() return nil }
func formatUnitDetailTabular(writer io.Writer, resources FormattedUnitDetails) { // note that the unit resource can be a zero value here, to indicate that // the unit has not downloaded that resource yet. fmt.Fprintln(writer, "[Unit]") sort.Sort(byUnitID(resources)) // To format things into columns. tw := output.TabWriter(writer) // Write the header. fmt.Fprintln(tw, "RESOURCE\tREVISION\tEXPECTED") for _, r := range resources { fmt.Fprintf(tw, "%v\t%v\t%v\n", r.Expected.Name, r.Unit.combinedRevision, r.revProgress, ) } tw.Flush() }
func formatUnitTabular(writer io.Writer, resources []FormattedUnitResource) { // TODO(ericsnow) sort the rows first? fmt.Fprintln(writer, "[Unit]") // To format things into columns. tw := output.TabWriter(writer) // Write the header. // We do not print a section label. fmt.Fprintln(tw, "Resource\tRevision") // Print each info to its own row. for _, r := range resources { // the column headers must be kept in sync with these. fmt.Fprintf(tw, "%v\t%v\n", r.Name, r.combinedRevision, ) } tw.Flush() }
func (c *addModelCommand) unsupportedCloudOrRegionError(cloudClient CloudAPI, defaultCloudTag names.CloudTag) (err error) { clouds, err := cloudClient.Clouds() if err != nil { return errors.Annotate(err, "querying supported clouds") } cloudNames := make([]string, 0, len(clouds)) for tag := range clouds { cloudNames = append(cloudNames, tag.Id()) } sort.Strings(cloudNames) var buf bytes.Buffer tw := output.TabWriter(&buf) fmt.Fprintln(tw, "CLOUD\tREGIONS") for _, cloudName := range cloudNames { cloud := clouds[names.NewCloudTag(cloudName)] regionNames := make([]string, len(cloud.Regions)) for i, region := range cloud.Regions { regionNames[i] = region.Name } fmt.Fprintf(tw, "%s\t%s\n", cloudName, strings.Join(regionNames, ", ")) } tw.Flush() var prefix string if defaultCloudTag != (names.CloudTag{}) { prefix = fmt.Sprintf(` %q is neither a cloud supported by this controller, nor a region in the controller's default cloud %q. The clouds/regions supported by this controller are:`[1:], c.CloudRegion, defaultCloudTag.Id()) } else { prefix = fmt.Sprintf(` %q is not a cloud supported by this controller, and there is no default cloud. The clouds/regions supported by this controller are:`[1:], c.CloudRegion) } return errors.Errorf("%s\n\n%s", prefix, buf.String()) }
func formatServiceTabular(writer io.Writer, info FormattedServiceInfo) { // TODO(ericsnow) sort the rows first? fmt.Fprintln(writer, "[Application]") tw := output.TabWriter(writer) fmt.Fprintln(tw, "Resource\tSupplied by\tRevision") // Print each info to its own row. for _, r := range info.Resources { // the column headers must be kept in sync with these. fmt.Fprintf(tw, "%v\t%v\t%v\n", r.Name, r.combinedOrigin, r.combinedRevision, ) } // Don't forget to flush! The Tab writer won't actually write to the output // until you flush, which would then have its output incorrectly ordered // with the below fmt.Fprintlns. tw.Flush() writeUpdates(info.Updates, writer, tw) }
// formatListTabular writes a tabular summary of storage instances. func formatStorageListTabular(writer io.Writer, storageInfo map[string]StorageInfo) error { tw := output.TabWriter(writer) p := func(values ...interface{}) { for _, v := range values { fmt.Fprintf(tw, "%v\t", v) } fmt.Fprintln(tw) } p("[Storage]") p("Unit\tId\tLocation\tStatus\tMessage") byUnit := make(map[string]map[string]storageAttachmentInfo) for storageId, storageInfo := range storageInfo { if storageInfo.Attachments == nil { byStorage := byUnit[""] if byStorage == nil { byStorage = make(map[string]storageAttachmentInfo) byUnit[""] = byStorage } byStorage[storageId] = storageAttachmentInfo{ storageId: storageId, kind: storageInfo.Kind, persistent: storageInfo.Persistent, status: storageInfo.Status, } continue } for unitId, a := range storageInfo.Attachments.Units { byStorage := byUnit[unitId] if byStorage == nil { byStorage = make(map[string]storageAttachmentInfo) byUnit[unitId] = byStorage } byStorage[storageId] = storageAttachmentInfo{ storageId: storageId, unitId: unitId, kind: storageInfo.Kind, persistent: storageInfo.Persistent, location: a.Location, status: storageInfo.Status, } } } // First sort by units units := make([]string, 0, len(storageInfo)) for unit := range byUnit { units = append(units, unit) } sort.Strings(slashSeparatedIds(units)) for _, unit := range units { // Then sort by storage ids byStorage := byUnit[unit] storageIds := make([]string, 0, len(byStorage)) for storageId := range byStorage { storageIds = append(storageIds, storageId) } sort.Strings(slashSeparatedIds(storageIds)) for _, storageId := range storageIds { info := byStorage[storageId] p(info.unitId, info.storageId, info.location, info.status.Current, info.status.Message) } } tw.Flush() return nil }
// formatControllersTabular returns a tabular summary of controller/model items // sorted by controller name alphabetically. func formatControllersTabular(writer io.Writer, set ControllerSet, promptRefresh bool) error { tw := output.TabWriter(writer) w := output.Wrapper{tw} if promptRefresh && len(set.Controllers) > 0 { fmt.Fprintln(writer, "Use --refresh to see the latest information.") fmt.Fprintln(writer) } w.Println("CONTROLLER", "MODEL", "USER", "ACCESS", "CLOUD/REGION", "MODELS", "MACHINES", "HA", "VERSION") tw.SetColumnAlignRight(5) tw.SetColumnAlignRight(6) tw.SetColumnAlignRight(7) names := []string{} for name := range set.Controllers { names = append(names, name) } sort.Strings(names) for _, name := range names { c := set.Controllers[name] modelName := noValueDisplay if c.ModelName != "" { modelName = c.ModelName } userName := noValueDisplay access := noValueDisplay if c.User != "" { userName = c.User access = notKnownDisplay if c.Access != "" { access = c.Access } } if name == set.CurrentController { name += "*" w.PrintColor(output.CurrentHighlight, name) } else { w.Print(name) } cloudRegion := c.Cloud if c.CloudRegion != "" { cloudRegion += "/" + c.CloudRegion } agentVersion := c.AgentVersion staleVersion := false if agentVersion == "" { agentVersion = notKnownDisplay } else { agentVersionNum, err := version.Parse(agentVersion) staleVersion = err == nil && jujuversion.Current.Compare(agentVersionNum) > 0 } machineCount := noValueDisplay if c.MachineCount != nil && *c.MachineCount > 0 { machineCount = fmt.Sprintf("%d", *c.MachineCount) } modelCount := noValueDisplay if c.ModelCount != nil && *c.ModelCount > 0 { modelCount = fmt.Sprintf("%d", *c.ModelCount) } w.Print(modelName, userName, access, cloudRegion, modelCount, machineCount) controllerMachineInfo, warn := controllerMachineStatus(c.ControllerMachines) if warn { w.PrintColor(output.WarningHighlight, controllerMachineInfo) } else { w.Print(controllerMachineInfo) } if staleVersion { w.PrintColor(output.WarningHighlight, agentVersion) } else { w.Print(agentVersion) } w.Println() } tw.Flush() return nil }