github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/pkg/platform/runtime/buildexpression/buildexpression.go (about) 1 package buildexpression 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "sort" 8 "strings" 9 10 "github.com/ActiveState/cli/internal/errs" 11 "github.com/ActiveState/cli/internal/locale" 12 "github.com/ActiveState/cli/internal/logging" 13 "github.com/ActiveState/cli/internal/multilog" 14 "github.com/ActiveState/cli/internal/rtutils/ptr" 15 "github.com/ActiveState/cli/internal/sliceutils" 16 "github.com/ActiveState/cli/pkg/platform/api/buildplanner/types" 17 "github.com/go-openapi/strfmt" 18 ) 19 20 const ( 21 SolveFuncName = "solve" 22 SolveLegacyFuncName = "solve_legacy" 23 RequirementsKey = "requirements" 24 PlatformsKey = "platforms" 25 AtTimeKey = "at_time" 26 RequirementNameKey = "name" 27 RequirementNamespaceKey = "namespace" 28 RequirementVersionRequirementsKey = "version_requirements" 29 RequirementVersionKey = "version" 30 RequirementRevisionKey = "revision" 31 RequirementComparatorKey = "comparator" 32 33 ctxLet = "let" 34 ctxIn = "in" 35 ctxAp = "ap" 36 ctxValue = "value" 37 ctxAssignments = "assignments" 38 ctxIsAp = "isAp" 39 ) 40 41 var funcNodeNotFoundError = errors.New("Could not find function node") 42 43 type BuildExpression struct { 44 Let *Let 45 Assignments []*Value 46 } 47 48 type Let struct { 49 // Let statements can be nested. 50 // Each let will contain its own assignments and an in statement. 51 Let *Let 52 Assignments []*Var 53 In *In 54 } 55 56 type Var struct { 57 Name string 58 Value *Value 59 } 60 61 type Value struct { 62 Ap *Ap 63 List *[]*Value 64 Str *string 65 Null *Null 66 Float *float64 67 Int *int 68 69 Assignment *Var 70 Object *[]*Var 71 Ident *string 72 } 73 74 type Null struct { 75 Null string 76 } 77 78 type Ap struct { 79 Name string 80 Arguments []*Value 81 } 82 83 type In struct { 84 FuncCall *Ap 85 Name *string 86 } 87 88 // New creates a BuildExpression from a JSON byte array. 89 // The JSON must be a valid BuildExpression in the following format: 90 // 91 // { 92 // "let": { 93 // "runtime": { 94 // "solve_legacy": { 95 // "at_time": "$at_time", 96 // "build_flags": [], 97 // "camel_flags": [], 98 // "platforms": [ 99 // "96b7e6f2-bebf-564c-bc1c-f04482398f38" 100 // ], 101 // "requirements": [ 102 // { 103 // "name": "requests", 104 // "namespace": "language/python" 105 // }, 106 // { 107 // "name": "python", 108 // "namespace": "language", 109 // "version_requirements": [ 110 // { 111 // "comparator": "eq", 112 // "version": "3.10.10" 113 // } 114 // ] 115 // }, 116 // ], 117 // "solver_version": null 118 // } 119 // }, 120 // "in": "$runtime" 121 // } 122 // } 123 func New(data []byte) (*BuildExpression, error) { 124 rawBuildExpression := make(map[string]interface{}) 125 err := json.Unmarshal(data, &rawBuildExpression) 126 if err != nil { 127 return nil, errs.Wrap(err, "Could not unmarshal build expression") 128 } 129 130 if len(rawBuildExpression) != 1 { 131 return nil, errs.New("Build expression must have exactly one key") 132 } 133 134 expr := &BuildExpression{} 135 var path []string 136 for key, value := range rawBuildExpression { 137 switch v := value.(type) { 138 case map[string]interface{}: 139 // At this level the key must either be a let, an ap, or an assignment. 140 if key == "let" { 141 let, err := newLet(path, v) 142 if err != nil { 143 return nil, errs.Wrap(err, "Could not parse 'let' key") 144 } 145 146 expr.Let = let 147 } else if isAp(path, v) { 148 ap, err := newAp(path, v) 149 if err != nil { 150 return nil, errs.Wrap(err, "Could not parse '%s' key", key) 151 } 152 153 expr.Assignments = append(expr.Assignments, &Value{Ap: ap}) 154 } else { 155 assignments, err := newAssignments(path, v) 156 if err != nil { 157 return nil, errs.Wrap(err, "Could not parse assignments") 158 } 159 160 expr.Assignments = append(expr.Assignments, &Value{Assignment: &Var{Name: key, Value: &Value{Object: &assignments}}}) 161 } 162 default: 163 return nil, errs.New("Build expression's value must be a map[string]interface{}") 164 } 165 } 166 167 err = expr.validateRequirements() 168 if err != nil { 169 return nil, errs.Wrap(err, "Could not validate requirements") 170 } 171 172 err = expr.normalizeTimestamp() 173 if err != nil { 174 return nil, errs.Wrap(err, "Could not normalize timestamp") 175 } 176 177 return expr, nil 178 } 179 180 // NewEmpty creates a minimal, empty buildexpression. 181 func NewEmpty() (*BuildExpression, error) { 182 // At this time, there is no way to ask the Platform for an empty buildexpression, so build one 183 // manually. 184 expr, err := New([]byte(` 185 { 186 "let": { 187 "runtime": { 188 "solve_legacy": { 189 "at_time": "$at_time", 190 "build_flags": [], 191 "camel_flags": [], 192 "platforms": [], 193 "requirements": [], 194 "solver_version": null 195 } 196 }, 197 "in": "$runtime" 198 } 199 } 200 `)) 201 if err != nil { 202 return nil, errs.Wrap(err, "Unable to create initial buildexpression") 203 } 204 return expr, nil 205 } 206 207 func newLet(path []string, m map[string]interface{}) (*Let, error) { 208 path = append(path, ctxLet) 209 defer func() { 210 _, _, err := sliceutils.Pop(path) 211 if err != nil { 212 multilog.Error("Could not pop context: %v", err) 213 } 214 }() 215 216 inValue, ok := m["in"] 217 if !ok { 218 return nil, errs.New("Build expression's 'let' object has no 'in' key") 219 } 220 221 in, err := newIn(path, inValue) 222 if err != nil { 223 return nil, errs.Wrap(err, "Could not parse 'in' key's value: %v", inValue) 224 } 225 226 // Delete in so it doesn't get parsed as an assignment. 227 delete(m, "in") 228 229 result := &Let{In: in} 230 let, ok := m["let"] 231 if ok { 232 letMap, ok := let.(map[string]interface{}) 233 if !ok { 234 return nil, errs.New("'let' key's value is not a map[string]interface{}") 235 } 236 237 l, err := newLet(path, letMap) 238 if err != nil { 239 return nil, errs.Wrap(err, "Could not parse 'let' key") 240 } 241 result.Let = l 242 243 // Delete let so it doesn't get parsed as an assignment. 244 delete(m, "let") 245 } 246 247 assignments, err := newAssignments(path, m) 248 if err != nil { 249 return nil, errs.Wrap(err, "Could not parse assignments") 250 } 251 252 result.Assignments = assignments 253 return result, nil 254 } 255 256 func isAp(path []string, value map[string]interface{}) bool { 257 path = append(path, ctxIsAp) 258 defer func() { 259 _, _, err := sliceutils.Pop(path) 260 if err != nil { 261 multilog.Error("Could not pop context: %v", err) 262 } 263 }() 264 265 _, hasIn := value["in"] 266 if hasIn && !sliceutils.Contains(path, ctxAssignments) { 267 return false 268 } 269 270 return true 271 } 272 273 func newValue(path []string, valueInterface interface{}) (*Value, error) { 274 path = append(path, ctxValue) 275 defer func() { 276 _, _, err := sliceutils.Pop(path) 277 if err != nil { 278 multilog.Error("Could not pop context: %v", err) 279 } 280 }() 281 282 value := &Value{} 283 284 switch v := valueInterface.(type) { 285 case map[string]interface{}: 286 // Examine keys first to see if this is a function call. 287 for key, val := range v { 288 if _, ok := val.(map[string]interface{}); !ok { 289 continue 290 } 291 292 // If the length of the value is greater than 1, 293 // then it's not a function call. It's an object 294 // and will be set as such outside the loop. 295 if len(v) > 1 { 296 continue 297 } 298 299 if isAp(path, val.(map[string]interface{})) { 300 f, err := newAp(path, v) 301 if err != nil { 302 return nil, errs.Wrap(err, "Could not parse '%s' function's value: %v", key, v) 303 } 304 value.Ap = f 305 } 306 } 307 308 if value.Ap == nil { 309 // It's not a function call, but an object. 310 object, err := newAssignments(path, v) 311 if err != nil { 312 return nil, errs.Wrap(err, "Could not parse object: %v", v) 313 } 314 value.Object = &object 315 } 316 317 case []interface{}: 318 values := []*Value{} 319 for _, item := range v { 320 value, err := newValue(path, item) 321 if err != nil { 322 return nil, errs.Wrap(err, "Could not parse list: %v", v) 323 } 324 values = append(values, value) 325 } 326 value.List = &values 327 328 case string: 329 if sliceutils.Contains(path, ctxIn) { 330 value.Ident = &v 331 } else { 332 value.Str = ptr.To(v) 333 } 334 335 case float64: 336 value.Float = ptr.To(v) 337 338 case nil: 339 // An empty value is interpreted as JSON null. 340 value.Null = &Null{} 341 342 default: 343 logging.Debug("Unknown type: %T at path %s", v, strings.Join(path, ".")) 344 // An empty value is interpreted as JSON null. 345 value.Null = &Null{} 346 } 347 348 return value, nil 349 } 350 351 func newAp(path []string, m map[string]interface{}) (*Ap, error) { 352 path = append(path, ctxAp) 353 defer func() { 354 _, _, err := sliceutils.Pop(path) 355 if err != nil { 356 multilog.Error("Could not pop context: %v", err) 357 } 358 }() 359 360 // m is a mapping of function name to arguments. There should only be one 361 // set of arugments. Since the arguments are key-value pairs, it should be 362 // a map[string]interface{}. 363 if len(m) > 1 { 364 return nil, errs.New("Function call has more than one argument mapping") 365 } 366 367 // Look in the given object for the function's name and argument mapping. 368 var name string 369 var argsInterface interface{} 370 for key, value := range m { 371 _, ok := value.(map[string]interface{}) 372 if !ok { 373 return nil, errs.New("Incorrect argument format") 374 } 375 376 name = key 377 argsInterface = value 378 } 379 380 args := []*Value{} 381 382 switch v := argsInterface.(type) { 383 case map[string]interface{}: 384 for key, valueInterface := range v { 385 value, err := newValue(path, valueInterface) 386 if err != nil { 387 return nil, errs.Wrap(err, "Could not parse '%s' function's argument '%s': %v", name, key, valueInterface) 388 } 389 args = append(args, &Value{Assignment: &Var{Name: key, Value: value}}) 390 } 391 sort.SliceStable(args, func(i, j int) bool { return args[i].Assignment.Name < args[j].Assignment.Name }) 392 393 case []interface{}: 394 for _, item := range v { 395 value, err := newValue(path, item) 396 if err != nil { 397 return nil, errs.Wrap(err, "Could not parse '%s' function's argument list item: %v", name, item) 398 } 399 args = append(args, value) 400 } 401 402 default: 403 return nil, errs.New("Function '%s' expected to be object or list", name) 404 } 405 406 return &Ap{Name: name, Arguments: args}, nil 407 } 408 409 func newAssignments(path []string, m map[string]interface{}) ([]*Var, error) { 410 path = append(path, ctxAssignments) 411 defer func() { 412 _, _, err := sliceutils.Pop(path) 413 if err != nil { 414 multilog.Error("Could not pop context: %v", err) 415 } 416 }() 417 418 assignments := []*Var{} 419 for key, valueInterface := range m { 420 value, err := newValue(path, valueInterface) 421 if err != nil { 422 return nil, errs.Wrap(err, "Could not parse '%s' key's value: %v", key, valueInterface) 423 } 424 assignments = append(assignments, &Var{Name: key, Value: value}) 425 426 } 427 sort.SliceStable(assignments, func(i, j int) bool { 428 return assignments[i].Name < assignments[j].Name 429 }) 430 return assignments, nil 431 } 432 433 func newIn(path []string, inValue interface{}) (*In, error) { 434 path = append(path, ctxIn) 435 defer func() { 436 _, _, err := sliceutils.Pop(path) 437 if err != nil { 438 multilog.Error("Could not pop context: %v", err) 439 } 440 }() 441 442 in := &In{} 443 444 switch v := inValue.(type) { 445 case map[string]interface{}: 446 f, err := newAp(path, v) 447 if err != nil { 448 return nil, errs.Wrap(err, "'in' object is not a function call") 449 } 450 in.FuncCall = f 451 452 case string: 453 in.Name = ptr.To(strings.TrimPrefix(v, "$")) 454 455 default: 456 return nil, errs.New("'in' value expected to be a function call or string") 457 } 458 459 return in, nil 460 } 461 462 // validateRequirements ensures that the requirements in the BuildExpression contain 463 // both the name and namespace fields. These fileds are used for requirement operations. 464 func (e *BuildExpression) validateRequirements() error { 465 requirements, err := e.getRequirementsNode() 466 if err != nil { 467 return errs.Wrap(err, "Could not get requirements node") 468 } 469 470 for _, r := range requirements { 471 if r.Object == nil { 472 continue 473 } 474 475 // The requirement object needs to have a name and value field. 476 // The value can be a string (in the case of name or namespace) 477 // or a list (in the case of version requirements). 478 for _, o := range *r.Object { 479 if o.Name == "" { 480 return errs.New("Requirement object missing name field") 481 } 482 483 if o.Value == nil { 484 return errs.New("Requirement object missing value field") 485 } 486 487 if o.Name == RequirementNameKey || o.Name == RequirementNamespaceKey { 488 if o.Value.Str == nil { 489 return errs.New("Requirement object value is not set to a string") 490 } 491 } 492 493 if o.Name == RequirementVersionRequirementsKey { 494 if o.Value.List == nil { 495 return errs.New("Requirement object value is not set to a list") 496 } 497 } 498 } 499 } 500 return nil 501 } 502 503 // Requirements returns the requirements in the BuildExpression. 504 // It returns an error if the requirements are not found or if they are malformed. 505 // It expects the JSON representation of the solve node to be formatted as follows: 506 // 507 // { 508 // "requirements": [ 509 // { 510 // "name": "requests", 511 // "namespace": "language/python" 512 // }, 513 // { 514 // "name": "python", 515 // "namespace": "language", 516 // "version_requirements": [{ 517 // "comparator": "eq", 518 // "version": "3.10.10" 519 // }] 520 // } 521 // ] 522 // } 523 func (e *BuildExpression) Requirements() ([]types.Requirement, error) { 524 requirementsNode, err := e.getRequirementsNode() 525 if err != nil { 526 return nil, errs.Wrap(err, "Could not get requirements node") 527 } 528 529 var requirements []types.Requirement 530 for _, r := range requirementsNode { 531 if r.Object == nil { 532 continue 533 } 534 535 var req types.Requirement 536 for _, o := range *r.Object { 537 if o.Name == RequirementNameKey { 538 req.Name = *o.Value.Str 539 } 540 541 if o.Name == RequirementNamespaceKey { 542 req.Namespace = *o.Value.Str 543 } 544 545 if o.Name == RequirementVersionRequirementsKey { 546 req.VersionRequirement = getVersionRequirements(o.Value.List) 547 } 548 } 549 requirements = append(requirements, req) 550 } 551 552 return requirements, nil 553 } 554 555 func (e *BuildExpression) getRequirementsNode() ([]*Value, error) { 556 solveAp, err := e.getSolveNode() 557 if err != nil { 558 return nil, errs.Wrap(err, "Could not get solve node") 559 } 560 561 var reqs []*Value 562 for _, arg := range solveAp.Arguments { 563 if arg.Assignment == nil { 564 continue 565 } 566 567 if arg.Assignment.Name == RequirementsKey && arg.Assignment.Value != nil { 568 reqs = *arg.Assignment.Value.List 569 } 570 } 571 572 return reqs, nil 573 } 574 575 func getVersionRequirements(v *[]*Value) []types.VersionRequirement { 576 var reqs []types.VersionRequirement 577 578 if v == nil { 579 return reqs 580 } 581 582 for _, r := range *v { 583 if r.Object == nil { 584 continue 585 } 586 587 versionReq := make(types.VersionRequirement) 588 for _, o := range *r.Object { 589 if o.Name == RequirementComparatorKey { 590 versionReq[RequirementComparatorKey] = *o.Value.Str 591 } 592 593 if o.Name == RequirementVersionKey { 594 versionReq[RequirementVersionKey] = *o.Value.Str 595 } 596 } 597 reqs = append(reqs, versionReq) 598 } 599 return reqs 600 } 601 602 // getSolveNode returns the solve node from the build expression. 603 // It returns an error if the solve node is not found. 604 // Currently, the solve node can have the name of "solve" or "solve_legacy". 605 // It expects the JSON representation of the build expression to be formatted as follows: 606 // 607 // { 608 // "let": { 609 // "runtime": { 610 // "solve": { 611 // } 612 // } 613 // } 614 // } 615 func (e *BuildExpression) getSolveNode() (*Ap, error) { 616 // First, try to find the solve node via lets. 617 if e.Let != nil { 618 solveAp, err := recurseLets(e.Let) 619 if err != nil { 620 return nil, errs.Wrap(err, "Could not recurse lets") 621 } 622 623 return solveAp, nil 624 } 625 626 // Search for solve node in the top level assignments. 627 for _, a := range e.Assignments { 628 if a.Assignment == nil { 629 continue 630 } 631 632 if a.Assignment.Name == "" { 633 continue 634 } 635 636 if a.Assignment.Value == nil { 637 continue 638 } 639 640 if a.Assignment.Value.Ap == nil { 641 continue 642 } 643 644 if a.Assignment.Value.Ap.Name == SolveFuncName || a.Assignment.Value.Ap.Name == SolveLegacyFuncName { 645 return a.Assignment.Value.Ap, nil 646 } 647 } 648 649 return nil, funcNodeNotFoundError 650 } 651 652 // recurseLets recursively searches for the solve node in the let statements. 653 // The solve node is specified by the name "runtime" and the function name "solve" 654 // or "solve_legacy". 655 func recurseLets(let *Let) (*Ap, error) { 656 for _, a := range let.Assignments { 657 if a.Value == nil { 658 continue 659 } 660 661 if a.Value.Ap == nil { 662 continue 663 } 664 665 if a.Name == "" { 666 continue 667 } 668 669 if a.Value.Ap.Name == SolveFuncName || a.Value.Ap.Name == SolveLegacyFuncName { 670 return a.Value.Ap, nil 671 } 672 } 673 674 // The highest level solve node is not found, so recurse into the next let. 675 if let.Let != nil { 676 return recurseLets(let.Let) 677 } 678 679 return nil, funcNodeNotFoundError 680 } 681 682 func (e *BuildExpression) getSolveNodeArguments() ([]*Value, error) { 683 solveAp, err := e.getSolveNode() 684 if err != nil { 685 return nil, errs.Wrap(err, "Could not get solve node") 686 } 687 688 return solveAp.Arguments, nil 689 } 690 691 func (e *BuildExpression) getSolveAtTimeValue() (*Value, error) { 692 solveAp, err := e.getSolveNode() 693 if err != nil { 694 return nil, errs.Wrap(err, "Could not get solve node") 695 } 696 697 for _, arg := range solveAp.Arguments { 698 if arg.Assignment != nil && arg.Assignment.Name == AtTimeKey { 699 return arg.Assignment.Value, nil 700 } 701 } 702 703 return nil, errs.New("Could not find %s", AtTimeKey) 704 } 705 706 func (e *BuildExpression) getPlatformsNode() (*[]*Value, error) { 707 solveAp, err := e.getSolveNode() 708 if err != nil { 709 return nil, errs.Wrap(err, "Could not get solve node") 710 } 711 712 for _, arg := range solveAp.Arguments { 713 if arg.Assignment == nil { 714 continue 715 } 716 717 if arg.Assignment.Name == PlatformsKey && arg.Assignment.Value != nil { 718 return arg.Assignment.Value.List, nil 719 } 720 } 721 722 return nil, errs.New("Could not find platforms node") 723 } 724 725 // Update updates the BuildExpression's requirements based on the operation and requirement. 726 func (e *BuildExpression) UpdateRequirement(operation types.Operation, requirement types.Requirement) error { 727 var err error 728 switch operation { 729 case types.OperationAdded: 730 err = e.addRequirement(requirement) 731 case types.OperationRemoved: 732 err = e.removeRequirement(requirement) 733 case types.OperationUpdated: 734 err = e.removeRequirement(requirement) 735 if err != nil { 736 break 737 } 738 err = e.addRequirement(requirement) 739 default: 740 return errs.New("Unsupported operation") 741 } 742 if err != nil { 743 return errs.Wrap(err, "Could not update BuildExpression's requirements") 744 } 745 746 return nil 747 } 748 749 func (e *BuildExpression) addRequirement(requirement types.Requirement) error { 750 obj := []*Var{ 751 {Name: RequirementNameKey, Value: &Value{Str: ptr.To(requirement.Name)}}, 752 {Name: RequirementNamespaceKey, Value: &Value{Str: ptr.To(requirement.Namespace)}}, 753 } 754 755 if requirement.Revision != nil { 756 obj = append(obj, &Var{Name: RequirementRevisionKey, Value: &Value{Int: requirement.Revision}}) 757 } 758 759 if requirement.VersionRequirement != nil { 760 values := []*Value{} 761 for _, r := range requirement.VersionRequirement { 762 values = append(values, &Value{Object: &[]*Var{ 763 {Name: RequirementComparatorKey, Value: &Value{Str: ptr.To(r[RequirementComparatorKey])}}, 764 {Name: RequirementVersionKey, Value: &Value{Str: ptr.To(r[RequirementVersionKey])}}, 765 }}) 766 } 767 obj = append(obj, &Var{Name: RequirementVersionRequirementsKey, Value: &Value{List: &values}}) 768 } 769 770 requirementsNode, err := e.getRequirementsNode() 771 if err != nil { 772 return errs.Wrap(err, "Could not get requirements node") 773 } 774 775 requirementsNode = append(requirementsNode, &Value{Object: &obj}) 776 777 arguments, err := e.getSolveNodeArguments() 778 if err != nil { 779 return errs.Wrap(err, "Could not get solve node arguments") 780 } 781 782 for _, arg := range arguments { 783 if arg.Assignment == nil { 784 continue 785 } 786 787 if arg.Assignment.Name == RequirementsKey { 788 arg.Assignment.Value.List = &requirementsNode 789 } 790 } 791 792 return nil 793 } 794 795 type RequirementNotFoundError struct { 796 Name string 797 *locale.LocalizedError // for legacy non-user-facing error usages 798 } 799 800 func (e *BuildExpression) removeRequirement(requirement types.Requirement) error { 801 requirementsNode, err := e.getRequirementsNode() 802 if err != nil { 803 return errs.Wrap(err, "Could not get requirements node") 804 } 805 806 var found bool 807 for i, r := range requirementsNode { 808 if r.Object == nil { 809 continue 810 } 811 812 for _, o := range *r.Object { 813 if o.Name == RequirementNameKey && *o.Value.Str == requirement.Name { 814 requirementsNode = append(requirementsNode[:i], requirementsNode[i+1:]...) 815 found = true 816 break 817 } 818 } 819 } 820 821 if !found { 822 return &RequirementNotFoundError{ 823 requirement.Name, 824 locale.NewInputError("err_remove_requirement_not_found", "", requirement.Name), 825 } 826 } 827 828 solveNode, err := e.getSolveNode() 829 if err != nil { 830 return errs.Wrap(err, "Could not get solve node") 831 } 832 833 for _, arg := range solveNode.Arguments { 834 if arg.Assignment == nil { 835 continue 836 } 837 838 if arg.Assignment.Name == RequirementsKey { 839 arg.Assignment.Value.List = &requirementsNode 840 } 841 } 842 843 return nil 844 } 845 846 func (e *BuildExpression) UpdatePlatform(operation types.Operation, platformID strfmt.UUID) error { 847 var err error 848 switch operation { 849 case types.OperationAdded: 850 err = e.addPlatform(platformID) 851 case types.OperationRemoved: 852 err = e.removePlatform(platformID) 853 default: 854 return errs.New("Unsupported operation") 855 } 856 if err != nil { 857 return errs.Wrap(err, "Could not update BuildExpression's platform") 858 } 859 860 return nil 861 } 862 863 func (e *BuildExpression) addPlatform(platformID strfmt.UUID) error { 864 platformsNode, err := e.getPlatformsNode() 865 if err != nil { 866 return errs.Wrap(err, "Could not get platforms node") 867 } 868 869 *platformsNode = append(*platformsNode, &Value{Str: ptr.To(platformID.String())}) 870 871 return nil 872 } 873 874 func (e *BuildExpression) removePlatform(platformID strfmt.UUID) error { 875 platformsNode, err := e.getPlatformsNode() 876 if err != nil { 877 return errs.Wrap(err, "Could not get platforms node") 878 } 879 880 var found bool 881 for i, p := range *platformsNode { 882 if p.Str == nil { 883 continue 884 } 885 886 if *p.Str == platformID.String() { 887 *platformsNode = append((*platformsNode)[:i], (*platformsNode)[i+1:]...) 888 found = true 889 break 890 } 891 } 892 893 if !found { 894 return errs.New("Could not find platform") 895 } 896 897 return nil 898 } 899 900 func (e *BuildExpression) SetDefaultTimestamp() error { 901 atTimeNode, err := e.getSolveAtTimeValue() 902 if err != nil { 903 return errs.Wrap(err, "Could not get %s node", AtTimeKey) 904 } 905 atTimeNode.Str = ptr.To("$" + AtTimeKey) 906 return nil 907 } 908 909 // MaybeSetDefaultTimestamp changes the solve node's "at_time" value to "$at_time" if and only if 910 // the current value is the given timestamp. 911 // Buildscripts prefer to use variables for at_time and define them outside the buildexpression as 912 // the expression's commit time. 913 // While modern buildexpressions use variables, older ones bake in the commit time. This function 914 // exists primarily to update those older buildexpressions for use in buildscripts. 915 func (e *BuildExpression) MaybeSetDefaultTimestamp(ts *strfmt.DateTime) error { 916 if ts == nil { 917 return nil // nothing to compare to 918 } 919 atTimeNode, err := e.getSolveAtTimeValue() 920 if err != nil { 921 return errs.Wrap(err, "Could not get %s node", AtTimeKey) 922 } 923 if strings.HasPrefix(*atTimeNode.Str, "$") { 924 return nil 925 } 926 if atTime, err := strfmt.ParseDateTime(*atTimeNode.Str); err == nil && atTime == *ts { 927 return e.SetDefaultTimestamp() 928 } else if err != nil { 929 return errs.Wrap(err, "Invalid timestamp: %s", *atTimeNode.Str) 930 } 931 return nil 932 } 933 934 // normalizeTimestamp normalizes the solve node's timestamp, if possible. 935 // Platform timestamps may differ from the strfmt.DateTime format. For example, Platform 936 // timestamps will have microsecond precision, while strfmt.DateTime will only have millisecond 937 // precision. This will affect comparisons between buildexpressions (which is normally done 938 // byte-by-byte). 939 func (e *BuildExpression) normalizeTimestamp() error { 940 atTimeNode, err := e.getSolveAtTimeValue() 941 if err != nil { 942 return errs.Wrap(err, "Could not get at time node") 943 } 944 945 if atTimeNode.Str != nil && !strings.HasPrefix(*atTimeNode.Str, "$") { 946 atTime, err := strfmt.ParseDateTime(*atTimeNode.Str) 947 if err != nil { 948 return errs.Wrap(err, "Invalid timestamp: %s", *atTimeNode.Str) 949 } 950 atTimeNode.Str = ptr.To(atTime.String()) 951 } 952 953 return nil 954 } 955 956 func (e *BuildExpression) Copy() (*BuildExpression, error) { 957 bytes, err := json.Marshal(e) 958 if err != nil { 959 return nil, errs.Wrap(err, "Failed to marshal build expression during copy") 960 } 961 return New(bytes) 962 } 963 964 func (e *BuildExpression) MarshalJSON() ([]byte, error) { 965 m := make(map[string]interface{}) 966 967 if e.Let != nil { 968 m["let"] = e.Let 969 } 970 971 for _, value := range e.Assignments { 972 if value.Assignment == nil { 973 continue 974 } 975 976 m[value.Assignment.Name] = value 977 } 978 979 return json.Marshal(m) 980 } 981 982 func (l *Let) MarshalJSON() ([]byte, error) { 983 m := make(map[string]interface{}) 984 985 if l.Let != nil { 986 m["let"] = l.Let 987 } 988 989 for _, v := range l.Assignments { 990 if v.Value == nil { 991 continue 992 } 993 994 m[v.Name] = v.Value 995 } 996 997 m["in"] = l.In 998 999 return json.Marshal(m) 1000 } 1001 1002 func (a *Var) MarshalJSON() ([]byte, error) { 1003 m := make(map[string]interface{}) 1004 m[a.Name] = a.Value 1005 return json.Marshal(m) 1006 } 1007 1008 func (v *Value) MarshalJSON() ([]byte, error) { 1009 switch { 1010 case v.Ap != nil: 1011 return json.Marshal(v.Ap) 1012 case v.List != nil: 1013 return json.Marshal(v.List) 1014 case v.Str != nil: 1015 return json.Marshal(strings.Trim(*v.Str, `"`)) 1016 case v.Null != nil: 1017 return json.Marshal(nil) 1018 case v.Assignment != nil: 1019 return json.Marshal(v.Assignment) 1020 case v.Float != nil: 1021 return json.Marshal(*v.Float) 1022 case v.Int != nil: 1023 return json.Marshal(*v.Int) 1024 case v.Object != nil: 1025 m := make(map[string]interface{}) 1026 for _, assignment := range *v.Object { 1027 m[assignment.Name] = assignment.Value 1028 } 1029 return json.Marshal(m) 1030 case v.Ident != nil: 1031 return json.Marshal(v.Ident) 1032 } 1033 return json.Marshal([]*Value{}) 1034 } 1035 1036 func (f *Ap) MarshalJSON() ([]byte, error) { 1037 m := make(map[string]interface{}) 1038 args := make(map[string]interface{}) 1039 for _, argument := range f.Arguments { 1040 switch { 1041 case argument.Assignment != nil: 1042 args[argument.Assignment.Name] = argument.Assignment.Value 1043 default: 1044 return nil, fmt.Errorf("Cannot marshal %v (arg %v)", f, argument) 1045 } 1046 } 1047 m[f.Name] = args 1048 return json.Marshal(m) 1049 } 1050 1051 func (i *In) MarshalJSON() ([]byte, error) { 1052 switch { 1053 case i.FuncCall != nil: 1054 return json.Marshal(i.FuncCall) 1055 case i.Name != nil: 1056 return json.Marshal("$" + *i.Name) 1057 } 1058 return nil, fmt.Errorf("Cannot marshal %v", i) 1059 }