github.com/rigado/snapd@v2.42.5-go-mod+incompatible/cmd/snap/cmd_connections.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2018 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package main
    21  
    22  import (
    23  	"fmt"
    24  	"sort"
    25  	"strings"
    26  
    27  	"github.com/jessevdk/go-flags"
    28  
    29  	"github.com/snapcore/snapd/client"
    30  	"github.com/snapcore/snapd/i18n"
    31  )
    32  
    33  type cmdConnections struct {
    34  	clientMixin
    35  	All         bool `long:"all"`
    36  	Positionals struct {
    37  		Snap installedSnapName
    38  	} `positional-args:"true"`
    39  }
    40  
    41  var shortConnectionsHelp = i18n.G("List interface connections")
    42  var longConnectionsHelp = i18n.G(`
    43  The connections command lists connections between plugs and slots
    44  in the system.
    45  
    46  Unless <snap> is provided, the listing is for connected plugs and
    47  slots for all snaps in the system. In this mode, pass --all to also
    48  list unconnected plugs and slots.
    49  
    50  $ snap connections <snap>
    51  
    52  Lists connected and unconnected plugs and slots for the specified
    53  snap.
    54  `)
    55  
    56  func init() {
    57  	addCommand("connections", shortConnectionsHelp, longConnectionsHelp, func() flags.Commander {
    58  		return &cmdConnections{}
    59  	}, map[string]string{
    60  		"all": i18n.G("Show connected and unconnected plugs and slots"),
    61  	}, []argDesc{{
    62  		// TRANSLATORS: This needs to be wrapped in <>s.
    63  		name: "<snap>",
    64  		// TRANSLATORS: This should not start with a lowercase letter.
    65  		desc: i18n.G("Constrain listing to a specific snap"),
    66  	}})
    67  }
    68  
    69  func isSystemSnap(snap string) bool {
    70  	return snap == "core" || snap == "snapd" || snap == "system"
    71  }
    72  
    73  func endpoint(snap, name string) string {
    74  	if isSystemSnap(snap) {
    75  		return ":" + name
    76  	}
    77  	return snap + ":" + name
    78  }
    79  
    80  type connection struct {
    81  	slot                 string
    82  	plug                 string
    83  	interfaceName        string
    84  	interfaceDeterminant string
    85  	manual               bool
    86  	gadget               bool
    87  }
    88  
    89  func (cn connection) String() string {
    90  	opts := []string{}
    91  	if cn.manual {
    92  		opts = append(opts, "manual")
    93  	}
    94  	if cn.gadget {
    95  		opts = append(opts, "gadget")
    96  	}
    97  	if len(opts) == 0 {
    98  		return "-"
    99  	}
   100  	return strings.Join(opts, ",")
   101  }
   102  
   103  type byConnectionData []connection
   104  
   105  func (b byConnectionData) Len() int      { return len(b) }
   106  func (b byConnectionData) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
   107  func (b byConnectionData) Less(i, j int) bool {
   108  	iCon, jCon := b[i], b[j]
   109  	if iCon.interfaceName != jCon.interfaceName {
   110  		return iCon.interfaceName < jCon.interfaceName
   111  	}
   112  	if iCon.plug != jCon.plug {
   113  		return iCon.plug < jCon.plug
   114  	}
   115  	return iCon.slot < jCon.slot
   116  }
   117  
   118  func interfaceDeterminant(conn *client.Connection) string {
   119  	var value string
   120  
   121  	switch conn.Interface {
   122  	case "content":
   123  		value, _ = conn.PlugAttrs["content"].(string)
   124  		if value == "" {
   125  			value, _ = conn.SlotAttrs["content"].(string)
   126  		}
   127  	}
   128  	if value == "" {
   129  		return ""
   130  	}
   131  	return fmt.Sprintf("[%v]", value)
   132  }
   133  
   134  func (x *cmdConnections) Execute(args []string) error {
   135  	if len(args) > 0 {
   136  		return ErrExtraArgs
   137  	}
   138  
   139  	opts := client.ConnectionOptions{
   140  		All: x.All,
   141  	}
   142  	wanted := string(x.Positionals.Snap)
   143  	if wanted != "" {
   144  		if x.All {
   145  			// passing a snap name already implies --all, error out
   146  			// when it was passed explicitly
   147  			return fmt.Errorf(i18n.G("cannot use --all with snap name"))
   148  		}
   149  		// when asking for a single snap, include its disconnected plugs
   150  		// and slots
   151  		opts.Snap = wanted
   152  		opts.All = true
   153  		// print all slots
   154  		x.All = true
   155  	}
   156  
   157  	connections, err := x.client.Connections(&opts)
   158  	if err != nil {
   159  		return err
   160  	}
   161  	if len(connections.Plugs) == 0 && len(connections.Slots) == 0 {
   162  		return nil
   163  	}
   164  
   165  	annotatedConns := make([]connection, 0, len(connections.Established)+len(connections.Undesired))
   166  	for _, conn := range connections.Established {
   167  		annotatedConns = append(annotatedConns, connection{
   168  			plug:                 endpoint(conn.Plug.Snap, conn.Plug.Name),
   169  			slot:                 endpoint(conn.Slot.Snap, conn.Slot.Name),
   170  			manual:               conn.Manual,
   171  			gadget:               conn.Gadget,
   172  			interfaceName:        conn.Interface,
   173  			interfaceDeterminant: interfaceDeterminant(&conn),
   174  		})
   175  	}
   176  
   177  	w := tabWriter()
   178  	fmt.Fprintln(w, i18n.G("Interface\tPlug\tSlot\tNotes"))
   179  
   180  	for _, plug := range connections.Plugs {
   181  		if len(plug.Connections) == 0 && x.All {
   182  			annotatedConns = append(annotatedConns, connection{
   183  				plug:          endpoint(plug.Snap, plug.Name),
   184  				slot:          "-",
   185  				interfaceName: plug.Interface,
   186  			})
   187  		}
   188  	}
   189  	for _, slot := range connections.Slots {
   190  		if !isSystemSnap(wanted) && isSystemSnap(slot.Snap) {
   191  			// displaying unconnected system snap slots is boring,
   192  			// unless explicitly asked to show them
   193  			continue
   194  		}
   195  		if len(slot.Connections) == 0 && x.All {
   196  			annotatedConns = append(annotatedConns, connection{
   197  				plug:          "-",
   198  				slot:          endpoint(slot.Snap, slot.Name),
   199  				interfaceName: slot.Interface,
   200  			})
   201  		}
   202  	}
   203  
   204  	sort.Sort(byConnectionData(annotatedConns))
   205  
   206  	for _, note := range annotatedConns {
   207  		fmt.Fprintf(w, "%s%s\t%s\t%s\t%s\n", note.interfaceName, note.interfaceDeterminant, note.plug, note.slot, note)
   208  	}
   209  
   210  	if len(annotatedConns) > 0 {
   211  		w.Flush()
   212  	}
   213  	return nil
   214  }