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  }