github.com/juju/charm/v11@v11.2.0/overlay.go (about) 1 // Copyright 2019 Canonical Ltd. 2 // Licensed under the LGPLv3, see LICENCE file for details. 3 4 package charm 5 6 import ( 7 "encoding/base64" 8 "fmt" 9 "math" 10 "path/filepath" 11 "reflect" 12 "strings" 13 14 "github.com/juju/errors" 15 "github.com/mohae/deepcopy" 16 ) 17 18 // ExtractBaseAndOverlayParts splits the bundle data into a base and 19 // overlay-specific bundle so that their union yields bd. To decide whether a 20 // field is overlay-specific, the implementation uses reflection and 21 // recursively scans the BundleData fields looking for fields annotated with 22 // the "overlay-only: true" tag. 23 // 24 // To produce the base bundle, the original bundle is filtered and all 25 // overlay-specific values are set to the zero value for their type. To produce 26 // the overlay-specific bundle, we once again filter the original bundle but 27 // this time zero out fields that do not contain any descendant fields that are 28 // overlay-specific. 29 // 30 // To clarify how this method works let's consider a bundle created via the 31 // yaml blob below: 32 // 33 // applications: 34 // apache2: 35 // charm: cs:apache2-26 36 // offers: 37 // my-offer: 38 // endpoints: 39 // - apache-website 40 // - website-cache 41 // my-other-offer: 42 // endpoints: 43 // - apache-website 44 // series: bionic 45 // 46 // The "offers" and "endpoints" attributes are overlay-specific fields. If we 47 // were to run this method and then marshal the results back to yaml we would 48 // get: 49 // 50 // The base bundle: 51 // 52 // applications: 53 // apache2: 54 // charm: cs:apache2-26 55 // series: bionic 56 // 57 // The overlay-specific bundle: 58 // 59 // applications: 60 // apache2: 61 // offers: 62 // my-offer: 63 // endpoints: 64 // - apache-website 65 // - website-cache 66 // my-other-offer: 67 // endpoints: 68 // - apache-website 69 // 70 // The two bundles returned by this method are copies of the original bundle 71 // data and can thus be safely manipulated by the caller. 72 func ExtractBaseAndOverlayParts(bd *BundleData) (base, overlay *BundleData, err error) { 73 base = cloneBundleData(bd) 74 _ = visitField(&visitorContext{ 75 structVisitor: clearOverlayFields, 76 dropNonRequiredMapKeys: false, 77 }, base) 78 79 overlay = cloneBundleData(bd) 80 _ = visitField(&visitorContext{ 81 structVisitor: clearNonOverlayFields, 82 dropNonRequiredMapKeys: true, 83 }, overlay) 84 85 return base, overlay, nil 86 } 87 88 // cloneBundleData uses the gob package to perform a deep copy of bd. 89 func cloneBundleData(bd *BundleData) *BundleData { 90 return deepcopy.Copy(bd).(*BundleData) 91 } 92 93 // VerifyNoOverlayFieldsPresent scans the contents of bd and returns an error 94 // if the bundle contains any overlay-specific values. 95 func VerifyNoOverlayFieldsPresent(bd *BundleData) error { 96 var ( 97 errList []error 98 pathStack []string 99 ) 100 101 ctx := &visitorContext{ 102 structVisitor: func(ctx *visitorContext, val reflect.Value, typ reflect.Type) (foundOverlay bool) { 103 for i := 0; i < typ.NumField(); i++ { 104 structField := typ.Field(i) 105 106 // Skip non-exportable and empty fields 107 v := val.Field(i) 108 if !v.CanInterface() || isZero(v) { 109 continue 110 } 111 112 if isOverlayField(structField) { 113 errList = append( 114 errList, 115 fmt.Errorf( 116 "%s.%s can only appear in an overlay section", 117 strings.Join(pathStack, "."), 118 yamlName(structField), 119 ), 120 ) 121 foundOverlay = true 122 } 123 124 pathStack = append(pathStack, yamlName(structField)) 125 if visitField(ctx, v.Interface()) { 126 foundOverlay = true 127 } 128 pathStack = pathStack[:len(pathStack)-1] 129 } 130 return foundOverlay 131 }, 132 indexedElemPreVisitor: func(index interface{}) { 133 pathStack = append(pathStack, fmt.Sprint(index)) 134 }, 135 indexedElemPostVisitor: func(_ interface{}) { 136 pathStack = pathStack[:len(pathStack)-1] 137 }, 138 } 139 140 _ = visitField(ctx, bd) 141 if len(errList) == 0 { 142 return nil 143 } 144 145 return &VerificationError{errList} 146 } 147 148 func yamlName(structField reflect.StructField) string { 149 fields := strings.Split(structField.Tag.Get("yaml"), ",") 150 if len(fields) == 0 || fields[0] == "" { 151 return strings.ToLower(structField.Name) 152 } 153 154 return fields[0] 155 } 156 157 type visitorContext struct { 158 structVisitor func(ctx *visitorContext, val reflect.Value, typ reflect.Type) bool 159 160 // An optional pre/post visitor for indexable items (slices, maps) 161 indexedElemPreVisitor func(index interface{}) 162 indexedElemPostVisitor func(index interface{}) 163 164 dropNonRequiredMapKeys bool 165 } 166 167 // visitField invokes ctx.structVisitor(val) if v is a struct and returns back 168 // the visitor's result. On the other hand, if val is a slice or a map, 169 // visitField invoke specialized functions that support iterating such types. 170 func visitField(ctx *visitorContext, val interface{}) bool { 171 if val == nil { 172 return false 173 } 174 typ := reflect.TypeOf(val) 175 v := reflect.ValueOf(val) 176 177 // De-reference pointers 178 if v.Kind() == reflect.Ptr { 179 v = v.Elem() 180 if v.Kind() == reflect.Invalid { 181 return false 182 } 183 typ = v.Type() 184 } 185 186 switch typ.Kind() { 187 case reflect.Struct: 188 return ctx.structVisitor(ctx, v, typ) 189 case reflect.Map: 190 return visitFieldsInMap(ctx, v) 191 case reflect.Slice: 192 return visitFieldsInSlice(ctx, v) 193 } 194 195 // v is not a struct or something we can iterate to reach a struct 196 return false 197 } 198 199 // visitFieldsInMap iterates the map specified by val and recursively visits 200 // each map element. The returned value is the logical OR of the responses 201 // returned by visiting all map elements. 202 func visitFieldsInMap(ctx *visitorContext, val reflect.Value) (result bool) { 203 for _, key := range val.MapKeys() { 204 v := val.MapIndex(key) 205 if !v.CanInterface() { 206 continue 207 } 208 209 if ctx.indexedElemPreVisitor != nil { 210 ctx.indexedElemPreVisitor(key) 211 } 212 213 visRes := visitField(ctx, v.Interface()) 214 result = visRes || result 215 216 // If the map value is a non-scalar value and the visitor 217 // returned false (don't retain), consult the dropNonRequiredMapKeys 218 // hint to decide whether we need to delete the key from the map. 219 // 220 // This is required when splitting bundles into base/overlay 221 // bits as empty map values would be encoded as empty objects 222 // that the overlay merge code would mis-interpret as deletions. 223 if !visRes && isNonScalar(v) && ctx.dropNonRequiredMapKeys { 224 val.SetMapIndex(key, reflect.Value{}) 225 } 226 227 if ctx.indexedElemPostVisitor != nil { 228 ctx.indexedElemPostVisitor(key) 229 } 230 } 231 232 return result 233 } 234 235 // visitFieldsInSlice iterates the slice specified by val and recursively 236 // visits each element. The returned value is the logical OR of the responses 237 // returned by visiting all slice elements. 238 func visitFieldsInSlice(ctx *visitorContext, val reflect.Value) (result bool) { 239 for i := 0; i < val.Len(); i++ { 240 v := val.Index(i) 241 if !v.CanInterface() { 242 continue 243 } 244 245 if ctx.indexedElemPreVisitor != nil { 246 ctx.indexedElemPreVisitor(i) 247 } 248 249 result = visitField(ctx, v.Interface()) || result 250 251 if ctx.indexedElemPostVisitor != nil { 252 ctx.indexedElemPostVisitor(i) 253 } 254 } 255 256 return result 257 } 258 259 // clearOverlayFields is an implementation of structVisitor. It recursively 260 // visits all fields in the val struct and sets the ones that are tagged as 261 // overlay-only to the zero value for their particular type. 262 func clearOverlayFields(ctx *visitorContext, val reflect.Value, typ reflect.Type) (retainAncestors bool) { 263 for i := 0; i < typ.NumField(); i++ { 264 structField := typ.Field(i) 265 266 // Skip non-exportable and empty fields 267 v := val.Field(i) 268 if !v.CanInterface() || isZero(v) { 269 continue 270 } 271 272 // No need to recurse further down; just erase the field 273 if isOverlayField(structField) { 274 v.Set(reflect.Zero(v.Type())) 275 continue 276 } 277 278 _ = visitField(ctx, v.Interface()) 279 retainAncestors = true 280 } 281 return retainAncestors 282 } 283 284 // clearNonOverlayFields is an implementation of structVisitor. It recursively 285 // visits all fields in the val struct and sets any field that does not contain 286 // any overlay-only descendants to the zero value for its particular type. 287 func clearNonOverlayFields(ctx *visitorContext, val reflect.Value, typ reflect.Type) (retainAncestors bool) { 288 for i := 0; i < typ.NumField(); i++ { 289 structField := typ.Field(i) 290 291 // Skip non-exportable and empty fields 292 v := val.Field(i) 293 if !v.CanInterface() || isZero(v) { 294 continue 295 } 296 297 // If this is an overlay field we need to preserve it and all 298 // its ancestor fields up to the root. However, we still need 299 // to visit its descendants in case we need to clear additional 300 // non-overlay fields further down the tree. 301 isOverlayField := isOverlayField(structField) 302 if isOverlayField { 303 retainAncestors = true 304 } 305 306 target := v.Interface() 307 if retain := visitField(ctx, target); !isOverlayField && !retain { 308 v.Set(reflect.Zero(v.Type())) 309 continue 310 } 311 312 retainAncestors = true 313 } 314 return retainAncestors 315 } 316 317 // isOverlayField returns true if a struct field is tagged as overlay-only. 318 func isOverlayField(structField reflect.StructField) bool { 319 return structField.Tag.Get("source") == "overlay-only" 320 } 321 322 // isZero reports whether v is the zero value for its type. It panics if the 323 // argument is invalid. The implementation has been copied from the upstream Go 324 // repo as it has not made its way to a stable Go release yet. 325 func isZero(v reflect.Value) bool { 326 switch v.Kind() { 327 case reflect.Invalid: 328 return true 329 case reflect.Bool: 330 return !v.Bool() 331 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 332 return v.Int() == 0 333 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 334 return v.Uint() == 0 335 case reflect.Float32, reflect.Float64: 336 return math.Float64bits(v.Float()) == 0 337 case reflect.Complex64, reflect.Complex128: 338 c := v.Complex() 339 return math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0 340 case reflect.Array: 341 for i := 0; i < v.Len(); i++ { 342 if !isZero(v.Index(i)) { 343 return false 344 } 345 } 346 return true 347 case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer: 348 return v.IsNil() 349 case reflect.String: 350 return v.Len() == 0 351 case reflect.Struct: 352 for i := 0; i < v.NumField(); i++ { 353 if !isZero(v.Field(i)) { 354 return false 355 } 356 } 357 return true 358 default: 359 // This should never happens, but will act as a safeguard for 360 // later, as a default value doesn't makes sense here. 361 panic(fmt.Sprintf("unexpected value of type %s passed to isZero", v.Kind().String())) 362 } 363 } 364 365 // ReadAndMergeBundleData reads N bundle data sources, composes their contents 366 // together and returns the result. The first bundle data source is treated as 367 // a base bundle while subsequent bundle data sources are treated as overlays 368 // which are sequentially merged onto the base bundle. 369 // 370 // Before returning the merged bundle, ReadAndMergeBundleData will also attempt 371 // to resolve any include directives present in the machine annotations, 372 // application options and annotations. 373 // 374 // When merging an overlay into a base bundle the following rules apply for the 375 // BundleData struct fields: 376 // - if an overlay specifies a bundle-level series, it overrides the base bundle 377 // series. 378 // - overlay-defined relations are appended to the base bundle relations 379 // - overlay-defined machines overwrite the base bundle machines. 380 // - if an overlay defines an application that is not present in the base bundle, 381 // it will get appended to the application list. 382 // - if an overlay defines an empty application or saas value, it will be removed 383 // from the base bundle together with any associated relations. For example, to 384 // remove an application named "mysql" the following overlay snippet can be 385 // provided: 386 // applications: 387 // mysql: 388 // 389 // - if an overlay defines an application that is also present in the base bundle 390 // the two application specs are merged together (see following rules) 391 // 392 // ApplicationSpec merge rules: 393 // - if the overlay defines a value for a scalar or slice field, it will overwrite 394 // the value from the base spec (e.g. trust, series etc). 395 // - if the overlay specifies a nil/empty value for a map field, then the map 396 // field of the base spec will be cleared. 397 // - if the overlay specifies a non-empty value for a map field, its key/value 398 // tuples are iterated and: 399 // - if the value is nil/zero and the value is non-scalar, it is deleted from 400 // the base spec. 401 // - otherwise, the key/value is inserted into the base spec overwriting any 402 // existing entries. 403 func ReadAndMergeBundleData(sources ...BundleDataSource) (*BundleData, error) { 404 var allParts []*BundleDataPart 405 var partSrcIndex []int 406 for srcIndex, src := range sources { 407 if src == nil { 408 continue 409 } 410 411 for _, part := range src.Parts() { 412 allParts = append(allParts, part) 413 partSrcIndex = append(partSrcIndex, srcIndex) 414 } 415 } 416 417 if len(allParts) == 0 { 418 return nil, errors.NotValidf("malformed bundle: bundle is empty") 419 } 420 421 // Treat the first part as the base bundle 422 base := allParts[0] 423 if err := VerifyNoOverlayFieldsPresent(base.Data); err != nil { 424 return nil, errors.Trace(err) 425 } 426 427 // Merge parts and resolve include directives 428 for index, part := range allParts { 429 // Resolve any re-writing of normalisation that could cause the presence 430 // field to be out of sync with the actual bundle representation. 431 resolveOverlayPresenceFields(part) 432 433 if index != 0 { 434 if err := applyOverlay(base, part); err != nil { 435 return nil, errors.Trace(err) 436 } 437 } 438 439 // Relative include directives are resolved using the base path 440 // of the datasource that yielded this part 441 srcIndex := partSrcIndex[index] 442 incResolver := sources[srcIndex].ResolveInclude 443 basePath := sources[srcIndex].BasePath() 444 for app, appData := range base.Data.Applications { 445 if appData == nil { 446 return nil, errors.Errorf("base application %q has no body", app) 447 } 448 resolvedCharm, err := resolveRelativeCharmPath(basePath, appData.Charm) 449 if err != nil { 450 return nil, errors.Annotatef(err, "resolving relative charm path %q for application %q", appData.Charm, app) 451 } 452 appData.Charm = resolvedCharm 453 454 for k, v := range appData.Options { 455 newV, changed, err := resolveIncludes(incResolver, v) 456 if err != nil { 457 return nil, errors.Annotatef(err, "processing option %q for application %q", k, app) 458 } 459 if changed { 460 appData.Options[k] = newV 461 } 462 } 463 464 for k, v := range appData.Annotations { 465 newV, changed, err := resolveIncludes(incResolver, v) 466 if err != nil { 467 return nil, errors.Annotatef(err, "processing annotation %q for application %q", k, app) 468 } 469 if changed { 470 appData.Annotations[k] = newV 471 } 472 } 473 } 474 475 for machine, machineData := range base.Data.Machines { 476 if machineData == nil { 477 continue 478 } 479 480 for k, v := range machineData.Annotations { 481 newV, changed, err := resolveIncludes(incResolver, v) 482 if err != nil { 483 return nil, errors.Annotatef(err, "processing annotation %q for machine %q", k, machine) 484 } 485 if changed { 486 machineData.Annotations[k] = newV 487 } 488 } 489 } 490 } 491 492 return base.Data, nil 493 } 494 495 // resolveOverlayPresenceFields exists because we expose an internal bundle 496 // representation of a type out to the consumers of the library. This means it 497 // becomes very difficult to know what was re-written during the normalisation 498 // phase, without telling downstream consumers. 499 // 500 // The following attempts to guess when a normalisation has occurred, but the 501 // presence field map is out of sync with the new changes. 502 func resolveOverlayPresenceFields(base *BundleDataPart) { 503 applications := base.PresenceMap.forField("applications") 504 if len(applications) == 0 { 505 return 506 } 507 for name, app := range base.Data.Applications { 508 if !applications.fieldPresent(name) { 509 continue 510 } 511 512 presence := applications.forField(name) 513 // If the presence map contains scale, but doesn't contain num_units 514 // and if the app.Scale_ has been set to zero. We can then assume that a 515 // normalistion has occurred. 516 if presence.fieldPresent("scale") && !presence.fieldPresent("num_units") && app.Scale_ == 0 && app.NumUnits > 0 { 517 presence["num_units"] = presence["scale"] 518 } 519 } 520 } 521 522 func applyOverlay(base, overlay *BundleDataPart) error { 523 if overlay == nil || len(overlay.PresenceMap) == 0 { 524 return nil 525 } 526 if !overlay.PresenceMap.fieldPresent("applications") && len(overlay.Data.Applications) > 0 { 527 return errors.Errorf("bundle overlay file used deprecated 'services' key, this is not valid for bundle overlay files") 528 } 529 530 // Merge applications 531 if len(overlay.Data.Applications) != 0 { 532 if base.Data.Applications == nil { 533 base.Data.Applications = make(map[string]*ApplicationSpec, len(overlay.Data.Applications)) 534 } 535 536 fpm := overlay.PresenceMap.forField("applications") 537 for srcAppName, srcAppSpec := range overlay.Data.Applications { 538 // If the overlay map points to an empty object, delete 539 // it from the base bundle 540 if isZero(reflect.ValueOf(srcAppSpec)) { 541 delete(base.Data.Applications, srcAppName) 542 base.Data.Relations = removeRelations(base.Data.Relations, srcAppName) 543 continue 544 } 545 546 // If this is a new application just append it; otherwise 547 // recursively merge the two application specs. 548 dstAppSpec, defined := base.Data.Applications[srcAppName] 549 if !defined { 550 base.Data.Applications[srcAppName] = srcAppSpec 551 continue 552 } 553 554 mergeStructs(dstAppSpec, srcAppSpec, fpm.forField(srcAppName)) 555 } 556 } 557 558 // Merge SAAS blocks 559 if len(overlay.Data.Saas) != 0 { 560 if base.Data.Saas == nil { 561 base.Data.Saas = make(map[string]*SaasSpec, len(overlay.Data.Saas)) 562 } 563 564 fpm := overlay.PresenceMap.forField("saas") 565 for srcSaasName, srcSaasSpec := range overlay.Data.Saas { 566 // If the overlay map points to an empty object, delete 567 // it from the base bundle 568 if isZero(reflect.ValueOf(srcSaasSpec)) { 569 delete(base.Data.Saas, srcSaasName) 570 base.Data.Relations = removeRelations(base.Data.Relations, srcSaasName) 571 continue 572 } 573 574 // if this is a new saas block just append it; otherwise 575 // recursively merge the two saas specs. 576 dstSaasSpec, defined := base.Data.Saas[srcSaasName] 577 if !defined { 578 base.Data.Saas[srcSaasName] = srcSaasSpec 579 continue 580 } 581 582 mergeStructs(dstSaasSpec, srcSaasSpec, fpm.forField(srcSaasName)) 583 } 584 } 585 586 // If series is set in the config, it overrides the bundle. 587 if series := overlay.Data.Series; series != "" { 588 base.Data.Series = series 589 } 590 591 // Append any additional relations. 592 base.Data.Relations = append(base.Data.Relations, overlay.Data.Relations...) 593 594 // Override machine definitions. 595 if machines := overlay.Data.Machines; machines != nil { 596 base.Data.Machines = machines 597 } 598 599 return nil 600 } 601 602 // removeRelations removes any relation defined in data that references 603 // the application appName. 604 func removeRelations(data [][]string, appName string) [][]string { 605 var result [][]string 606 for _, relation := range data { 607 // Keep the dud relation in the set, it will be caught by the bundle 608 // verify code. 609 if len(relation) == 2 { 610 left, right := relation[0], relation[1] 611 if left == appName || strings.HasPrefix(left, appName+":") || 612 right == appName || strings.HasPrefix(right, appName+":") { 613 continue 614 } 615 } 616 result = append(result, relation) 617 } 618 return result 619 } 620 621 // mergeStructs iterates the fields of srcStruct and merges them into the 622 // equivalent fields of dstStruct using the following rules: 623 // 624 // - if src defines a value for a scalar or slice field, it will overwrite 625 // the value from the dst (e.g. trust, series etc). 626 // - if the src specifies a nil/empty value for a map field, then the map 627 // field of dst will be cleared. 628 // - if the src specifies a non-empty value for a map field, its key/value 629 // tuples are iterated and: 630 // - if the value is nil/zero and non-scalar, it is deleted from the dst map. 631 // - otherwise, the key/value is inserted into the dst map overwriting any 632 // existing entries. 633 func mergeStructs(dstStruct, srcStruct interface{}, fpm FieldPresenceMap) { 634 dst := reflect.ValueOf(dstStruct) 635 src := reflect.ValueOf(srcStruct) 636 typ := src.Type() 637 638 // Dereference pointers 639 if src.Kind() == reflect.Ptr { 640 src = src.Elem() 641 typ = src.Type() 642 } 643 if dst.Kind() == reflect.Ptr { 644 dst = dst.Elem() 645 } 646 dstTyp := dst.Type() 647 648 // Sanity check 649 if typ.Kind() != reflect.Struct || typ != dstTyp { 650 panic(errors.Errorf("BUG: source/destination type mismatch; expected destination to be a %q; got %q", typ.Name(), dstTyp.Name())) 651 } 652 653 for i := 0; i < typ.NumField(); i++ { 654 // Skip non-exportable fields 655 structField := typ.Field(i) 656 srcVal := src.Field(i) 657 if !srcVal.CanInterface() { 658 continue 659 } 660 661 fieldName := yamlName(structField) 662 if !fpm.fieldPresent(fieldName) { 663 continue 664 } 665 666 switch srcVal.Kind() { 667 case reflect.Map: 668 // If a nil/empty map is provided then clear the destination map. 669 if isZero(srcVal) { 670 dst.Field(i).Set(reflect.MakeMap(srcVal.Type())) 671 continue 672 } 673 674 dstMap := dst.Field(i) 675 if dstMap.IsNil() { 676 dstMap.Set(reflect.MakeMap(srcVal.Type())) 677 } 678 for _, srcKey := range srcVal.MapKeys() { 679 // If the key points to an empty non-scalar value delete it from the dst map 680 srcMapVal := srcVal.MapIndex(srcKey) 681 if isZero(srcMapVal) && isNonScalar(srcMapVal) { 682 // Setting an empty value effectively deletes the key from the map 683 dstMap.SetMapIndex(srcKey, reflect.Value{}) 684 continue 685 } 686 687 dstMap.SetMapIndex(srcKey, srcMapVal) 688 } 689 case reflect.Slice: 690 dst.Field(i).Set(srcVal) 691 default: 692 dst.Field(i).Set(srcVal) 693 } 694 } 695 } 696 697 // isNonScalar returns true if val is a non-scalar value such as a pointer, 698 // struct, map or slice. 699 func isNonScalar(val reflect.Value) bool { 700 kind := val.Kind() 701 702 if kind == reflect.Interface { 703 kind = reflect.TypeOf(val).Kind() 704 } 705 706 switch kind { 707 case reflect.Ptr, reflect.Struct, 708 reflect.Map, reflect.Slice, reflect.Array: 709 return true 710 default: 711 return false 712 } 713 } 714 715 // resolveIncludes operates on v which is expected to be string. It checks the 716 // value for the presence of an include directive. If such a directive is 717 // located, resolveIncludes invokes the provided includeResolver and returns 718 // back its output after applying the appropriate encoding for the directive. 719 func resolveIncludes(includeResolver func(path string) ([]byte, error), v interface{}) (string, bool, error) { 720 directives := []struct { 721 directive string 722 encoder func([]byte) string 723 }{ 724 { 725 directive: "include-file://", 726 encoder: func(d []byte) string { 727 return string(d) 728 }, 729 }, 730 { 731 directive: "include-base64://", 732 encoder: base64.StdEncoding.EncodeToString, 733 }, 734 } 735 736 val, isString := v.(string) 737 if !isString { 738 return "", false, nil 739 } 740 741 for _, dir := range directives { 742 if !strings.HasPrefix(val, dir.directive) { 743 continue 744 } 745 746 path := val[len(dir.directive):] 747 data, err := includeResolver(path) 748 if err != nil { 749 return "", false, errors.Annotatef(err, "resolving include %q", path) 750 } 751 752 return dir.encoder(data), true, nil 753 } 754 755 return val, false, nil 756 } 757 758 // resolveRelativeCharmPath resolves charmURL into an absolute path relative 759 // to basePath if charmURL contains a relative path. Otherwise, the function 760 // returns back the original charmURL. 761 // 762 // Note: this function will only resolve paths. It will not check whether the 763 // referenced charm path actually exists. That is the job of the bundle 764 // validator. 765 func resolveRelativeCharmPath(basePath, charmURL string) (string, error) { 766 // We don't need to do anything for non-relative paths. 767 if !strings.HasPrefix(charmURL, ".") { 768 return charmURL, nil 769 } 770 771 return filepath.Abs(filepath.Join(basePath, charmURL)) 772 }