github.com/david-imola/snapd@v0.0.0-20210611180407-2de8ddeece6d/daemon/api_interfaces.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015-2020 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 daemon 21 22 import ( 23 "encoding/json" 24 "fmt" 25 "net/http" 26 "sort" 27 "strings" 28 29 "github.com/snapcore/snapd/interfaces" 30 "github.com/snapcore/snapd/overlord/auth" 31 "github.com/snapcore/snapd/overlord/ifacestate" 32 "github.com/snapcore/snapd/overlord/snapstate" 33 "github.com/snapcore/snapd/overlord/state" 34 ) 35 36 var ( 37 interfacesCmd = &Command{ 38 Path: "/v2/interfaces", 39 GET: interfacesConnectionsMultiplexer, 40 POST: changeInterfaces, 41 ReadAccess: openAccess{}, 42 WriteAccess: authenticatedAccess{Polkit: polkitActionManageInterfaces}, 43 } 44 ) 45 46 // interfacesConnectionsMultiplexer multiplexes to either legacy (connection) or modern behavior (interfaces). 47 func interfacesConnectionsMultiplexer(c *Command, r *http.Request, user *auth.UserState) Response { 48 query := r.URL.Query() 49 qselect := query.Get("select") 50 if qselect == "" { 51 return getLegacyConnections(c, r, user) 52 } else { 53 return getInterfaces(c, r, user) 54 } 55 } 56 57 func getInterfaces(c *Command, r *http.Request, user *auth.UserState) Response { 58 // Collect query options from request arguments. 59 q := r.URL.Query() 60 pselect := q.Get("select") 61 if pselect != "all" && pselect != "connected" { 62 return BadRequest("unsupported select qualifier") 63 } 64 var names []string // Interface names 65 namesStr := q.Get("names") 66 if namesStr != "" { 67 names = strings.Split(namesStr, ",") 68 } 69 opts := &interfaces.InfoOptions{ 70 Names: names, 71 Doc: q.Get("doc") == "true", 72 Plugs: q.Get("plugs") == "true", 73 Slots: q.Get("slots") == "true", 74 Connected: pselect == "connected", 75 } 76 // Query the interface repository (this returns []*interface.Info). 77 infos := c.d.overlord.InterfaceManager().Repository().Info(opts) 78 infoJSONs := make([]*interfaceJSON, 0, len(infos)) 79 80 for _, info := range infos { 81 // Convert interfaces.Info into interfaceJSON 82 plugs := make([]*plugJSON, 0, len(info.Plugs)) 83 for _, plug := range info.Plugs { 84 plugs = append(plugs, &plugJSON{ 85 Snap: plug.Snap.InstanceName(), 86 Name: plug.Name, 87 Attrs: plug.Attrs, 88 Label: plug.Label, 89 }) 90 } 91 slots := make([]*slotJSON, 0, len(info.Slots)) 92 for _, slot := range info.Slots { 93 slots = append(slots, &slotJSON{ 94 Snap: slot.Snap.InstanceName(), 95 Name: slot.Name, 96 Attrs: slot.Attrs, 97 Label: slot.Label, 98 }) 99 } 100 infoJSONs = append(infoJSONs, &interfaceJSON{ 101 Name: info.Name, 102 Summary: info.Summary, 103 DocURL: info.DocURL, 104 Plugs: plugs, 105 Slots: slots, 106 }) 107 } 108 return SyncResponse(infoJSONs) 109 } 110 111 func getLegacyConnections(c *Command, r *http.Request, user *auth.UserState) Response { 112 connsjson, err := collectConnections(c.d.overlord.InterfaceManager(), collectFilter{}) 113 if err != nil { 114 return InternalError("collecting connection information failed: %v", err) 115 } 116 legacyconnsjson := legacyConnectionsJSON{ 117 Plugs: connsjson.Plugs, 118 Slots: connsjson.Slots, 119 } 120 return SyncResponse(legacyconnsjson) 121 } 122 123 // changeInterfaces controls the interfaces system. 124 // Plugs can be connected to and disconnected from slots. 125 func changeInterfaces(c *Command, r *http.Request, user *auth.UserState) Response { 126 var a interfaceAction 127 decoder := json.NewDecoder(r.Body) 128 if err := decoder.Decode(&a); err != nil { 129 return BadRequest("cannot decode request body into an interface action: %v", err) 130 } 131 if a.Action == "" { 132 return BadRequest("interface action not specified") 133 } 134 if len(a.Plugs) > 1 || len(a.Slots) > 1 { 135 return NotImplemented("many-to-many operations are not implemented") 136 } 137 if a.Action != "connect" && a.Action != "disconnect" { 138 return BadRequest("unsupported interface action: %q", a.Action) 139 } 140 if len(a.Plugs) == 0 || len(a.Slots) == 0 { 141 return BadRequest("at least one plug and slot is required") 142 } 143 144 var summary string 145 var err error 146 147 var tasksets []*state.TaskSet 148 var affected []string 149 150 st := c.d.overlord.State() 151 st.Lock() 152 defer st.Unlock() 153 154 checkInstalled := func(snapName string) error { 155 // empty snap name is fine, ResolveConnect/ResolveDisconnect handles it. 156 if snapName == "" { 157 return nil 158 } 159 var snapst snapstate.SnapState 160 err := snapstate.Get(st, snapName, &snapst) 161 if (err == nil && !snapst.IsInstalled()) || err == state.ErrNoState { 162 return fmt.Errorf("snap %q is not installed", snapName) 163 } 164 if err == nil { 165 return nil 166 } 167 return fmt.Errorf("internal error: cannot get state of snap %q: %v", snapName, err) 168 } 169 170 for i := range a.Plugs { 171 a.Plugs[i].Snap = ifacestate.RemapSnapFromRequest(a.Plugs[i].Snap) 172 if err := checkInstalled(a.Plugs[i].Snap); err != nil { 173 return errToResponse(err, nil, BadRequest, "%v") 174 } 175 } 176 for i := range a.Slots { 177 a.Slots[i].Snap = ifacestate.RemapSnapFromRequest(a.Slots[i].Snap) 178 if err := checkInstalled(a.Slots[i].Snap); err != nil { 179 return errToResponse(err, nil, BadRequest, "%v") 180 } 181 } 182 183 switch a.Action { 184 case "connect": 185 var connRef *interfaces.ConnRef 186 repo := c.d.overlord.InterfaceManager().Repository() 187 connRef, err = repo.ResolveConnect(a.Plugs[0].Snap, a.Plugs[0].Name, a.Slots[0].Snap, a.Slots[0].Name) 188 if err == nil { 189 var ts *state.TaskSet 190 affected = snapNamesFromConns([]*interfaces.ConnRef{connRef}) 191 summary = fmt.Sprintf("Connect %s:%s to %s:%s", connRef.PlugRef.Snap, connRef.PlugRef.Name, connRef.SlotRef.Snap, connRef.SlotRef.Name) 192 ts, err = ifacestate.Connect(st, connRef.PlugRef.Snap, connRef.PlugRef.Name, connRef.SlotRef.Snap, connRef.SlotRef.Name) 193 if _, ok := err.(*ifacestate.ErrAlreadyConnected); ok { 194 change := newChange(st, a.Action+"-snap", summary, nil, affected) 195 change.SetStatus(state.DoneStatus) 196 return AsyncResponse(nil, change.ID()) 197 } 198 tasksets = append(tasksets, ts) 199 } 200 case "disconnect": 201 var conns []*interfaces.ConnRef 202 summary = fmt.Sprintf("Disconnect %s:%s from %s:%s", a.Plugs[0].Snap, a.Plugs[0].Name, a.Slots[0].Snap, a.Slots[0].Name) 203 conns, err = c.d.overlord.InterfaceManager().ResolveDisconnect(a.Plugs[0].Snap, a.Plugs[0].Name, a.Slots[0].Snap, a.Slots[0].Name, a.Forget) 204 if err == nil { 205 if len(conns) == 0 { 206 return InterfacesUnchanged("nothing to do") 207 } 208 repo := c.d.overlord.InterfaceManager().Repository() 209 for _, connRef := range conns { 210 var ts *state.TaskSet 211 var conn *interfaces.Connection 212 if a.Forget { 213 ts, err = ifacestate.Forget(st, repo, connRef) 214 } else { 215 conn, err = repo.Connection(connRef) 216 if err != nil { 217 break 218 } 219 ts, err = ifacestate.Disconnect(st, conn) 220 if err != nil { 221 break 222 } 223 } 224 if err != nil { 225 break 226 } 227 ts.JoinLane(st.NewLane()) 228 tasksets = append(tasksets, ts) 229 } 230 affected = snapNamesFromConns(conns) 231 } 232 } 233 if err != nil { 234 return errToResponse(err, nil, BadRequest, "%v") 235 } 236 237 change := newChange(st, a.Action+"-snap", summary, tasksets, affected) 238 st.EnsureBefore(0) 239 240 return AsyncResponse(nil, change.ID()) 241 } 242 243 func snapNamesFromConns(conns []*interfaces.ConnRef) []string { 244 m := make(map[string]bool) 245 for _, conn := range conns { 246 m[conn.PlugRef.Snap] = true 247 m[conn.SlotRef.Snap] = true 248 } 249 l := make([]string, 0, len(m)) 250 for name := range m { 251 l = append(l, name) 252 } 253 sort.Strings(l) 254 return l 255 }