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