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  }