func formatOneline(writer io.Writer, value interface{}, printf onelinePrintf) error { fs, valueConverted := value.(formattedStatus) if !valueConverted { return errors.Errorf("expected value of type %T, got %T", fs, value) } pprint := func(uName string, u unitStatus, level int) { var fmtPorts string if len(u.OpenedPorts) > 0 { fmtPorts = fmt.Sprintf(" %s", strings.Join(u.OpenedPorts, ", ")) } format := indent("\n", level*2, "- %s: %s (%v)"+fmtPorts) printf(writer, format, uName, u, level) } for _, svcName := range utils.SortStringsNaturally(stringKeysFromMap(fs.Applications)) { svc := fs.Applications[svcName] for _, uName := range utils.SortStringsNaturally(stringKeysFromMap(svc.Units)) { unit := svc.Units[uName] pprint(uName, unit, 0) recurseUnits(unit, 1, pprint) } } return nil }
func printMachines(tw *ansiterm.TabWriter, machines map[string]machineStatus) { w := output.Wrapper{tw} w.Println("MACHINE", "STATE", "DNS", "INS-ID", "SERIES", "AZ") for _, name := range utils.SortStringsNaturally(stringKeysFromMap(machines)) { printMachine(w, machines[name]) } }
// FormatSummary writes a summary of the current environment // including the following information: // - Headers: // - All subnets the environment occupies. // - All ports the environment utilizes. // - Sections: // - Machines: Displays total #, and then the # in each state. // - Units: Displays total #, and then # in each state. // - Applications: Displays total #, their names, and how many of each // are exposed. func FormatSummary(writer io.Writer, value interface{}) error { fs, valueConverted := value.(formattedStatus) if !valueConverted { return errors.Errorf("expected value of type %T, got %T", fs, value) } f := newSummaryFormatter(writer) stateToMachine := f.aggregateMachineStates(fs.Machines) svcExposure := f.aggregateServiceAndUnitStates(fs.Applications) p := f.delimitValuesWithTabs // Print everything out p("Running on subnets:", strings.Join(f.netStrings, ", ")) p(" Utilizing ports:", f.portsInColumnsOf(3)) f.tw.Flush() // Right align summary information f.tw.Init(writer, 0, 1, 2, ' ', tabwriter.AlignRight) p("# Machines:", fmt.Sprintf("(%d)", len(fs.Machines))) f.printStateToCount(stateToMachine) p(" ") p("# Units:", fmt.Sprintf("(%d)", f.numUnits)) f.printStateToCount(f.stateToUnit) p(" ") p("# Applications:", fmt.Sprintf("(%d)", len(fs.Applications))) for _, svcName := range utils.SortStringsNaturally(stringKeysFromMap(svcExposure)) { s := svcExposure[svcName] p(svcName, fmt.Sprintf("%d/%d\texposed", s[true], s[true]+s[false])) } f.tw.Flush() return nil }
func checkSort(c *gc.C, expected []string, xform func([]string)) { input := copyStrSlice(expected) xform(input) origInput := copyStrSlice(input) utils.SortStringsNaturally(input) c.Check(input, gc.DeepEquals, expected, gc.Commentf("input was: %#v", origInput)) }
func printMachines(tw *ansiterm.TabWriter, machines map[string]machineStatus) { w := output.Wrapper{tw} w.Println("Machine", "State", "DNS", "Inst id", "Series", "AZ") for _, name := range utils.SortStringsNaturally(stringKeysFromMap(machines)) { printMachine(w, machines[name]) } }
// MinionReports returns details of the reports made by migration // minions to the controller for the current migration phase. func (api *API) MinionReports() (params.MinionReports, error) { var out params.MinionReports mig, err := api.backend.LatestMigration() if err != nil { return out, errors.Trace(err) } reports, err := mig.MinionReports() if err != nil { return out, errors.Trace(err) } out.MigrationId = mig.Id() phase, err := mig.Phase() if err != nil { return out, errors.Trace(err) } out.Phase = phase.String() out.SuccessCount = len(reports.Succeeded) out.Failed = make([]string, len(reports.Failed)) for i := 0; i < len(out.Failed); i++ { out.Failed[i] = reports.Failed[i].String() } utils.SortStringsNaturally(out.Failed) out.UnknownCount = len(reports.Unknown) unknown := make([]string, len(reports.Unknown)) for i := 0; i < len(unknown); i++ { unknown[i] = reports.Unknown[i].String() } utils.SortStringsNaturally(unknown) // Limit the number of unknowns reported numSamples := out.UnknownCount if numSamples > 10 { numSamples = 10 } out.UnknownSample = unknown[:numSamples] return out, nil }
func (f *summaryFormatter) aggregateServiceAndUnitStates(services map[string]applicationStatus) map[string]map[bool]int { svcExposure := make(map[string]map[bool]int) for _, name := range utils.SortStringsNaturally(stringKeysFromMap(services)) { s := services[name] // Grab unit states for _, un := range utils.SortStringsNaturally(stringKeysFromMap(s.Units)) { u := s.Units[un] f.trackUnit(un, u, 0) recurseUnits(u, 1, f.trackUnit) } if _, ok := svcExposure[name]; !ok { svcExposure[name] = make(map[bool]int) } svcExposure[name][s.Exposed]++ } return svcExposure }
// recurseUnits calls the given recurseMap function on the given unit // and its subordinates (recursively defined on the given unit). func recurseUnits(u unitStatus, il int, recurseMap func(string, unitStatus, int)) { if len(u.Subordinates) == 0 { return } for _, uName := range utils.SortStringsNaturally(stringKeysFromMap(u.Subordinates)) { unit := u.Subordinates[uName] recurseMap(uName, unit, il) recurseUnits(unit, il+1, recurseMap) } }
func (st *mockState) AllMachines() (machines []common.Machine, _ error) { // Ensure we get machines in id order. var ids []string for id := range st.machines { ids = append(ids, id) } utils.SortStringsNaturally(ids) for _, id := range ids { machines = append(machines, st.machines[id]) } return machines, nil }
// Run grabs the Actions spec from the api. It then sets up a sensible // output format for the map. func (c *listCommand) Run(ctx *cmd.Context) error { api, err := c.NewActionAPIClient() if err != nil { return err } defer api.Close() actions, err := api.ApplicationCharmActions(params.Entity{c.applicationTag.String()}) if err != nil { return err } if c.fullSchema { verboseSpecs := make(map[string]interface{}) for k, v := range actions { verboseSpecs[k] = v.Params } return c.out.Write(ctx, verboseSpecs) } shortOutput := make(map[string]string) var sortedNames []string for name, action := range actions { shortOutput[name] = action.Description if shortOutput[name] == "" { shortOutput[name] = "No description" } sortedNames = append(sortedNames, name) } utils.SortStringsNaturally(sortedNames) var output interface{} switch c.out.Name() { case "yaml", "json": output = shortOutput default: if len(sortedNames) == 0 { ctx.Infof("No actions defined for %s.", c.applicationTag.Id()) return nil } var list []listOutput for _, name := range sortedNames { list = append(list, listOutput{name, shortOutput[name]}) } output = list } return c.out.Write(ctx, output) }
func (f *summaryFormatter) aggregateMachineStates(machines map[string]machineStatus) map[status.Status]int { stateToMachine := make(map[status.Status]int) for _, name := range utils.SortStringsNaturally(stringKeysFromMap(machines)) { m := machines[name] f.resolveAndTrackIp(m.DNSName) if agentState := m.JujuStatus.Current; agentState == "" { agentState = status.Pending } else { stateToMachine[agentState]++ } } return stateToMachine }
func printMachine(w output.Wrapper, m machineStatus) { // We want to display availability zone so extract from hardware info". hw, err := instance.ParseHardware(m.Hardware) if err != nil { logger.Warningf("invalid hardware info %s for machine %v", m.Hardware, m) } az := "" if hw.AvailabilityZone != nil { az = *hw.AvailabilityZone } w.Print(m.Id) w.PrintStatus(m.JujuStatus.Current) w.Println(m.DNSName, m.InstanceId, m.Series, az) for _, name := range utils.SortStringsNaturally(stringKeysFromMap(m.Containers)) { printMachine(w, m.Containers[name]) } }
// FormatTabular writes a tabular summary of machines, applications, and // units. Any subordinate items are indented by two spaces beneath // their superior. func FormatTabular(writer io.Writer, forceColor bool, value interface{}) error { const maxVersionWidth = 15 const ellipsis = "..." const truncatedWidth = maxVersionWidth - len(ellipsis) fs, valueConverted := value.(formattedStatus) if !valueConverted { return errors.Errorf("expected value of type %T, got %T", fs, value) } // To format things into columns. tw := output.TabWriter(writer) if forceColor { tw.SetColorCapable(forceColor) } w := output.Wrapper{tw} p := w.Println outputHeaders := func(values ...interface{}) { p() p(values...) } cloudRegion := fs.Model.Cloud if fs.Model.CloudRegion != "" { cloudRegion += "/" + fs.Model.CloudRegion } header := []interface{}{"MODEL", "CONTROLLER", "CLOUD/REGION", "VERSION"} values := []interface{}{fs.Model.Name, fs.Model.Controller, cloudRegion, fs.Model.Version} message := getModelMessage(fs.Model) if message != "" { header = append(header, "NOTES") values = append(values, message) } // The first set of headers don't use outputHeaders because it adds the blank line. p(header...) p(values...) units := make(map[string]unitStatus) metering := false relations := newRelationFormatter() outputHeaders("APP", "VERSION", "STATUS", "SCALE", "CHARM", "STORE", "REV", "OS", "NOTES") tw.SetColumnAlignRight(3) tw.SetColumnAlignRight(6) for _, appName := range utils.SortStringsNaturally(stringKeysFromMap(fs.Applications)) { app := fs.Applications[appName] version := app.Version // Don't let a long version push out the version column. if len(version) > maxVersionWidth { version = version[:truncatedWidth] + ellipsis } // Notes may well contain other things later. notes := "" if app.Exposed { notes = "exposed" } w.Print(appName, version) w.PrintStatus(app.StatusInfo.Current) scale, warn := fs.applicationScale(appName) if warn { w.PrintColor(output.WarningHighlight, scale) } else { w.Print(scale) } p(app.CharmName, app.CharmOrigin, app.CharmRev, app.OS, notes) for un, u := range app.Units { units[un] = u if u.MeterStatus != nil { metering = true } } // Ensure that we pick a consistent name for peer relations. sortedRelTypes := make([]string, 0, len(app.Relations)) for relType := range app.Relations { sortedRelTypes = append(sortedRelTypes, relType) } sort.Strings(sortedRelTypes) subs := set.NewStrings(app.SubordinateTo...) for _, relType := range sortedRelTypes { for _, related := range app.Relations[relType] { relations.add(related, appName, relType, subs.Contains(related)) } } } pUnit := func(name string, u unitStatus, level int) { message := u.WorkloadStatusInfo.Message agentDoing := agentDoing(u.JujuStatusInfo) if agentDoing != "" { message = fmt.Sprintf("(%s) %s", agentDoing, message) } w.Print(indent("", level*2, name)) w.PrintStatus(u.WorkloadStatusInfo.Current) w.PrintStatus(u.JujuStatusInfo.Current) p( u.Machine, u.PublicAddress, strings.Join(u.OpenedPorts, ","), message, ) } outputHeaders("UNIT", "WORKLOAD", "AGENT", "MACHINE", "PUBLIC-ADDRESS", "PORTS", "MESSAGE") for _, name := range utils.SortStringsNaturally(stringKeysFromMap(units)) { u := units[name] pUnit(name, u, 0) const indentationLevel = 1 recurseUnits(u, indentationLevel, pUnit) } if metering { outputHeaders("METER", "STATUS", "MESSAGE") for _, name := range utils.SortStringsNaturally(stringKeysFromMap(units)) { u := units[name] if u.MeterStatus != nil { p(name, u.MeterStatus.Color, u.MeterStatus.Message) } } } p() printMachines(tw, fs.Machines) if relations.len() > 0 { outputHeaders("RELATION", "PROVIDES", "CONSUMES", "TYPE") for _, k := range relations.sorted() { r := relations.get(k) if r != nil { p(r.relation, r.application1, r.application2, r.relationType()) } } } tw.Flush() return nil }
func (f *summaryFormatter) printStateToCount(m map[status.Status]int) { for _, stateToCount := range utils.SortStringsNaturally(stringKeysFromMap(m)) { numInStatus := m[status.Status(stateToCount)] f.delimitValuesWithTabs(stateToCount+":", fmt.Sprintf(" %d ", numInStatus)) } }