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 }