github.com/tompreston/snapd@v0.0.0-20210817193607-954edfcb9611/snap/info_snap_yaml.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2014-2021 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 snap
    21  
    22  import (
    23  	"fmt"
    24  	"os"
    25  	"sort"
    26  	"strconv"
    27  	"strings"
    28  
    29  	"gopkg.in/yaml.v2"
    30  
    31  	"github.com/snapcore/snapd/metautil"
    32  	"github.com/snapcore/snapd/strutil"
    33  	"github.com/snapcore/snapd/timeout"
    34  )
    35  
    36  type snapYaml struct {
    37  	Name            string                 `yaml:"name"`
    38  	Version         string                 `yaml:"version"`
    39  	Type            Type                   `yaml:"type"`
    40  	Architectures   []string               `yaml:"architectures,omitempty"`
    41  	Assumes         []string               `yaml:"assumes"`
    42  	Title           string                 `yaml:"title"`
    43  	Description     string                 `yaml:"description"`
    44  	Summary         string                 `yaml:"summary"`
    45  	License         string                 `yaml:"license,omitempty"`
    46  	Epoch           Epoch                  `yaml:"epoch,omitempty"`
    47  	Base            string                 `yaml:"base,omitempty"`
    48  	Confinement     ConfinementType        `yaml:"confinement,omitempty"`
    49  	Environment     strutil.OrderedMap     `yaml:"environment,omitempty"`
    50  	Plugs           map[string]interface{} `yaml:"plugs,omitempty"`
    51  	Slots           map[string]interface{} `yaml:"slots,omitempty"`
    52  	Apps            map[string]appYaml     `yaml:"apps,omitempty"`
    53  	Hooks           map[string]hookYaml    `yaml:"hooks,omitempty"`
    54  	Layout          map[string]layoutYaml  `yaml:"layout,omitempty"`
    55  	SystemUsernames map[string]interface{} `yaml:"system-usernames,omitempty"`
    56  	Links           map[string][]string    `yaml:"links,omitempty"`
    57  
    58  	// TypoLayouts is used to detect the use of the incorrect plural form of "layout"
    59  	TypoLayouts typoDetector `yaml:"layouts,omitempty"`
    60  }
    61  
    62  type typoDetector struct {
    63  	Hint string
    64  }
    65  
    66  func (td *typoDetector) UnmarshalYAML(func(interface{}) error) error {
    67  	return fmt.Errorf("typo detected: %s", td.Hint)
    68  }
    69  
    70  type appYaml struct {
    71  	Aliases []string `yaml:"aliases,omitempty"`
    72  
    73  	Command      string   `yaml:"command"`
    74  	CommandChain []string `yaml:"command-chain,omitempty"`
    75  
    76  	Daemon      string      `yaml:"daemon"`
    77  	DaemonScope DaemonScope `yaml:"daemon-scope"`
    78  
    79  	StopCommand     string          `yaml:"stop-command,omitempty"`
    80  	ReloadCommand   string          `yaml:"reload-command,omitempty"`
    81  	PostStopCommand string          `yaml:"post-stop-command,omitempty"`
    82  	StopTimeout     timeout.Timeout `yaml:"stop-timeout,omitempty"`
    83  	StartTimeout    timeout.Timeout `yaml:"start-timeout,omitempty"`
    84  	WatchdogTimeout timeout.Timeout `yaml:"watchdog-timeout,omitempty"`
    85  	Completer       string          `yaml:"completer,omitempty"`
    86  	RefreshMode     string          `yaml:"refresh-mode,omitempty"`
    87  	StopMode        StopModeType    `yaml:"stop-mode,omitempty"`
    88  	InstallMode     string          `yaml:"install-mode,omitempty"`
    89  
    90  	RestartCond  RestartCondition `yaml:"restart-condition,omitempty"`
    91  	RestartDelay timeout.Timeout  `yaml:"restart-delay,omitempty"`
    92  	SlotNames    []string         `yaml:"slots,omitempty"`
    93  	PlugNames    []string         `yaml:"plugs,omitempty"`
    94  
    95  	BusName     string   `yaml:"bus-name,omitempty"`
    96  	ActivatesOn []string `yaml:"activates-on,omitempty"`
    97  	CommonID    string   `yaml:"common-id,omitempty"`
    98  
    99  	Environment strutil.OrderedMap `yaml:"environment,omitempty"`
   100  
   101  	Sockets map[string]socketsYaml `yaml:"sockets,omitempty"`
   102  
   103  	After  []string `yaml:"after,omitempty"`
   104  	Before []string `yaml:"before,omitempty"`
   105  
   106  	Timer string `yaml:"timer,omitempty"`
   107  
   108  	Autostart string `yaml:"autostart,omitempty"`
   109  }
   110  
   111  type hookYaml struct {
   112  	PlugNames    []string           `yaml:"plugs,omitempty"`
   113  	SlotNames    []string           `yaml:"slots,omitempty"`
   114  	Environment  strutil.OrderedMap `yaml:"environment,omitempty"`
   115  	CommandChain []string           `yaml:"command-chain,omitempty"`
   116  }
   117  
   118  type layoutYaml struct {
   119  	Bind     string `yaml:"bind,omitempty"`
   120  	BindFile string `yaml:"bind-file,omitempty"`
   121  	Type     string `yaml:"type,omitempty"`
   122  	User     string `yaml:"user,omitempty"`
   123  	Group    string `yaml:"group,omitempty"`
   124  	Mode     string `yaml:"mode,omitempty"`
   125  	Symlink  string `yaml:"symlink,omitempty"`
   126  }
   127  
   128  type socketsYaml struct {
   129  	ListenStream string      `yaml:"listen-stream,omitempty"`
   130  	SocketMode   os.FileMode `yaml:"socket-mode,omitempty"`
   131  }
   132  
   133  // InfoFromSnapYaml creates a new info based on the given snap.yaml data
   134  func InfoFromSnapYaml(yamlData []byte) (*Info, error) {
   135  	return infoFromSnapYaml(yamlData, new(scopedTracker))
   136  }
   137  
   138  // scopedTracker helps keeping track of which slots/plugs are scoped
   139  // to apps and hooks.
   140  type scopedTracker struct {
   141  	plugs map[*PlugInfo]bool
   142  	slots map[*SlotInfo]bool
   143  }
   144  
   145  func (strk *scopedTracker) init(sizeGuess int) {
   146  	strk.plugs = make(map[*PlugInfo]bool, sizeGuess)
   147  	strk.slots = make(map[*SlotInfo]bool, sizeGuess)
   148  }
   149  
   150  func (strk *scopedTracker) markPlug(plug *PlugInfo) {
   151  	strk.plugs[plug] = true
   152  }
   153  
   154  func (strk *scopedTracker) markSlot(slot *SlotInfo) {
   155  	strk.slots[slot] = true
   156  }
   157  
   158  func (strk *scopedTracker) plug(plug *PlugInfo) bool {
   159  	return strk.plugs[plug]
   160  }
   161  
   162  func (strk *scopedTracker) slot(slot *SlotInfo) bool {
   163  	return strk.slots[slot]
   164  }
   165  
   166  func infoFromSnapYaml(yamlData []byte, strk *scopedTracker) (*Info, error) {
   167  	var y snapYaml
   168  	// Customize hints for the typo detector.
   169  	y.TypoLayouts.Hint = `use singular "layout" instead of plural "layouts"`
   170  	err := yaml.Unmarshal(yamlData, &y)
   171  	if err != nil {
   172  		return nil, fmt.Errorf("cannot parse snap.yaml: %s", err)
   173  	}
   174  
   175  	snap := infoSkeletonFromSnapYaml(y)
   176  
   177  	// Collect top-level definitions of plugs and slots
   178  	if err := setPlugsFromSnapYaml(y, snap); err != nil {
   179  		return nil, err
   180  	}
   181  	if err := setSlotsFromSnapYaml(y, snap); err != nil {
   182  		return nil, err
   183  	}
   184  
   185  	strk.init(len(y.Apps) + len(y.Hooks))
   186  
   187  	// Collect all apps, their aliases and hooks
   188  	if err := setAppsFromSnapYaml(y, snap, strk); err != nil {
   189  		return nil, err
   190  	}
   191  	setHooksFromSnapYaml(y, snap, strk)
   192  
   193  	// Bind plugs and slots that are not scoped to all known apps and hooks.
   194  	bindUnscopedPlugs(snap, strk)
   195  	bindUnscopedSlots(snap, strk)
   196  
   197  	// Collect layout elements.
   198  	if y.Layout != nil {
   199  		snap.Layout = make(map[string]*Layout, len(y.Layout))
   200  		for path, l := range y.Layout {
   201  			var mode os.FileMode = 0755
   202  			if l.Mode != "" {
   203  				m, err := strconv.ParseUint(l.Mode, 8, 32)
   204  				if err != nil {
   205  					return nil, err
   206  				}
   207  				mode = os.FileMode(m)
   208  			}
   209  			user := "root"
   210  			if l.User != "" {
   211  				user = l.User
   212  			}
   213  			group := "root"
   214  			if l.Group != "" {
   215  				group = l.Group
   216  			}
   217  			snap.Layout[path] = &Layout{
   218  				Snap: snap, Path: path,
   219  				Bind: l.Bind, Type: l.Type, Symlink: l.Symlink, BindFile: l.BindFile,
   220  				User: user, Group: group, Mode: mode,
   221  			}
   222  		}
   223  	}
   224  
   225  	// Rename specific plugs on the core snap.
   226  	snap.renameClashingCorePlugs()
   227  
   228  	snap.BadInterfaces = make(map[string]string)
   229  	SanitizePlugsSlots(snap)
   230  
   231  	// Collect system usernames
   232  	if err := setSystemUsernamesFromSnapYaml(y, snap); err != nil {
   233  		return nil, err
   234  	}
   235  
   236  	if err := setLinksFromSnapYaml(y, snap); err != nil {
   237  		return nil, err
   238  	}
   239  
   240  	// FIXME: validation of the fields
   241  	return snap, nil
   242  }
   243  
   244  // infoSkeletonFromSnapYaml initializes an Info without apps, hook, plugs, or
   245  // slots
   246  func infoSkeletonFromSnapYaml(y snapYaml) *Info {
   247  	// Prepare defaults
   248  	architectures := []string{"all"}
   249  	if len(y.Architectures) != 0 {
   250  		architectures = y.Architectures
   251  	}
   252  
   253  	typ := TypeApp
   254  	if y.Type != "" {
   255  		typ = y.Type
   256  	}
   257  	// TODO: once we have epochs transition to the snapd type for real
   258  	if y.Name == "snapd" {
   259  		typ = TypeSnapd
   260  	}
   261  
   262  	if len(y.Epoch.Read) == 0 {
   263  		// normalize
   264  		y.Epoch.Read = []uint32{0}
   265  		y.Epoch.Write = []uint32{0}
   266  	}
   267  
   268  	confinement := StrictConfinement
   269  	if y.Confinement != "" {
   270  		confinement = y.Confinement
   271  	}
   272  
   273  	// Construct snap skeleton without apps, hooks, plugs, or slots
   274  	snap := &Info{
   275  		SuggestedName:       y.Name,
   276  		Version:             y.Version,
   277  		SnapType:            typ,
   278  		Architectures:       architectures,
   279  		Assumes:             y.Assumes,
   280  		OriginalTitle:       y.Title,
   281  		OriginalDescription: y.Description,
   282  		OriginalSummary:     y.Summary,
   283  		License:             y.License,
   284  		Epoch:               y.Epoch,
   285  		Confinement:         confinement,
   286  		Base:                y.Base,
   287  		Apps:                make(map[string]*AppInfo),
   288  		LegacyAliases:       make(map[string]*AppInfo),
   289  		Hooks:               make(map[string]*HookInfo),
   290  		Plugs:               make(map[string]*PlugInfo),
   291  		Slots:               make(map[string]*SlotInfo),
   292  		Environment:         y.Environment,
   293  		SystemUsernames:     make(map[string]*SystemUsernameInfo),
   294  		OriginalLinks:       make(map[string][]string),
   295  	}
   296  
   297  	sort.Strings(snap.Assumes)
   298  
   299  	return snap
   300  }
   301  
   302  func setPlugsFromSnapYaml(y snapYaml, snap *Info) error {
   303  	for name, data := range y.Plugs {
   304  		iface, label, attrs, err := convertToSlotOrPlugData("plug", name, data)
   305  		if err != nil {
   306  			return err
   307  		}
   308  		snap.Plugs[name] = &PlugInfo{
   309  			Snap:      snap,
   310  			Name:      name,
   311  			Interface: iface,
   312  			Attrs:     attrs,
   313  			Label:     label,
   314  		}
   315  		if len(y.Apps) > 0 {
   316  			snap.Plugs[name].Apps = make(map[string]*AppInfo)
   317  		}
   318  		if len(y.Hooks) > 0 {
   319  			snap.Plugs[name].Hooks = make(map[string]*HookInfo)
   320  		}
   321  	}
   322  
   323  	return nil
   324  }
   325  
   326  func setSlotsFromSnapYaml(y snapYaml, snap *Info) error {
   327  	for name, data := range y.Slots {
   328  		iface, label, attrs, err := convertToSlotOrPlugData("slot", name, data)
   329  		if err != nil {
   330  			return err
   331  		}
   332  		snap.Slots[name] = &SlotInfo{
   333  			Snap:      snap,
   334  			Name:      name,
   335  			Interface: iface,
   336  			Attrs:     attrs,
   337  			Label:     label,
   338  		}
   339  		if len(y.Apps) > 0 {
   340  			snap.Slots[name].Apps = make(map[string]*AppInfo)
   341  		}
   342  		if len(y.Hooks) > 0 {
   343  			snap.Slots[name].Hooks = make(map[string]*HookInfo)
   344  		}
   345  	}
   346  
   347  	return nil
   348  }
   349  
   350  func setAppsFromSnapYaml(y snapYaml, snap *Info, strk *scopedTracker) error {
   351  	for appName, yApp := range y.Apps {
   352  		// Collect all apps
   353  		app := &AppInfo{
   354  			Snap:            snap,
   355  			Name:            appName,
   356  			LegacyAliases:   yApp.Aliases,
   357  			Command:         yApp.Command,
   358  			CommandChain:    yApp.CommandChain,
   359  			StartTimeout:    yApp.StartTimeout,
   360  			Daemon:          yApp.Daemon,
   361  			DaemonScope:     yApp.DaemonScope,
   362  			StopTimeout:     yApp.StopTimeout,
   363  			StopCommand:     yApp.StopCommand,
   364  			ReloadCommand:   yApp.ReloadCommand,
   365  			PostStopCommand: yApp.PostStopCommand,
   366  			RestartCond:     yApp.RestartCond,
   367  			RestartDelay:    yApp.RestartDelay,
   368  			BusName:         yApp.BusName,
   369  			CommonID:        yApp.CommonID,
   370  			Environment:     yApp.Environment,
   371  			Completer:       yApp.Completer,
   372  			StopMode:        yApp.StopMode,
   373  			RefreshMode:     yApp.RefreshMode,
   374  			InstallMode:     yApp.InstallMode,
   375  			Before:          yApp.Before,
   376  			After:           yApp.After,
   377  			Autostart:       yApp.Autostart,
   378  			WatchdogTimeout: yApp.WatchdogTimeout,
   379  		}
   380  		if len(y.Plugs) > 0 || len(yApp.PlugNames) > 0 {
   381  			app.Plugs = make(map[string]*PlugInfo)
   382  		}
   383  		if len(y.Slots) > 0 || len(yApp.SlotNames) > 0 {
   384  			app.Slots = make(map[string]*SlotInfo)
   385  		}
   386  		if len(yApp.Sockets) > 0 {
   387  			app.Sockets = make(map[string]*SocketInfo, len(yApp.Sockets))
   388  		}
   389  		if len(yApp.ActivatesOn) > 0 {
   390  			app.ActivatesOn = make([]*SlotInfo, 0, len(yApp.ActivatesOn))
   391  		}
   392  		// Daemons default to being system daemons
   393  		if app.Daemon != "" && app.DaemonScope == "" {
   394  			app.DaemonScope = SystemDaemon
   395  		}
   396  
   397  		snap.Apps[appName] = app
   398  		for _, alias := range app.LegacyAliases {
   399  			if snap.LegacyAliases[alias] != nil {
   400  				return fmt.Errorf("cannot set %q as alias for both %q and %q", alias, snap.LegacyAliases[alias].Name, appName)
   401  			}
   402  			snap.LegacyAliases[alias] = app
   403  		}
   404  		// Bind all plugs/slots listed in this app
   405  		for _, plugName := range yApp.PlugNames {
   406  			plug, ok := snap.Plugs[plugName]
   407  			if !ok {
   408  				// Create implicit plug definitions if required
   409  				plug = &PlugInfo{
   410  					Snap:      snap,
   411  					Name:      plugName,
   412  					Interface: plugName,
   413  					Apps:      make(map[string]*AppInfo),
   414  				}
   415  				snap.Plugs[plugName] = plug
   416  			}
   417  			// Mark the plug as scoped.
   418  			strk.markPlug(plug)
   419  			app.Plugs[plugName] = plug
   420  			plug.Apps[appName] = app
   421  		}
   422  		for _, slotName := range yApp.SlotNames {
   423  			slot, ok := snap.Slots[slotName]
   424  			if !ok {
   425  				slot = &SlotInfo{
   426  					Snap:      snap,
   427  					Name:      slotName,
   428  					Interface: slotName,
   429  					Apps:      make(map[string]*AppInfo),
   430  				}
   431  				snap.Slots[slotName] = slot
   432  			}
   433  			// Mark the slot as scoped.
   434  			strk.markSlot(slot)
   435  			app.Slots[slotName] = slot
   436  			slot.Apps[appName] = app
   437  		}
   438  		for _, slotName := range yApp.ActivatesOn {
   439  			slot, ok := snap.Slots[slotName]
   440  			if !ok {
   441  				return fmt.Errorf("invalid activates-on value %q on app %q: slot not found", slotName, appName)
   442  			}
   443  			app.ActivatesOn = append(app.ActivatesOn, slot)
   444  			// Implicitly add the slot to the app
   445  			strk.markSlot(slot)
   446  			app.Slots[slotName] = slot
   447  			slot.Apps[appName] = app
   448  		}
   449  		for name, data := range yApp.Sockets {
   450  			app.Sockets[name] = &SocketInfo{
   451  				App:          app,
   452  				Name:         name,
   453  				ListenStream: data.ListenStream,
   454  				SocketMode:   data.SocketMode,
   455  			}
   456  		}
   457  		if yApp.Timer != "" {
   458  			app.Timer = &TimerInfo{
   459  				App:   app,
   460  				Timer: yApp.Timer,
   461  			}
   462  		}
   463  		// collect all common IDs
   464  		if app.CommonID != "" {
   465  			snap.CommonIDs = append(snap.CommonIDs, app.CommonID)
   466  		}
   467  	}
   468  	return nil
   469  }
   470  
   471  func setHooksFromSnapYaml(y snapYaml, snap *Info, strk *scopedTracker) {
   472  	for hookName, yHook := range y.Hooks {
   473  		if !IsHookSupported(hookName) {
   474  			continue
   475  		}
   476  
   477  		// Collect all hooks
   478  		hook := &HookInfo{
   479  			Snap:         snap,
   480  			Name:         hookName,
   481  			Environment:  yHook.Environment,
   482  			CommandChain: yHook.CommandChain,
   483  			Explicit:     true,
   484  		}
   485  		if len(y.Plugs) > 0 || len(yHook.PlugNames) > 0 {
   486  			hook.Plugs = make(map[string]*PlugInfo)
   487  		}
   488  		if len(y.Slots) > 0 || len(yHook.SlotNames) > 0 {
   489  			hook.Slots = make(map[string]*SlotInfo)
   490  		}
   491  
   492  		snap.Hooks[hookName] = hook
   493  		// Bind all plugs/slots listed in this hook
   494  		for _, plugName := range yHook.PlugNames {
   495  			plug, ok := snap.Plugs[plugName]
   496  			if !ok {
   497  				// Create implicit plug definitions if required
   498  				plug = &PlugInfo{
   499  					Snap:      snap,
   500  					Name:      plugName,
   501  					Interface: plugName,
   502  					Hooks:     make(map[string]*HookInfo),
   503  				}
   504  				snap.Plugs[plugName] = plug
   505  			}
   506  			// Mark the plug as scoped.
   507  			strk.markPlug(plug)
   508  			if plug.Hooks == nil {
   509  				plug.Hooks = make(map[string]*HookInfo)
   510  			}
   511  			hook.Plugs[plugName] = plug
   512  			plug.Hooks[hookName] = hook
   513  		}
   514  		for _, slotName := range yHook.SlotNames {
   515  			slot, ok := snap.Slots[slotName]
   516  			if !ok {
   517  				// Create implicit slot definitions if required
   518  				slot = &SlotInfo{
   519  					Snap:      snap,
   520  					Name:      slotName,
   521  					Interface: slotName,
   522  					Hooks:     make(map[string]*HookInfo),
   523  				}
   524  				snap.Slots[slotName] = slot
   525  			}
   526  			// Mark the slot as scoped.
   527  			strk.markSlot(slot)
   528  			if slot.Hooks == nil {
   529  				slot.Hooks = make(map[string]*HookInfo)
   530  			}
   531  			hook.Slots[slotName] = slot
   532  			slot.Hooks[hookName] = hook
   533  		}
   534  	}
   535  }
   536  
   537  func setSystemUsernamesFromSnapYaml(y snapYaml, snap *Info) error {
   538  	for user, data := range y.SystemUsernames {
   539  		if user == "" {
   540  			return fmt.Errorf("system username cannot be empty")
   541  		}
   542  		scope, attrs, err := convertToUsernamesData(user, data)
   543  		if err != nil {
   544  			return err
   545  		}
   546  		if scope == "" {
   547  			return fmt.Errorf("system username %q does not specify a scope", user)
   548  		}
   549  		snap.SystemUsernames[user] = &SystemUsernameInfo{
   550  			Name:  user,
   551  			Scope: scope,
   552  			Attrs: attrs,
   553  		}
   554  	}
   555  
   556  	return nil
   557  }
   558  
   559  func setLinksFromSnapYaml(y snapYaml, snap *Info) error {
   560  	for linksKey, links := range y.Links {
   561  		if linksKey == "" {
   562  			return fmt.Errorf("links key cannot be empty")
   563  		}
   564  		if !isValidLinksKey(linksKey) {
   565  			return fmt.Errorf("links key is invalid: %s", linksKey)
   566  		}
   567  		snap.OriginalLinks[linksKey] = links
   568  	}
   569  	return nil
   570  }
   571  
   572  func bindUnscopedPlugs(snap *Info, strk *scopedTracker) {
   573  	for plugName, plug := range snap.Plugs {
   574  		if strk.plug(plug) {
   575  			continue
   576  		}
   577  		for appName, app := range snap.Apps {
   578  			app.Plugs[plugName] = plug
   579  			plug.Apps[appName] = app
   580  		}
   581  
   582  		for hookName, hook := range snap.Hooks {
   583  			hook.Plugs[plugName] = plug
   584  			plug.Hooks[hookName] = hook
   585  		}
   586  	}
   587  }
   588  
   589  func bindUnscopedSlots(snap *Info, strk *scopedTracker) {
   590  	for slotName, slot := range snap.Slots {
   591  		if strk.slot(slot) {
   592  			continue
   593  		}
   594  		for appName, app := range snap.Apps {
   595  			app.Slots[slotName] = slot
   596  			slot.Apps[appName] = app
   597  		}
   598  		for hookName, hook := range snap.Hooks {
   599  			hook.Slots[slotName] = slot
   600  			slot.Hooks[hookName] = hook
   601  		}
   602  	}
   603  }
   604  
   605  // bindImplicitHooks binds all global plugs and slots to implicit hooks
   606  func bindImplicitHooks(snap *Info, strk *scopedTracker) {
   607  	for hookName, hook := range snap.Hooks {
   608  		if hook.Explicit {
   609  			continue
   610  		}
   611  		for _, plug := range snap.Plugs {
   612  			if strk.plug(plug) {
   613  				continue
   614  			}
   615  			if hook.Plugs == nil {
   616  				hook.Plugs = make(map[string]*PlugInfo)
   617  			}
   618  			hook.Plugs[plug.Name] = plug
   619  			if plug.Hooks == nil {
   620  				plug.Hooks = make(map[string]*HookInfo)
   621  			}
   622  			plug.Hooks[hookName] = hook
   623  		}
   624  		for _, slot := range snap.Slots {
   625  			if strk.slot(slot) {
   626  				continue
   627  			}
   628  			if hook.Slots == nil {
   629  				hook.Slots = make(map[string]*SlotInfo)
   630  			}
   631  			hook.Slots[slot.Name] = slot
   632  			if slot.Hooks == nil {
   633  				slot.Hooks = make(map[string]*HookInfo)
   634  			}
   635  			slot.Hooks[hookName] = hook
   636  		}
   637  	}
   638  }
   639  
   640  func convertToSlotOrPlugData(plugOrSlot, name string, data interface{}) (iface, label string, attrs map[string]interface{}, err error) {
   641  	iface = name
   642  	switch data.(type) {
   643  	case string:
   644  		return data.(string), "", nil, nil
   645  	case nil:
   646  		return name, "", nil, nil
   647  	case map[interface{}]interface{}:
   648  		for keyData, valueData := range data.(map[interface{}]interface{}) {
   649  			key, ok := keyData.(string)
   650  			if !ok {
   651  				err := fmt.Errorf("%s %q has attribute key that is not a string (found %T)",
   652  					plugOrSlot, name, keyData)
   653  				return "", "", nil, err
   654  			}
   655  			if strings.HasPrefix(key, "$") {
   656  				err := fmt.Errorf("%s %q uses reserved attribute %q", plugOrSlot, name, key)
   657  				return "", "", nil, err
   658  			}
   659  			switch key {
   660  			case "":
   661  				return "", "", nil, fmt.Errorf("%s %q has an empty attribute key", plugOrSlot, name)
   662  			case "interface":
   663  				value, ok := valueData.(string)
   664  				if !ok {
   665  					err := fmt.Errorf("interface name on %s %q is not a string (found %T)",
   666  						plugOrSlot, name, valueData)
   667  					return "", "", nil, err
   668  				}
   669  				iface = value
   670  			case "label":
   671  				value, ok := valueData.(string)
   672  				if !ok {
   673  					err := fmt.Errorf("label of %s %q is not a string (found %T)",
   674  						plugOrSlot, name, valueData)
   675  					return "", "", nil, err
   676  				}
   677  				label = value
   678  			default:
   679  				if attrs == nil {
   680  					attrs = make(map[string]interface{})
   681  				}
   682  				value, err := metautil.NormalizeValue(valueData)
   683  				if err != nil {
   684  					return "", "", nil, fmt.Errorf("attribute %q of %s %q: %v", key, plugOrSlot, name, err)
   685  				}
   686  				attrs[key] = value
   687  			}
   688  		}
   689  		return iface, label, attrs, nil
   690  	default:
   691  		err := fmt.Errorf("%s %q has malformed definition (found %T)", plugOrSlot, name, data)
   692  		return "", "", nil, err
   693  	}
   694  }
   695  
   696  // Short form:
   697  //   system-usernames:
   698  //     snap_daemon: shared  # 'scope' is 'shared'
   699  //     lxd: external        # currently unsupported
   700  //     foo: private         # currently unsupported
   701  // Attributes form:
   702  //   system-usernames:
   703  //     snap_daemon:
   704  //       scope: shared
   705  //       attrib1: ...
   706  //       attrib2: ...
   707  func convertToUsernamesData(user string, data interface{}) (scope string, attrs map[string]interface{}, err error) {
   708  	switch data.(type) {
   709  	case string:
   710  		return data.(string), nil, nil
   711  	case nil:
   712  		return "", nil, nil
   713  	case map[interface{}]interface{}:
   714  		for keyData, valueData := range data.(map[interface{}]interface{}) {
   715  			key, ok := keyData.(string)
   716  			if !ok {
   717  				err := fmt.Errorf("system username %q has attribute key that is not a string (found %T)", user, keyData)
   718  				return "", nil, err
   719  			}
   720  			switch key {
   721  			case "scope":
   722  				value, ok := valueData.(string)
   723  				if !ok {
   724  					err := fmt.Errorf("scope on system username %q is not a string (found %T)", user, valueData)
   725  					return "", nil, err
   726  				}
   727  				scope = value
   728  			case "":
   729  				return "", nil, fmt.Errorf("system username %q has an empty attribute key", user)
   730  			default:
   731  				if attrs == nil {
   732  					attrs = make(map[string]interface{})
   733  				}
   734  				value, err := metautil.NormalizeValue(valueData)
   735  				if err != nil {
   736  					return "", nil, fmt.Errorf("attribute %q of system username %q: %v", key, user, err)
   737  				}
   738  				attrs[key] = value
   739  			}
   740  		}
   741  		return scope, attrs, nil
   742  	default:
   743  		err := fmt.Errorf("system username %q has malformed definition (found %T)", user, data)
   744  		return "", nil, err
   745  	}
   746  }