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