gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/interfaces/core.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 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  	"regexp"
    25  	"strings"
    26  
    27  	"github.com/snapcore/snapd/snap"
    28  )
    29  
    30  // BeforePreparePlug sanitizes a plug with a given snapd interface.
    31  func BeforePreparePlug(iface Interface, plugInfo *snap.PlugInfo) error {
    32  	if iface.Name() != plugInfo.Interface {
    33  		return fmt.Errorf("cannot sanitize plug %q (interface %q) using interface %q",
    34  			PlugRef{Snap: plugInfo.Snap.InstanceName(), Name: plugInfo.Name}, plugInfo.Interface, iface.Name())
    35  	}
    36  	var err error
    37  	if iface, ok := iface.(PlugSanitizer); ok {
    38  		err = iface.BeforePreparePlug(plugInfo)
    39  	}
    40  	return err
    41  }
    42  
    43  // ByName returns an Interface for the given interface name. Note that in order for
    44  // this to work properly, the package "interfaces/builtin" must also eventually be
    45  // imported to populate the full list of interfaces.
    46  var ByName = func(name string) (iface Interface, err error) {
    47  	panic("ByName is unset, import interfaces/builtin to initialize this")
    48  }
    49  
    50  // PlugRef is a reference to a plug.
    51  type PlugRef struct {
    52  	Snap string `json:"snap"`
    53  	Name string `json:"plug"`
    54  }
    55  
    56  // String returns the "snap:plug" representation of a plug reference.
    57  func (ref PlugRef) String() string {
    58  	return fmt.Sprintf("%s:%s", ref.Snap, ref.Name)
    59  }
    60  
    61  // SortsBefore returns true when plug should be sorted before the other
    62  func (ref PlugRef) SortsBefore(other PlugRef) bool {
    63  	if ref.Snap != other.Snap {
    64  		return ref.Snap < other.Snap
    65  	}
    66  	return ref.Name < other.Name
    67  }
    68  
    69  // Sanitize slot with a given snapd interface.
    70  func BeforePrepareSlot(iface Interface, slotInfo *snap.SlotInfo) error {
    71  	if iface.Name() != slotInfo.Interface {
    72  		return fmt.Errorf("cannot sanitize slot %q (interface %q) using interface %q",
    73  			SlotRef{Snap: slotInfo.Snap.InstanceName(), Name: slotInfo.Name}, slotInfo.Interface, iface.Name())
    74  	}
    75  	var err error
    76  	if iface, ok := iface.(SlotSanitizer); ok {
    77  		err = iface.BeforePrepareSlot(slotInfo)
    78  	}
    79  	return err
    80  }
    81  
    82  // SlotRef is a reference to a slot.
    83  type SlotRef struct {
    84  	Snap string `json:"snap"`
    85  	Name string `json:"slot"`
    86  }
    87  
    88  // String returns the "snap:slot" representation of a slot reference.
    89  func (ref SlotRef) String() string {
    90  	return fmt.Sprintf("%s:%s", ref.Snap, ref.Name)
    91  }
    92  
    93  // SortsBefore returns true when slot should be sorted before the other
    94  func (ref SlotRef) SortsBefore(other SlotRef) bool {
    95  	if ref.Snap != other.Snap {
    96  		return ref.Snap < other.Snap
    97  	}
    98  	return ref.Name < other.Name
    99  }
   100  
   101  // Interfaces holds information about a list of plugs, slots and their connections.
   102  type Interfaces struct {
   103  	Plugs       []*snap.PlugInfo
   104  	Slots       []*snap.SlotInfo
   105  	Connections []*ConnRef
   106  }
   107  
   108  // Info holds information about a given interface and its instances.
   109  type Info struct {
   110  	Name    string
   111  	Summary string
   112  	DocURL  string
   113  	Plugs   []*snap.PlugInfo
   114  	Slots   []*snap.SlotInfo
   115  }
   116  
   117  // ConnRef holds information about plug and slot reference that form a particular connection.
   118  type ConnRef struct {
   119  	PlugRef PlugRef
   120  	SlotRef SlotRef
   121  }
   122  
   123  // NewConnRef creates a connection reference for given plug and slot
   124  func NewConnRef(plug *snap.PlugInfo, slot *snap.SlotInfo) *ConnRef {
   125  	return &ConnRef{
   126  		PlugRef: PlugRef{Snap: plug.Snap.InstanceName(), Name: plug.Name},
   127  		SlotRef: SlotRef{Snap: slot.Snap.InstanceName(), Name: slot.Name},
   128  	}
   129  }
   130  
   131  // ID returns a string identifying a given connection.
   132  func (conn *ConnRef) ID() string {
   133  	return fmt.Sprintf("%s:%s %s:%s", conn.PlugRef.Snap, conn.PlugRef.Name, conn.SlotRef.Snap, conn.SlotRef.Name)
   134  }
   135  
   136  // SortsBefore returns true when connection should be sorted before the other
   137  func (conn *ConnRef) SortsBefore(other *ConnRef) bool {
   138  	if conn.PlugRef != other.PlugRef {
   139  		return conn.PlugRef.SortsBefore(other.PlugRef)
   140  	}
   141  	return conn.SlotRef.SortsBefore(other.SlotRef)
   142  }
   143  
   144  // ParseConnRef parses an ID string
   145  func ParseConnRef(id string) (*ConnRef, error) {
   146  	var conn ConnRef
   147  	parts := strings.SplitN(id, " ", 2)
   148  	if len(parts) != 2 {
   149  		return nil, fmt.Errorf("malformed connection identifier: %q", id)
   150  	}
   151  	plugParts := strings.Split(parts[0], ":")
   152  	slotParts := strings.Split(parts[1], ":")
   153  	if len(plugParts) != 2 || len(slotParts) != 2 {
   154  		return nil, fmt.Errorf("malformed connection identifier: %q", id)
   155  	}
   156  	conn.PlugRef.Snap = plugParts[0]
   157  	conn.PlugRef.Name = plugParts[1]
   158  	conn.SlotRef.Snap = slotParts[0]
   159  	conn.SlotRef.Name = slotParts[1]
   160  	return &conn, nil
   161  }
   162  
   163  // Interface describes a group of interchangeable capabilities with common features.
   164  // Interfaces act as a contract between system builders, application developers
   165  // and end users.
   166  type Interface interface {
   167  	// Unique and public name of this interface.
   168  	Name() string
   169  
   170  	// AutoConnect returns whether plug and slot should be
   171  	// implicitly auto-connected assuming they will be an
   172  	// unambiguous connection candidate and declaration-based checks
   173  	// allow.
   174  	AutoConnect(plug *snap.PlugInfo, slot *snap.SlotInfo) bool
   175  }
   176  
   177  // PlugSanitizer can be implemented by Interfaces that have reasons to sanitize their plugs.
   178  type PlugSanitizer interface {
   179  	BeforePreparePlug(plug *snap.PlugInfo) error
   180  }
   181  
   182  // SlotSanitizer can be implemented by Interfaces that have reasons to sanitize their slots.
   183  type SlotSanitizer interface {
   184  	BeforePrepareSlot(slot *snap.SlotInfo) error
   185  }
   186  
   187  // StaticInfo describes various static-info of a given interface.
   188  //
   189  // The Summary must be a one-line string of length suitable for listing views.
   190  // The DocsURL can point to website (e.g. a forum thread) that goes into more
   191  // depth and documents the interface in detail.
   192  type StaticInfo struct {
   193  	Summary string `json:"summary,omitempty"`
   194  	DocURL  string `json:"doc-url,omitempty"`
   195  
   196  	// ImplicitOnCore controls if a slot is automatically added to core (non-classic) systems.
   197  	ImplicitOnCore bool `json:"implicit-on-core,omitempty"`
   198  	// ImplicitOnClassic controls if a slot is automatically added to classic systems.
   199  	ImplicitOnClassic bool `json:"implicit-on-classic,omitempty"`
   200  
   201  	// AffectsPlugOnRefresh tells if refreshing of a snap with a slot of this interface
   202  	// is disruptive for the snap on the plug side (when the interface is connected),
   203  	// meaning that a refresh of the slot-side affects snap(s) on the plug side
   204  	// due to e.g. namespace changes which require freezing and thawing of the
   205  	// running processes. This flag is consulted when computing snaps affected
   206  	// by refresh for auto-refresh gating with gate-auto-refresh hooks.
   207  	// TODO: if we change the snap-update-ns logic to avoid the freezeing/thawing
   208  	// if there are no changes, there are interfaces like appstream-metadata or
   209  	// system-packages-doc that could get the flag set back to false.
   210  	AffectsPlugOnRefresh bool `json:"affects-plug-on-refresh,omitempty"`
   211  
   212  	// BaseDeclarationPlugs defines an optional extension to the base-declaration assertion relevant for this interface.
   213  	BaseDeclarationPlugs string
   214  	// BaseDeclarationSlots defines an optional extension to the base-declaration assertion relevant for this interface.
   215  	BaseDeclarationSlots string
   216  }
   217  
   218  // PermanentPlugServiceSnippets will return the set of snippets for the systemd
   219  // service unit that should be generated for a snap with the specified plug.
   220  // The list returned is not unique, callers must de-duplicate themselves.
   221  // The plug is provided because the snippet may depend on plug attributes for
   222  // example. The plug is sanitized before the snippets are returned.
   223  func PermanentPlugServiceSnippets(iface Interface, plug *snap.PlugInfo) (snips []string, err error) {
   224  	// sanitize the plug first
   225  	err = BeforePreparePlug(iface, plug)
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  
   230  	type serviceSnippetPlugger interface {
   231  		ServicePermanentPlug(plug *snap.PlugInfo) []string
   232  	}
   233  	if iface, ok := iface.(serviceSnippetPlugger); ok {
   234  		snips = iface.ServicePermanentPlug(plug)
   235  	}
   236  	return snips, nil
   237  }
   238  
   239  // StaticInfoOf returns the static-info of the given interface.
   240  func StaticInfoOf(iface Interface) (si StaticInfo) {
   241  	type metaDataProvider interface {
   242  		StaticInfo() StaticInfo
   243  	}
   244  	if iface, ok := iface.(metaDataProvider); ok {
   245  		si = iface.StaticInfo()
   246  	}
   247  	return si
   248  }
   249  
   250  // Specification describes interactions between backends and interfaces.
   251  type Specification interface {
   252  	// AddPermanentSlot records side-effects of having a slot.
   253  	AddPermanentSlot(iface Interface, slot *snap.SlotInfo) error
   254  	// AddPermanentPlug records side-effects of having a plug.
   255  	AddPermanentPlug(iface Interface, plug *snap.PlugInfo) error
   256  	// AddConnectedSlot records side-effects of having a connected slot.
   257  	AddConnectedSlot(iface Interface, plug *ConnectedPlug, slot *ConnectedSlot) error
   258  	// AddConnectedPlug records side-effects of having a connected plug.
   259  	AddConnectedPlug(iface Interface, plug *ConnectedPlug, slot *ConnectedSlot) error
   260  }
   261  
   262  // SecuritySystem is a name of a security system.
   263  type SecuritySystem string
   264  
   265  const (
   266  	// SecurityAppArmor identifies the apparmor security system.
   267  	SecurityAppArmor SecuritySystem = "apparmor"
   268  	// SecuritySecComp identifies the seccomp security system.
   269  	SecuritySecComp SecuritySystem = "seccomp"
   270  	// SecurityDBus identifies the DBus security system.
   271  	SecurityDBus SecuritySystem = "dbus"
   272  	// SecurityUDev identifies the UDev security system.
   273  	SecurityUDev SecuritySystem = "udev"
   274  	// SecurityMount identifies the mount security system.
   275  	SecurityMount SecuritySystem = "mount"
   276  	// SecurityKMod identifies the kernel modules security system.
   277  	SecurityKMod SecuritySystem = "kmod"
   278  	// SecuritySystemd identifies the systemd services security system.
   279  	SecuritySystemd SecuritySystem = "systemd"
   280  )
   281  
   282  var isValidBusName = regexp.MustCompile(`^[a-zA-Z_-][a-zA-Z0-9_-]*(\.[a-zA-Z_-][a-zA-Z0-9_-]*)+$`).MatchString
   283  
   284  // ValidateDBusBusName checks if a string conforms to
   285  // https://dbus.freedesktop.org/doc/dbus-specification.html#message-protocol-names
   286  func ValidateDBusBusName(busName string) error {
   287  	if len(busName) == 0 {
   288  		return fmt.Errorf("DBus bus name must be set")
   289  	} else if len(busName) > 255 {
   290  		return fmt.Errorf("DBus bus name is too long (must be <= 255)")
   291  	}
   292  
   293  	if !isValidBusName(busName) {
   294  		return fmt.Errorf("invalid DBus bus name: %q", busName)
   295  	}
   296  	return nil
   297  }