package cmd import ( "encoding/json" "fmt" "os" "text/template" "github.com/greenled/portainer-stack-utils/client" portainer "github.com/portainer/portainer/api" "github.com/sirupsen/logrus" "github.com/greenled/portainer-stack-utils/common" "github.com/spf13/cobra" "github.com/spf13/viper" ) // stackListCmd represents the remove command var stackListCmd = &cobra.Command{ Use: "list", Short: "List stacks", Aliases: []string{"ls"}, Example: ` Print stacks in endpoint with name=primary in a table format: psu stack ls --endpoint primary Print names of stacks in endpoint with name=primary: psu stack ls --endpoint primary --format "{{ .Name }}" Print environment variables of stacks in all endpoints: psu stack ls --format "{{ .Name }}: {{ range .Env }}{{ .Name }}=\"{{ .Value }}\" {{ end }}"`, Run: func(cmd *cobra.Command, args []string) { portainerClient, err := common.GetClient() common.CheckError(err) endpoints, endpointsRetrievalErr := portainerClient.EndpointList() common.CheckError(endpointsRetrievalErr) var endpointSwarmClusterID string var stacks []portainer.Stack if endpointName := viper.GetString("stack.list.endpoint"); endpointName != "" { // Get endpoint by name endpoint, endpointRetrievalErr := common.GetEndpointFromListByName(endpoints, endpointName) common.CheckError(endpointRetrievalErr) logrus.WithFields(logrus.Fields{ "endpoint": endpoint.Name, }).Debug("Getting endpoint's Docker info") var selectionErr error endpointSwarmClusterID, selectionErr = common.GetEndpointSwarmClusterID(endpoint.ID) if selectionErr == nil { // It's a swarm cluster logrus.WithFields(logrus.Fields{ "endpoint": endpoint.Name, }).Debug("Getting stacks") stacks, err = portainerClient.StackList(client.StackListOptions{ Filter: client.StackListFilter{ SwarmID: endpointSwarmClusterID, EndpointID: endpoint.ID, }, }) common.CheckError(err) } else if selectionErr == common.ErrStackClusterNotFound { // It's not a swarm cluster logrus.WithFields(logrus.Fields{ "endpoint": endpoint.Name, }).Debug("Getting stacks") stacks, err = portainerClient.StackList(client.StackListOptions{ Filter: client.StackListFilter{ EndpointID: endpoint.ID, }, }) common.CheckError(err) } else { // Something else happened common.CheckError(selectionErr) } } else { logrus.Debug("Getting stacks") stacks, err = portainerClient.StackList(client.StackListOptions{}) common.CheckError(err) } switch viper.GetString("stack.list.format") { case "table": // Print stacks in a table format writer, err := common.NewTabWriter([]string{ "ID", "NAME", "TYPE", "ENDPOINT", }) common.CheckError(err) for _, s := range stacks { stackEndpoint, err := common.GetEndpointFromListByID(endpoints, s.EndpointID) common.CheckError(err) _, err = fmt.Fprintln(writer, fmt.Sprintf( "%v\t%s\t%v\t%s", s.ID, s.Name, client.GetTranslatedStackType(s.Type), stackEndpoint.Name, )) common.CheckError(err) } flushErr := writer.Flush() common.CheckError(flushErr) case "json": // Print stacks in a json format stacksJSONBytes, err := json.Marshal(stacks) common.CheckError(err) fmt.Println(string(stacksJSONBytes)) default: // Print stacks in a custom format template, templateParsingErr := template.New("stackTpl").Parse(viper.GetString("stack.list.format")) common.CheckError(templateParsingErr) for _, s := range stacks { templateExecutionErr := template.Execute(os.Stdout, s) common.CheckError(templateExecutionErr) fmt.Println() } } }, } func init() { stackCmd.AddCommand(stackListCmd) stackListCmd.Flags().String("endpoint", "", "Filter by endpoint name.") stackListCmd.Flags().String("format", "table", `Output format. Can be "table", "json" or a Go template.`) viper.BindPFlag("stack.list.endpoint", stackListCmd.Flags().Lookup("endpoint")) viper.BindPFlag("stack.list.format", stackListCmd.Flags().Lookup("format")) stackListCmd.SetUsageTemplate(stackListCmd.UsageTemplate() + common.GetFormatHelp(portainer.Stack{})) }