github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/interfaces/connection.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017 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 interfaces
    21  
    22  import (
    23  	"fmt"
    24  	"reflect"
    25  	"strings"
    26  
    27  	"github.com/snapcore/snapd/interfaces/utils"
    28  	"github.com/snapcore/snapd/snap"
    29  )
    30  
    31  // Connection represents a connection between a particular plug and slot.
    32  type Connection struct {
    33  	Plug *ConnectedPlug
    34  	Slot *ConnectedSlot
    35  }
    36  
    37  // ConnectedPlug represents a plug that is connected to a slot.
    38  type ConnectedPlug struct {
    39  	plugInfo     *snap.PlugInfo
    40  	staticAttrs  map[string]interface{}
    41  	dynamicAttrs map[string]interface{}
    42  }
    43  
    44  // ConnectedSlot represents a slot that is connected to a plug.
    45  type ConnectedSlot struct {
    46  	slotInfo     *snap.SlotInfo
    47  	staticAttrs  map[string]interface{}
    48  	dynamicAttrs map[string]interface{}
    49  }
    50  
    51  // Attrer is an interface with Attr getter method common
    52  // to ConnectedSlot, ConnectedPlug, PlugInfo and SlotInfo types.
    53  type Attrer interface {
    54  	// Attr returns attribute value for given path, or an error. Dotted paths are supported.
    55  	Attr(path string, value interface{}) error
    56  	// Lookup returns attribute value for given path, or false. Dotted paths are supported.
    57  	Lookup(path string) (value interface{}, ok bool)
    58  }
    59  
    60  func lookupAttr(staticAttrs map[string]interface{}, dynamicAttrs map[string]interface{}, path string) (interface{}, bool) {
    61  	var v interface{}
    62  	comps := strings.FieldsFunc(path, func(r rune) bool { return r == '.' })
    63  	if len(comps) == 0 {
    64  		return nil, false
    65  	}
    66  	if _, ok := dynamicAttrs[comps[0]]; ok {
    67  		v = dynamicAttrs
    68  	} else {
    69  		v = staticAttrs
    70  	}
    71  
    72  	for _, comp := range comps {
    73  		m, ok := v.(map[string]interface{})
    74  		if !ok {
    75  			return nil, false
    76  		}
    77  		v, ok = m[comp]
    78  		if !ok {
    79  			return nil, false
    80  		}
    81  	}
    82  
    83  	return v, true
    84  }
    85  
    86  func getAttribute(snapName string, ifaceName string, staticAttrs map[string]interface{}, dynamicAttrs map[string]interface{}, path string, val interface{}) error {
    87  	v, ok := lookupAttr(staticAttrs, dynamicAttrs, path)
    88  	if !ok {
    89  		return fmt.Errorf("snap %q does not have attribute %q for interface %q", snapName, path, ifaceName)
    90  	}
    91  
    92  	rt := reflect.TypeOf(val)
    93  	if rt.Kind() != reflect.Ptr || val == nil {
    94  		return fmt.Errorf("internal error: cannot get %q attribute of interface %q with non-pointer value", path, ifaceName)
    95  	}
    96  
    97  	if reflect.TypeOf(v) != rt.Elem() {
    98  		return fmt.Errorf("snap %q has interface %q with invalid value type %T for %q attribute: %T", snapName, ifaceName, v, path, val)
    99  	}
   100  	rv := reflect.ValueOf(val)
   101  	rv.Elem().Set(reflect.ValueOf(v))
   102  	return nil
   103  }
   104  
   105  // NewConnectedSlot creates an object representing a connected slot.
   106  func NewConnectedSlot(slot *snap.SlotInfo, staticAttrs, dynamicAttrs map[string]interface{}) *ConnectedSlot {
   107  	var static map[string]interface{}
   108  	if staticAttrs != nil {
   109  		static = staticAttrs
   110  	} else {
   111  		static = slot.Attrs
   112  	}
   113  	return &ConnectedSlot{
   114  		slotInfo:     slot,
   115  		staticAttrs:  utils.CopyAttributes(static),
   116  		dynamicAttrs: utils.NormalizeInterfaceAttributes(dynamicAttrs).(map[string]interface{}),
   117  	}
   118  }
   119  
   120  // NewConnectedPlug creates an object representing a connected plug.
   121  func NewConnectedPlug(plug *snap.PlugInfo, staticAttrs, dynamicAttrs map[string]interface{}) *ConnectedPlug {
   122  	var static map[string]interface{}
   123  	if staticAttrs != nil {
   124  		static = staticAttrs
   125  	} else {
   126  		static = plug.Attrs
   127  	}
   128  	return &ConnectedPlug{
   129  		plugInfo:     plug,
   130  		staticAttrs:  utils.CopyAttributes(static),
   131  		dynamicAttrs: utils.NormalizeInterfaceAttributes(dynamicAttrs).(map[string]interface{}),
   132  	}
   133  }
   134  
   135  // Interface returns the name of the interface for this plug.
   136  func (plug *ConnectedPlug) Interface() string {
   137  	return plug.plugInfo.Interface
   138  }
   139  
   140  // Name returns the name of this plug.
   141  func (plug *ConnectedPlug) Name() string {
   142  	return plug.plugInfo.Name
   143  }
   144  
   145  // Snap returns the snap Info of this plug.
   146  func (plug *ConnectedPlug) Snap() *snap.Info {
   147  	return plug.plugInfo.Snap
   148  }
   149  
   150  // Apps returns all the apps associated with this plug.
   151  func (plug *ConnectedPlug) Apps() map[string]*snap.AppInfo {
   152  	return plug.plugInfo.Apps
   153  }
   154  
   155  // Hooks returns all the hooks associated with this plug.
   156  func (plug *ConnectedPlug) Hooks() map[string]*snap.HookInfo {
   157  	return plug.plugInfo.Hooks
   158  }
   159  
   160  // SecurityTags returns the security tags for this plug.
   161  func (plug *ConnectedPlug) SecurityTags() []string {
   162  	return plug.plugInfo.SecurityTags()
   163  }
   164  
   165  // StaticAttr returns a static attribute with the given key, or error if attribute doesn't exist.
   166  func (plug *ConnectedPlug) StaticAttr(key string, val interface{}) error {
   167  	return getAttribute(plug.Snap().InstanceName(), plug.Interface(), plug.staticAttrs, nil, key, val)
   168  }
   169  
   170  // StaticAttrs returns all static attributes.
   171  func (plug *ConnectedPlug) StaticAttrs() map[string]interface{} {
   172  	return utils.CopyAttributes(plug.staticAttrs)
   173  }
   174  
   175  // DynamicAttrs returns all dynamic attributes.
   176  func (plug *ConnectedPlug) DynamicAttrs() map[string]interface{} {
   177  	return utils.CopyAttributes(plug.dynamicAttrs)
   178  }
   179  
   180  // Attr returns a dynamic attribute with the given name. It falls back to returning static
   181  // attribute if dynamic one doesn't exist. Error is returned if neither dynamic nor static
   182  // attribute exist.
   183  func (plug *ConnectedPlug) Attr(key string, val interface{}) error {
   184  	return getAttribute(plug.Snap().InstanceName(), plug.Interface(), plug.staticAttrs, plug.dynamicAttrs, key, val)
   185  }
   186  
   187  func (plug *ConnectedPlug) Lookup(path string) (interface{}, bool) {
   188  	return lookupAttr(plug.staticAttrs, plug.dynamicAttrs, path)
   189  }
   190  
   191  // SetAttr sets the given dynamic attribute. Error is returned if the key is already used by a static attribute.
   192  func (plug *ConnectedPlug) SetAttr(key string, value interface{}) error {
   193  	if _, ok := plug.staticAttrs[key]; ok {
   194  		return fmt.Errorf("cannot change attribute %q as it was statically specified in the snap details", key)
   195  	}
   196  	if plug.dynamicAttrs == nil {
   197  		plug.dynamicAttrs = make(map[string]interface{})
   198  	}
   199  	plug.dynamicAttrs[key] = utils.NormalizeInterfaceAttributes(value)
   200  	return nil
   201  }
   202  
   203  // Ref returns the PlugRef for this plug.
   204  func (plug *ConnectedPlug) Ref() *PlugRef {
   205  	return &PlugRef{Snap: plug.Snap().InstanceName(), Name: plug.Name()}
   206  }
   207  
   208  // Interface returns the name of the interface for this slot.
   209  func (slot *ConnectedSlot) Interface() string {
   210  	return slot.slotInfo.Interface
   211  }
   212  
   213  // Name returns the name of this slot.
   214  func (slot *ConnectedSlot) Name() string {
   215  	return slot.slotInfo.Name
   216  }
   217  
   218  // Snap returns the snap Info of this slot.
   219  func (slot *ConnectedSlot) Snap() *snap.Info {
   220  	return slot.slotInfo.Snap
   221  }
   222  
   223  // Apps returns all the apps associated with this slot.
   224  func (slot *ConnectedSlot) Apps() map[string]*snap.AppInfo {
   225  	return slot.slotInfo.Apps
   226  }
   227  
   228  // Hooks returns all the hooks associated with this slot.
   229  func (slot *ConnectedSlot) Hooks() map[string]*snap.HookInfo {
   230  	return slot.slotInfo.Hooks
   231  }
   232  
   233  // SecurityTags returns the security tags for this slot.
   234  func (slot *ConnectedSlot) SecurityTags() []string {
   235  	return slot.slotInfo.SecurityTags()
   236  }
   237  
   238  // StaticAttr returns a static attribute with the given key, or error if attribute doesn't exist.
   239  func (slot *ConnectedSlot) StaticAttr(key string, val interface{}) error {
   240  	return getAttribute(slot.Snap().InstanceName(), slot.Interface(), slot.staticAttrs, nil, key, val)
   241  }
   242  
   243  // StaticAttrs returns all static attributes.
   244  func (slot *ConnectedSlot) StaticAttrs() map[string]interface{} {
   245  	return utils.CopyAttributes(slot.staticAttrs)
   246  }
   247  
   248  // DynamicAttrs returns all dynamic attributes.
   249  func (slot *ConnectedSlot) DynamicAttrs() map[string]interface{} {
   250  	return utils.CopyAttributes(slot.dynamicAttrs)
   251  }
   252  
   253  // Attr returns a dynamic attribute with the given name. It falls back to returning static
   254  // attribute if dynamic one doesn't exist. Error is returned if neither dynamic nor static
   255  // attribute exist.
   256  func (slot *ConnectedSlot) Attr(key string, val interface{}) error {
   257  	return getAttribute(slot.Snap().InstanceName(), slot.Interface(), slot.staticAttrs, slot.dynamicAttrs, key, val)
   258  }
   259  
   260  func (slot *ConnectedSlot) Lookup(path string) (interface{}, bool) {
   261  	return lookupAttr(slot.staticAttrs, slot.dynamicAttrs, path)
   262  }
   263  
   264  // SetAttr sets the given dynamic attribute. Error is returned if the key is already used by a static attribute.
   265  func (slot *ConnectedSlot) SetAttr(key string, value interface{}) error {
   266  	if _, ok := slot.staticAttrs[key]; ok {
   267  		return fmt.Errorf("cannot change attribute %q as it was statically specified in the snap details", key)
   268  	}
   269  	if slot.dynamicAttrs == nil {
   270  		slot.dynamicAttrs = make(map[string]interface{})
   271  	}
   272  	slot.dynamicAttrs[key] = utils.NormalizeInterfaceAttributes(value)
   273  	return nil
   274  }
   275  
   276  // Ref returns the SlotRef for this slot.
   277  func (slot *ConnectedSlot) Ref() *SlotRef {
   278  	return &SlotRef{Snap: slot.Snap().InstanceName(), Name: slot.Name()}
   279  }
   280  
   281  // Interface returns the name of the interface for this connection.
   282  func (conn *Connection) Interface() string {
   283  	return conn.Plug.plugInfo.Interface
   284  }