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]) } }
// 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 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]) } }
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 }
// 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 }
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 }
// 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) w := output.Wrapper{tw} w.Println("Cloud", "Credentials") 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) } w.Println(cloudName, strings.Join(credentialNames, ", ")) } 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 }
// 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 }
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]) } }
// 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 }
// formatTabular takes an interface{} to adhere to the cmd.Formatter interface func (c *modelsCommand) formatTabular(writer io.Writer, value interface{}) error { modelSet, ok := value.(ModelSet) if !ok { return errors.Errorf("expected value of type %T, got %T", modelSet, value) } // We need the tag of the user for which we're listing models, // and for the logged-in user. We use these below when formatting // the model display names. loggedInUser := names.NewUserTag(c.loggedInUser) userForLastConn := loggedInUser var userForListing names.UserTag if c.user != "" { userForListing = names.NewUserTag(c.user) userForLastConn = userForListing } tw := output.TabWriter(writer) w := output.Wrapper{tw} w.Println("Controller: " + c.ControllerName()) w.Println() w.Print("Model") if c.listUUID { w.Print("UUID") } // Only owners, or users with write access or above get to see machines and cores. haveMachineInfo := false for _, m := range modelSet.Models { if haveMachineInfo = len(m.Machines) > 0; haveMachineInfo { break } } if haveMachineInfo { w.Println("Owner", "Status", "Machines", "Cores", "Access", "Last connection") offset := 0 if c.listUUID { offset++ } tw.SetColumnAlignRight(3 + offset) tw.SetColumnAlignRight(4 + offset) } else { w.Println("Owner", "Status", "Access", "Last connection") } for _, model := range modelSet.Models { owner := names.NewUserTag(model.Owner) name := common.OwnerQualifiedModelName(model.Name, owner, userForListing) if jujuclient.JoinOwnerModelName(owner, model.Name) == modelSet.CurrentModelQualified { name += "*" w.PrintColor(output.CurrentHighlight, name) } else { w.Print(name) } if c.listUUID { w.Print(model.UUID) } lastConnection := model.Users[userForLastConn.Id()].LastConnection if lastConnection == "" { lastConnection = "never connected" } userForAccess := loggedInUser if c.user != "" { userForAccess = names.NewUserTag(c.user) } access := model.Users[userForAccess.Id()].Access w.Print(model.Owner, model.Status.Current) if haveMachineInfo { machineInfo := fmt.Sprintf("%d", len(model.Machines)) cores := uint64(0) for _, m := range model.Machines { cores += m.Cores } coresInfo := "-" if cores > 0 { coresInfo = fmt.Sprintf("%d", cores) } w.Print(machineInfo, coresInfo) } w.Println(access, 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 }
// 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 }
// 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 }