github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/core/description/machine.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package description 5 6 import ( 7 "github.com/juju/errors" 8 "github.com/juju/names" 9 "github.com/juju/schema" 10 "github.com/juju/version" 11 ) 12 13 type machines struct { 14 Version int `yaml:"version"` 15 Machines_ []*machine `yaml:"machines"` 16 } 17 18 type machine struct { 19 Id_ string `yaml:"id"` 20 Nonce_ string `yaml:"nonce"` 21 PasswordHash_ string `yaml:"password-hash"` 22 Placement_ string `yaml:"placement,omitempty"` 23 Instance_ *cloudInstance `yaml:"instance,omitempty"` 24 Series_ string `yaml:"series"` 25 ContainerType_ string `yaml:"container-type,omitempty"` 26 27 Status_ *status `yaml:"status"` 28 StatusHistory_ `yaml:"status-history"` 29 30 ProviderAddresses_ []*address `yaml:"provider-addresses,omitempty"` 31 MachineAddresses_ []*address `yaml:"machine-addresses,omitempty"` 32 33 PreferredPublicAddress_ *address `yaml:"preferred-public-address,omitempty"` 34 PreferredPrivateAddress_ *address `yaml:"preferred-private-address,omitempty"` 35 36 Tools_ *agentTools `yaml:"tools"` 37 Jobs_ []string `yaml:"jobs"` 38 39 SupportedContainers_ *[]string `yaml:"supported-containers,omitempty"` 40 41 Containers_ []*machine `yaml:"containers"` 42 43 OpenedPorts_ *versionedOpenedPorts `yaml:"opened-ports,omitempty"` 44 45 Annotations_ `yaml:"annotations,omitempty"` 46 47 Constraints_ *constraints `yaml:"constraints,omitempty"` 48 } 49 50 // MachineArgs is an argument struct used to add a machine to the Model. 51 type MachineArgs struct { 52 Id names.MachineTag 53 Nonce string 54 PasswordHash string 55 Placement string 56 Series string 57 ContainerType string 58 Jobs []string 59 // A null value means that we don't yet know which containers 60 // are supported. An empty slice means 'no containers are supported'. 61 SupportedContainers *[]string 62 } 63 64 func newMachine(args MachineArgs) *machine { 65 var jobs []string 66 if count := len(args.Jobs); count > 0 { 67 jobs = make([]string, count) 68 copy(jobs, args.Jobs) 69 } 70 m := &machine{ 71 Id_: args.Id.Id(), 72 Nonce_: args.Nonce, 73 PasswordHash_: args.PasswordHash, 74 Placement_: args.Placement, 75 Series_: args.Series, 76 ContainerType_: args.ContainerType, 77 Jobs_: jobs, 78 StatusHistory_: newStatusHistory(), 79 } 80 if args.SupportedContainers != nil { 81 supported := make([]string, len(*args.SupportedContainers)) 82 copy(supported, *args.SupportedContainers) 83 m.SupportedContainers_ = &supported 84 } 85 return m 86 } 87 88 // Id implements Machine. 89 func (m *machine) Id() string { 90 return m.Id_ 91 } 92 93 // Tag implements Machine. 94 func (m *machine) Tag() names.MachineTag { 95 return names.NewMachineTag(m.Id_) 96 } 97 98 // Nonce implements Machine. 99 func (m *machine) Nonce() string { 100 return m.Nonce_ 101 } 102 103 // PasswordHash implements Machine. 104 func (m *machine) PasswordHash() string { 105 return m.PasswordHash_ 106 } 107 108 // Placement implements Machine. 109 func (m *machine) Placement() string { 110 return m.Placement_ 111 } 112 113 // Instance implements Machine. 114 func (m *machine) Instance() CloudInstance { 115 // To avoid typed nils check nil here. 116 if m.Instance_ == nil { 117 return nil 118 } 119 return m.Instance_ 120 } 121 122 // SetInstance implements Machine. 123 func (m *machine) SetInstance(args CloudInstanceArgs) { 124 m.Instance_ = newCloudInstance(args) 125 } 126 127 // Series implements Machine. 128 func (m *machine) Series() string { 129 return m.Series_ 130 } 131 132 // ContainerType implements Machine. 133 func (m *machine) ContainerType() string { 134 return m.ContainerType_ 135 } 136 137 // Status implements Machine. 138 func (m *machine) Status() Status { 139 // To avoid typed nils check nil here. 140 if m.Status_ == nil { 141 return nil 142 } 143 return m.Status_ 144 } 145 146 // SetStatus implements Machine. 147 func (m *machine) SetStatus(args StatusArgs) { 148 m.Status_ = newStatus(args) 149 } 150 151 // ProviderAddresses implements Machine. 152 func (m *machine) ProviderAddresses() []Address { 153 var result []Address 154 for _, addr := range m.ProviderAddresses_ { 155 result = append(result, addr) 156 } 157 return result 158 } 159 160 // MachineAddresses implements Machine. 161 func (m *machine) MachineAddresses() []Address { 162 var result []Address 163 for _, addr := range m.MachineAddresses_ { 164 result = append(result, addr) 165 } 166 return result 167 } 168 169 // SetAddresses implements Machine. 170 func (m *machine) SetAddresses(margs []AddressArgs, pargs []AddressArgs) { 171 m.MachineAddresses_ = nil 172 m.ProviderAddresses_ = nil 173 for _, args := range margs { 174 if args.Value != "" { 175 m.MachineAddresses_ = append(m.MachineAddresses_, newAddress(args)) 176 } 177 } 178 for _, args := range pargs { 179 if args.Value != "" { 180 m.ProviderAddresses_ = append(m.ProviderAddresses_, newAddress(args)) 181 } 182 } 183 } 184 185 // PreferredPublicAddress implements Machine. 186 func (m *machine) PreferredPublicAddress() Address { 187 // To avoid typed nils check nil here. 188 if m.PreferredPublicAddress_ == nil { 189 return nil 190 } 191 return m.PreferredPublicAddress_ 192 } 193 194 // PreferredPrivateAddress implements Machine. 195 func (m *machine) PreferredPrivateAddress() Address { 196 // To avoid typed nils check nil here. 197 if m.PreferredPrivateAddress_ == nil { 198 return nil 199 } 200 return m.PreferredPrivateAddress_ 201 } 202 203 // SetPreferredAddresses implements Machine. 204 func (m *machine) SetPreferredAddresses(public AddressArgs, private AddressArgs) { 205 if public.Value != "" { 206 m.PreferredPublicAddress_ = newAddress(public) 207 } 208 if private.Value != "" { 209 m.PreferredPrivateAddress_ = newAddress(private) 210 } 211 } 212 213 // Tools implements Machine. 214 func (m *machine) Tools() AgentTools { 215 // To avoid a typed nil, check before returning. 216 if m.Tools_ == nil { 217 return nil 218 } 219 return m.Tools_ 220 } 221 222 // SetTools implements Machine. 223 func (m *machine) SetTools(args AgentToolsArgs) { 224 m.Tools_ = newAgentTools(args) 225 } 226 227 // Jobs implements Machine. 228 func (m *machine) Jobs() []string { 229 return m.Jobs_ 230 } 231 232 // SupportedContainers implements Machine. 233 func (m *machine) SupportedContainers() ([]string, bool) { 234 if m.SupportedContainers_ == nil { 235 return nil, false 236 } 237 return *m.SupportedContainers_, true 238 } 239 240 // Containers implements Machine. 241 func (m *machine) Containers() []Machine { 242 var result []Machine 243 for _, container := range m.Containers_ { 244 result = append(result, container) 245 } 246 return result 247 } 248 249 // AddContainer implements Machine. 250 func (m *machine) AddContainer(args MachineArgs) Machine { 251 container := newMachine(args) 252 m.Containers_ = append(m.Containers_, container) 253 return container 254 } 255 256 // OpenedPorts implements Machine. 257 func (m *machine) OpenedPorts() []OpenedPorts { 258 if m.OpenedPorts_ == nil { 259 return nil 260 } 261 var result []OpenedPorts 262 for _, ports := range m.OpenedPorts_.OpenedPorts_ { 263 result = append(result, ports) 264 } 265 return result 266 } 267 268 // AddOpenedPorts implements Machine. 269 func (m *machine) AddOpenedPorts(args OpenedPortsArgs) OpenedPorts { 270 if m.OpenedPorts_ == nil { 271 m.OpenedPorts_ = &versionedOpenedPorts{Version: 1} 272 } 273 ports := newOpenedPorts(args) 274 m.OpenedPorts_.OpenedPorts_ = append(m.OpenedPorts_.OpenedPorts_, ports) 275 return ports 276 } 277 278 func (m *machine) setOpenedPorts(portsList []*openedPorts) { 279 m.OpenedPorts_ = &versionedOpenedPorts{ 280 Version: 1, 281 OpenedPorts_: portsList, 282 } 283 } 284 285 // Constraints implements HasConstraints. 286 func (m *machine) Constraints() Constraints { 287 if m.Constraints_ == nil { 288 return nil 289 } 290 return m.Constraints_ 291 } 292 293 // SetConstraints implements HasConstraints. 294 func (m *machine) SetConstraints(args ConstraintsArgs) { 295 m.Constraints_ = newConstraints(args) 296 } 297 298 // Validate implements Machine. 299 func (m *machine) Validate() error { 300 if m.Id_ == "" { 301 return errors.NotValidf("machine missing id") 302 } 303 if m.Status_ == nil { 304 return errors.NotValidf("machine %q missing status", m.Id_) 305 } 306 // Since all exports should be done when machines are stable, 307 // there should always be tools and cloud instance. 308 if m.Tools_ == nil { 309 return errors.NotValidf("machine %q missing tools", m.Id_) 310 } 311 if m.Instance_ == nil { 312 return errors.NotValidf("machine %q missing instance", m.Id_) 313 } 314 for _, container := range m.Containers_ { 315 if err := container.Validate(); err != nil { 316 return errors.Trace(err) 317 } 318 } 319 320 return nil 321 } 322 323 func importMachines(source map[string]interface{}) ([]*machine, error) { 324 checker := versionedChecker("machines") 325 coerced, err := checker.Coerce(source, nil) 326 if err != nil { 327 return nil, errors.Annotatef(err, "machines version schema check failed") 328 } 329 valid := coerced.(map[string]interface{}) 330 331 version := int(valid["version"].(int64)) 332 importFunc, ok := machineDeserializationFuncs[version] 333 if !ok { 334 return nil, errors.NotValidf("version %d", version) 335 } 336 sourceList := valid["machines"].([]interface{}) 337 return importMachineList(sourceList, importFunc) 338 } 339 340 func importMachineList(sourceList []interface{}, importFunc machineDeserializationFunc) ([]*machine, error) { 341 result := make([]*machine, 0, len(sourceList)) 342 for i, value := range sourceList { 343 source, ok := value.(map[string]interface{}) 344 if !ok { 345 return nil, errors.Errorf("unexpected value for machine %d, %T", i, value) 346 } 347 machine, err := importFunc(source) 348 if err != nil { 349 return nil, errors.Annotatef(err, "machine %d", i) 350 } 351 result = append(result, machine) 352 } 353 return result, nil 354 } 355 356 type machineDeserializationFunc func(map[string]interface{}) (*machine, error) 357 358 var machineDeserializationFuncs = map[int]machineDeserializationFunc{ 359 1: importMachineV1, 360 } 361 362 func importMachineV1(source map[string]interface{}) (*machine, error) { 363 fields := schema.Fields{ 364 "id": schema.String(), 365 "nonce": schema.String(), 366 "password-hash": schema.String(), 367 "placement": schema.String(), 368 "instance": schema.StringMap(schema.Any()), 369 "series": schema.String(), 370 "container-type": schema.String(), 371 "jobs": schema.List(schema.String()), 372 "status": schema.StringMap(schema.Any()), 373 "supported-containers": schema.List(schema.String()), 374 "tools": schema.StringMap(schema.Any()), 375 "containers": schema.List(schema.StringMap(schema.Any())), 376 "opened-ports": schema.StringMap(schema.Any()), 377 378 "provider-addresses": schema.List(schema.StringMap(schema.Any())), 379 "machine-addresses": schema.List(schema.StringMap(schema.Any())), 380 "preferred-public-address": schema.StringMap(schema.Any()), 381 "preferred-private-address": schema.StringMap(schema.Any()), 382 } 383 384 defaults := schema.Defaults{ 385 "placement": "", 386 "container-type": "", 387 // Even though we are expecting instance data for every machine, 388 // it isn't strictly necessary, so we allow it to not exist here. 389 "instance": schema.Omit, 390 "supported-containers": schema.Omit, 391 "opened-ports": schema.Omit, 392 "provider-addresses": schema.Omit, 393 "machine-addresses": schema.Omit, 394 "preferred-public-address": schema.Omit, 395 "preferred-private-address": schema.Omit, 396 } 397 addAnnotationSchema(fields, defaults) 398 addConstraintsSchema(fields, defaults) 399 addStatusHistorySchema(fields) 400 checker := schema.FieldMap(fields, defaults) 401 402 coerced, err := checker.Coerce(source, nil) 403 if err != nil { 404 return nil, errors.Annotatef(err, "machine v1 schema check failed") 405 } 406 valid := coerced.(map[string]interface{}) 407 // From here we know that the map returned from the schema coercion 408 // contains fields of the right type. 409 result := &machine{ 410 Id_: valid["id"].(string), 411 Nonce_: valid["nonce"].(string), 412 PasswordHash_: valid["password-hash"].(string), 413 Placement_: valid["placement"].(string), 414 Series_: valid["series"].(string), 415 ContainerType_: valid["container-type"].(string), 416 StatusHistory_: newStatusHistory(), 417 } 418 result.importAnnotations(valid) 419 if err := result.importStatusHistory(valid); err != nil { 420 return nil, errors.Trace(err) 421 } 422 423 if constraintsMap, ok := valid["constraints"]; ok { 424 constraints, err := importConstraints(constraintsMap.(map[string]interface{})) 425 if err != nil { 426 return nil, errors.Trace(err) 427 } 428 result.Constraints_ = constraints 429 } 430 431 if jobs := valid["jobs"].([]interface{}); len(jobs) > 0 { 432 for _, job := range jobs { 433 result.Jobs_ = append(result.Jobs_, job.(string)) 434 } 435 } 436 if supported, ok := valid["supported-containers"]; ok { 437 supportedList := supported.([]interface{}) 438 s := make([]string, len(supportedList)) 439 for i, containerType := range supportedList { 440 s[i] = containerType.(string) 441 } 442 result.SupportedContainers_ = &s 443 } 444 445 if instanceMap, ok := valid["instance"]; ok { 446 instance, err := importCloudInstance(instanceMap.(map[string]interface{})) 447 if err != nil { 448 return nil, errors.Trace(err) 449 } 450 result.Instance_ = instance 451 } 452 453 // Tools and status are required, so we expect them to be there. 454 tools, err := importAgentTools(valid["tools"].(map[string]interface{})) 455 if err != nil { 456 return nil, errors.Trace(err) 457 } 458 result.Tools_ = tools 459 460 status, err := importStatus(valid["status"].(map[string]interface{})) 461 if err != nil { 462 return nil, errors.Trace(err) 463 } 464 result.Status_ = status 465 466 if addresses, ok := valid["provider-addresses"]; ok { 467 providerAddresses, err := importAddresses(addresses.([]interface{})) 468 if err != nil { 469 return nil, errors.Trace(err) 470 } 471 result.ProviderAddresses_ = providerAddresses 472 } 473 474 if addresses, ok := valid["machine-addresses"]; ok { 475 machineAddresses, err := importAddresses(addresses.([]interface{})) 476 if err != nil { 477 return nil, errors.Trace(err) 478 } 479 result.MachineAddresses_ = machineAddresses 480 } 481 482 if address, ok := valid["preferred-public-address"]; ok { 483 publicAddress, err := importAddress(address.(map[string]interface{})) 484 if err != nil { 485 return nil, errors.Trace(err) 486 } 487 result.PreferredPublicAddress_ = publicAddress 488 } 489 490 if address, ok := valid["preferred-private-address"]; ok { 491 privateAddress, err := importAddress(address.(map[string]interface{})) 492 if err != nil { 493 return nil, errors.Trace(err) 494 } 495 result.PreferredPrivateAddress_ = privateAddress 496 } 497 498 machineList := valid["containers"].([]interface{}) 499 machines, err := importMachineList(machineList, importMachineV1) 500 if err != nil { 501 return nil, errors.Annotatef(err, "containers") 502 } 503 result.Containers_ = machines 504 505 if portsMap, ok := valid["opened-ports"]; ok { 506 portsList, err := importOpenedPorts(portsMap.(map[string]interface{})) 507 if err != nil { 508 return nil, errors.Trace(err) 509 } 510 result.setOpenedPorts(portsList) 511 } 512 513 return result, nil 514 515 } 516 517 // CloudInstanceArgs is an argument struct used to add information about the 518 // cloud instance to a Machine. 519 type CloudInstanceArgs struct { 520 InstanceId string 521 Status string 522 Architecture string 523 Memory uint64 524 RootDisk uint64 525 CpuCores uint64 526 CpuPower uint64 527 Tags []string 528 AvailabilityZone string 529 } 530 531 func newCloudInstance(args CloudInstanceArgs) *cloudInstance { 532 tags := make([]string, len(args.Tags)) 533 copy(tags, args.Tags) 534 return &cloudInstance{ 535 Version: 1, 536 InstanceId_: args.InstanceId, 537 Status_: args.Status, 538 Architecture_: args.Architecture, 539 Memory_: args.Memory, 540 RootDisk_: args.RootDisk, 541 CpuCores_: args.CpuCores, 542 CpuPower_: args.CpuPower, 543 Tags_: tags, 544 AvailabilityZone_: args.AvailabilityZone, 545 } 546 } 547 548 type cloudInstance struct { 549 Version int `yaml:"version"` 550 551 InstanceId_ string `yaml:"instance-id"` 552 Status_ string `yaml:"status"` 553 // For all the optional values, empty values make no sense, and 554 // it would be better to have them not set rather than set with 555 // a nonsense value. 556 Architecture_ string `yaml:"architecture,omitempty"` 557 Memory_ uint64 `yaml:"memory,omitempty"` 558 RootDisk_ uint64 `yaml:"root-disk,omitempty"` 559 CpuCores_ uint64 `yaml:"cpu-cores,omitempty"` 560 CpuPower_ uint64 `yaml:"cpu-power,omitempty"` 561 Tags_ []string `yaml:"tags,omitempty"` 562 AvailabilityZone_ string `yaml:"availability-zone,omitempty"` 563 } 564 565 // InstanceId implements CloudInstance. 566 func (c *cloudInstance) InstanceId() string { 567 return c.InstanceId_ 568 } 569 570 // Status implements CloudInstance. 571 func (c *cloudInstance) Status() string { 572 return c.Status_ 573 } 574 575 // Architecture implements CloudInstance. 576 func (c *cloudInstance) Architecture() string { 577 return c.Architecture_ 578 } 579 580 // Memory implements CloudInstance. 581 func (c *cloudInstance) Memory() uint64 { 582 return c.Memory_ 583 } 584 585 // RootDisk implements CloudInstance. 586 func (c *cloudInstance) RootDisk() uint64 { 587 return c.RootDisk_ 588 } 589 590 // CpuCores implements CloudInstance. 591 func (c *cloudInstance) CpuCores() uint64 { 592 return c.CpuCores_ 593 } 594 595 // CpuPower implements CloudInstance. 596 func (c *cloudInstance) CpuPower() uint64 { 597 return c.CpuPower_ 598 } 599 600 // Tags implements CloudInstance. 601 func (c *cloudInstance) Tags() []string { 602 tags := make([]string, len(c.Tags_)) 603 copy(tags, c.Tags_) 604 return tags 605 } 606 607 // AvailabilityZone implements CloudInstance. 608 func (c *cloudInstance) AvailabilityZone() string { 609 return c.AvailabilityZone_ 610 } 611 612 func importCloudInstance(source map[string]interface{}) (*cloudInstance, error) { 613 version, err := getVersion(source) 614 if err != nil { 615 return nil, errors.Annotate(err, "cloudInstance version schema check failed") 616 } 617 618 importFunc, ok := cloudInstanceDeserializationFuncs[version] 619 if !ok { 620 return nil, errors.NotValidf("version %d", version) 621 } 622 623 return importFunc(source) 624 } 625 626 type cloudInstanceDeserializationFunc func(map[string]interface{}) (*cloudInstance, error) 627 628 var cloudInstanceDeserializationFuncs = map[int]cloudInstanceDeserializationFunc{ 629 1: importCloudInstanceV1, 630 } 631 632 func importCloudInstanceV1(source map[string]interface{}) (*cloudInstance, error) { 633 fields := schema.Fields{ 634 "instance-id": schema.String(), 635 "status": schema.String(), 636 "architecture": schema.String(), 637 "memory": schema.Uint(), 638 "root-disk": schema.Uint(), 639 "cpu-cores": schema.Uint(), 640 "cpu-power": schema.Uint(), 641 "tags": schema.List(schema.String()), 642 "availability-zone": schema.String(), 643 } 644 // Some values don't have to be there. 645 defaults := schema.Defaults{ 646 "architecture": "", 647 "memory": uint64(0), 648 "root-disk": uint64(0), 649 "cpu-cores": uint64(0), 650 "cpu-power": uint64(0), 651 "tags": schema.Omit, 652 "availability-zone": "", 653 } 654 checker := schema.FieldMap(fields, defaults) 655 656 coerced, err := checker.Coerce(source, nil) 657 if err != nil { 658 return nil, errors.Annotatef(err, "cloudInstance v1 schema check failed") 659 } 660 valid := coerced.(map[string]interface{}) 661 // From here we know that the map returned from the schema coercion 662 // contains fields of the right type. 663 664 return &cloudInstance{ 665 Version: 1, 666 InstanceId_: valid["instance-id"].(string), 667 Status_: valid["status"].(string), 668 Architecture_: valid["architecture"].(string), 669 Memory_: valid["memory"].(uint64), 670 RootDisk_: valid["root-disk"].(uint64), 671 CpuCores_: valid["cpu-cores"].(uint64), 672 CpuPower_: valid["cpu-power"].(uint64), 673 Tags_: convertToStringSlice(valid["tags"]), 674 AvailabilityZone_: valid["availability-zone"].(string), 675 }, nil 676 } 677 678 // AgentToolsArgs is an argument struct used to add information about the 679 // tools the agent is using to a Machine. 680 type AgentToolsArgs struct { 681 Version version.Binary 682 URL string 683 SHA256 string 684 Size int64 685 } 686 687 func newAgentTools(args AgentToolsArgs) *agentTools { 688 return &agentTools{ 689 Version_: 1, 690 ToolsVersion_: args.Version, 691 URL_: args.URL, 692 SHA256_: args.SHA256, 693 Size_: args.Size, 694 } 695 } 696 697 // Keeping the agentTools with the machine code, because we hope 698 // that one day we will succeed in merging the unit agents with the 699 // machine agents. 700 type agentTools struct { 701 Version_ int `yaml:"version"` 702 ToolsVersion_ version.Binary `yaml:"tools-version"` 703 URL_ string `yaml:"url"` 704 SHA256_ string `yaml:"sha256"` 705 Size_ int64 `yaml:"size"` 706 } 707 708 // Version implements AgentTools. 709 func (a *agentTools) Version() version.Binary { 710 return a.ToolsVersion_ 711 } 712 713 // URL implements AgentTools. 714 func (a *agentTools) URL() string { 715 return a.URL_ 716 } 717 718 // SHA256 implements AgentTools. 719 func (a *agentTools) SHA256() string { 720 return a.SHA256_ 721 } 722 723 // Size implements AgentTools. 724 func (a *agentTools) Size() int64 { 725 return a.Size_ 726 } 727 728 func importAgentTools(source map[string]interface{}) (*agentTools, error) { 729 version, err := getVersion(source) 730 if err != nil { 731 return nil, errors.Annotate(err, "agentTools version schema check failed") 732 } 733 734 importFunc, ok := agentToolsDeserializationFuncs[version] 735 if !ok { 736 return nil, errors.NotValidf("version %d", version) 737 } 738 739 return importFunc(source) 740 } 741 742 type agentToolsDeserializationFunc func(map[string]interface{}) (*agentTools, error) 743 744 var agentToolsDeserializationFuncs = map[int]agentToolsDeserializationFunc{ 745 1: importAgentToolsV1, 746 } 747 748 func importAgentToolsV1(source map[string]interface{}) (*agentTools, error) { 749 fields := schema.Fields{ 750 "tools-version": schema.String(), 751 "url": schema.String(), 752 "sha256": schema.String(), 753 "size": schema.Int(), 754 } 755 checker := schema.FieldMap(fields, nil) // no defaults 756 757 coerced, err := checker.Coerce(source, nil) 758 if err != nil { 759 return nil, errors.Annotatef(err, "agentTools v1 schema check failed") 760 } 761 valid := coerced.(map[string]interface{}) 762 // From here we know that the map returned from the schema coercion 763 // contains fields of the right type. 764 765 verString := valid["tools-version"].(string) 766 toolsVersion, err := version.ParseBinary(verString) 767 if err != nil { 768 return nil, errors.Annotatef(err, "agentTools tools-version") 769 } 770 771 return &agentTools{ 772 Version_: 1, 773 ToolsVersion_: toolsVersion, 774 URL_: valid["url"].(string), 775 SHA256_: valid["sha256"].(string), 776 Size_: valid["size"].(int64), 777 }, nil 778 }