github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/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 }