github.com/Schaudge/grailbase@v0.0.0-20240223061707-44c758a471c0/config/profile.go (about)

     1  // Copyright 2019 GRAIL, Inc. All rights reserved.
     2  // Use of this source code is governed by the Apache 2.0
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package config is used to configure software systems. A
     6  // configuration managed by package config is called a profile. The
     7  // binary that loads a profile declares a set of named, global
     8  // objects through the APIs in this package. A profile configures
     9  // these objects (objects may depend on each other, forming a DAG)
    10  // and lets the user retrieve configured objects through its API.
    11  //
    12  // The semantics of profiles provide the kind of flexibility that is
    13  // often required in operational contexts. Profiles define a
    14  // principled overriding so that a base configuration can be extended
    15  // by the user, either by composing multiple configuration or by
    16  // editing the configuration through a command-line integration.
    17  // Profiles may also derive multiple instances from the same base
    18  // instance in order to provide small variations on instance
    19  // configuration. Profiles define a concrete syntax so that they may
    20  // be stored (e.g., centrally) or transmitted over a network
    21  // connection (e.g., to bootstrap a remote binary with a particular
    22  // configuration). Profiles are also self-documenting in the manner
    23  // of Go's flag package. Profiles are resolved lazily, and thus
    24  // maintain configuration for unknown instances, so long as these are
    25  // never retrieved. This permits a single profile to be reused across
    26  // many binaries without concern for compatibility.
    27  //
    28  // Profile syntax
    29  //
    30  // A profile contains a set of clauses, or directives. Each clause
    31  // either declares a new instance or configures an existing instance.
    32  // Clauses are interpreted in order, top-to-bottom, and later
    33  // configurations override earlier configurations. These semantics
    34  // accommodate for "overlays", where for example a user profile is
    35  // loaded after a base profile to provide customization. Within
    36  // GRAIL, a base profile is declared in the standard package
    37  // github.com/Schaudge/grailbase/grail, which also loads a user
    38  // profile from $HOME/grail/profile.
    39  //
    40  // A parameter is set by the directive param. For example, the
    41  // following sets the parallelism parameter on the instance bigslice
    42  // to 1024:
    43  //
    44  //	param bigslice parallelism = 1024
    45  //
    46  // The values supported by profiles are: integers, strings, booleans, floats,
    47  // and indirections (naming other instances). The following shows an example
    48  // of each:
    49  //
    50  //	param bigslice load-factor = 0.8
    51  //	param bigmachine/ec2system username = "marius"
    52  //	param bigmachine/ec2system on-demand = false
    53  //	param s3 retries = 8
    54  //
    55  // As a shortcut, parameters for the same instance may be grouped
    56  // together. For example, the two parameters on the instance
    57  // bigmachine/ec2system may be grouped together as follows:
    58  //
    59  //	param bigmachine/ec2system (
    60  //		username = "marius"
    61  //		on-demand = false
    62  //	)
    63  //
    64  // Instances may refer to each other by name. The following
    65  // configures the aws/ticket instance to use a particular ticket path
    66  // and region; it then configures bigmachine/ec2system to use this
    67  // AWS session.
    68  //
    69  //	param aws/ticket (
    70  //		path = "eng/dev/aws"
    71  //		region = "us-west-2"
    72  //	)
    73  //
    74  //	param bigmachine/ec2system aws = aws/ticket
    75  //
    76  // Profiles may also define new instances with different configurations.
    77  // This is done via the instance directive. For example, if we wanted to
    78  // declare a new bigmachine/ec2system that used on-demand instances
    79  // instead of spot instances, we could define a profile as follows:
    80  //
    81  //	instance bigmachine/ec2ondemand bigmachine/ec2system
    82  //
    83  //	param bigmachine/ec2ondemand on-demand = false
    84  //
    85  // Since it is common to declare an instance and configure it, the
    86  // profile syntax provides an affordance for combining the two,
    87  // also through grouping. The above is equivalent to:
    88  //
    89  //	instance bigmachine/ec2ondemand bigmachine/ec2system (
    90  //		on-demand = false
    91  //		username = "marius-ondemand"
    92  //		// (any other configuration to be changed from the base)
    93  //	)
    94  //
    95  // New instances may depend on any instance. For example, the above
    96  // may be further customized as follows.
    97  //
    98  //	instance bigmachine/ec2ondemand-anonymous bigmachine/ec2ondemand (
    99  //		username = "anonymous"
   100  //	)
   101  //
   102  // Customization through flags
   103  //
   104  // Profile parameters may be adjusted via command-line flags. Profile
   105  // provides utility methods to register flags and interpret them. See
   106  // the appropriate methods for more details. Any parameter may be
   107  // set through the provided command-line flags by specifying the path
   108  // to the parameter. As an example, the following invocations customize
   109  // aspects of the above profile.
   110  //
   111  //	# Override the ticket path and the default ec2system username.
   112  //	# -set flags are interpreted in order, and the following is equivalent
   113  //	# to the clauses
   114  //	# 	param aws/ticket path = "eng/prod/aws"
   115  //	#	param bigmachine/ec2system username = "anonymous"
   116  //	$ program -set aws/ticket.path=eng/prod/aws -set bigmachine/ec2system.username=anonymous
   117  //
   118  //	# User the aws/env instance instead of aws/ticket, as above.
   119  //	# The type of a flag is interpreted based on underlying type, so
   120  //	# the following is equivalent to the clause
   121  //	# 	param bigmachine/ec2system aws = aws/env
   122  //	$ program -set bigmachine/ec2system.aws=aws/env
   123  //
   124  // Default profile
   125  //
   126  // Package config also defines a default profile and a set of package-level
   127  // methods that operate on this profile. Most users should make use only
   128  // of the default profile. This package also exports an http handler on the
   129  // path /debug/profile on the default (global) ServeMux, which returns the
   130  // global profile in parseable form.
   131  package config
   132  
   133  import (
   134  	"fmt"
   135  	"io"
   136  	"log"
   137  	"net/http"
   138  	"reflect"
   139  	"runtime"
   140  	"sort"
   141  	"strconv"
   142  	"strings"
   143  	"sync"
   144  	"unsafe"
   145  )
   146  
   147  func init() {
   148  	http.HandleFunc("/debug/profile", func(w http.ResponseWriter, r *http.Request) {
   149  		if err := Application().PrintTo(w); err != nil {
   150  			http.Error(w, fmt.Sprintf("writing profile: %v", err), http.StatusInternalServerError)
   151  		}
   152  	})
   153  }
   154  
   155  type (
   156  	// Profile stores a set of parameters and configures instances based
   157  	// on these. It is the central data structure of this package as
   158  	// detailed in the package docs. Each Profile instance maintains its
   159  	// own set of instances. Most users should use the package-level
   160  	// methods that operate on the default profile.
   161  	Profile struct {
   162  		// The following are used by the flag registration and
   163  		// handling mechanism.
   164  		flags    flags
   165  		flagDump bool
   166  
   167  		globals map[string]typedConstructor
   168  
   169  		mu        sync.Mutex
   170  		instances instances
   171  		cached    map[string]interface{}
   172  	}
   173  	// typedConstructor is a type-erased *Constructor[T].
   174  	typedConstructor struct {
   175  		constructor *Constructor[any]
   176  		typ         reflect.Type
   177  	}
   178  )
   179  
   180  // New creates and returns a new profile, installing all currently
   181  // registered global objects. Global objects registered after a call
   182  // to New are not reflected in the returned profile.
   183  func New() *Profile {
   184  	p := &Profile{
   185  		globals:   make(map[string]typedConstructor),
   186  		instances: make(instances),
   187  		cached:    make(map[string]interface{}),
   188  	}
   189  
   190  	globalsMu.Lock()
   191  	for name, ct := range globals {
   192  		p.globals[name] = typedConstructor{newConstructor(), ct.typ}
   193  		ct.configure(p.globals[name].constructor)
   194  	}
   195  	globalsMu.Unlock()
   196  
   197  	// Make a shadow instance for each global instance. This helps keep
   198  	// the downstream code simple. We also populate any defaults
   199  	// provided by the configured instances, so that printing the
   200  	// profile shows the true, global (and re-createable) state of the
   201  	// profile.
   202  	for name, global := range p.globals {
   203  		inst := &instance{name: name, params: make(map[string]interface{})}
   204  		for pname, param := range global.constructor.params {
   205  			// Special case for interface params: use their indirections
   206  			// instead of their value; this is always how they are satisfied
   207  			// in practice.
   208  			if param.kind == paramInterface {
   209  				inst.params[pname] = def{param.ifaceindir}
   210  			} else {
   211  				inst.params[pname] = def{param.Interface()}
   212  			}
   213  		}
   214  		p.instances[name] = inst
   215  	}
   216  
   217  	// Populate defaults as empty instance declarations, effectively
   218  	// redirecting the instance and making it overridable, etc.
   219  	globalsMu.Lock()
   220  	for name, parent := range defaults {
   221  		p.instances[name] = &instance{name: name, parent: parent}
   222  	}
   223  	globalsMu.Unlock()
   224  
   225  	return p
   226  }
   227  
   228  // Set sets the value of the parameter at the provided path to the
   229  // provided value, which is intepreted according to the type of the
   230  // parameter at that path. Set returns an error if the parameter does
   231  // not exist or if the value cannot be parsed into the expected type.
   232  // The path is a set of identifiers separated by dots ("."). Paths may
   233  // traverse multiple indirections.
   234  func (p *Profile) Set(path string, value string) error {
   235  	p.mu.Lock()
   236  	defer p.mu.Unlock()
   237  
   238  	// Special case: toplevel instance assignment.
   239  	elems := strings.Split(path, ".")
   240  	if len(elems) == 1 {
   241  		if value == "" || value == "nil" {
   242  			return fmt.Errorf(
   243  				"%s: top-level path may only be set to an instance; cannot be set to nil/empty",
   244  				elems[0],
   245  			)
   246  		}
   247  		p.instances[elems[0]] = &instance{
   248  			name:   elems[0],
   249  			parent: value,
   250  		}
   251  		return nil
   252  	}
   253  
   254  	// Otherwise infer the type and parse it accordingly.
   255  	inst := p.instances[elems[0]]
   256  	if inst == nil {
   257  		return fmt.Errorf("%s: path not found: instance not found", path)
   258  	}
   259  	for i := 1; i < len(elems)-1; i++ {
   260  		var v interface{}
   261  		for {
   262  			var ok bool
   263  			v, ok = inst.params[elems[i]]
   264  			if ok {
   265  				break
   266  			}
   267  			if inst.parent == "" || p.instances[inst.parent] == nil {
   268  				return fmt.Errorf("%s: path not found: instance not found: %s", path, strings.Join(elems[:i], "."))
   269  			}
   270  			inst = p.instances[inst.parent]
   271  		}
   272  		v, _ = unwrap(v)
   273  		indir, ok := v.(indirect)
   274  		if !ok {
   275  			return fmt.Errorf("%s: path not found: %s is not an instance", path, strings.Join(elems[:i], "."))
   276  		}
   277  		inst = p.instances[string(indir)]
   278  		if inst == nil {
   279  			return fmt.Errorf("%s: path not found: instance not found: %s", path, strings.Join(elems[:i], "."))
   280  		}
   281  	}
   282  
   283  	name := elems[len(elems)-1]
   284  	for {
   285  		if _, ok := inst.params[name]; ok {
   286  			break
   287  		}
   288  		if inst.parent == "" || p.instances[inst.parent] == nil {
   289  			return fmt.Errorf("%s: no such parameter", path)
   290  		}
   291  		inst = p.instances[inst.parent]
   292  	}
   293  
   294  	switch v, _ := unwrap(inst.params[name]); v.(type) {
   295  	case indirect:
   296  		// TODO(marius): validate that it's a good identifier?
   297  		if value == "nil" {
   298  			value = ""
   299  		}
   300  		inst.params[name] = indirect(value)
   301  	case string:
   302  		inst.params[name] = value
   303  	case bool:
   304  		v, err := strconv.ParseBool(value)
   305  		if err != nil {
   306  			return fmt.Errorf("param %s is a bool, but could not parse %s into bool: %v", path, value, err)
   307  		}
   308  		inst.params[name] = v
   309  	case int:
   310  		v, err := strconv.ParseInt(value, 0, 64)
   311  		if err != nil {
   312  			return fmt.Errorf("param %s is an int, but could not parse %s into int: %v", path, value, err)
   313  		}
   314  		inst.params[name] = int(v)
   315  	case float64:
   316  		v, err := strconv.ParseFloat(value, 64)
   317  		if err != nil {
   318  			return fmt.Errorf("param %s is a float, but could not parse %s into float: %v", path, value, err)
   319  		}
   320  		inst.params[name] = v
   321  	default:
   322  		panic(fmt.Sprintf("%T", v))
   323  	}
   324  	return nil
   325  }
   326  
   327  // Get returns the value of the configured parameter at the provided
   328  // dot-separated path.
   329  func (p *Profile) Get(path string) (value string, ok bool) {
   330  	p.mu.Lock()
   331  	defer p.mu.Unlock()
   332  
   333  	var (
   334  		elems = strings.Split(path, ".")
   335  		inst  = p.instances[elems[0]]
   336  	)
   337  	if inst == nil {
   338  		return "", false
   339  	}
   340  	// Special case: toplevels are "set" only if they are inherited.
   341  	// We return only the first level of inheritance.
   342  	if len(elems) == 1 {
   343  		return inst.parent, inst.parent != ""
   344  	}
   345  
   346  	for i := 1; i < len(elems)-1; i++ {
   347  		elem := elems[i]
   348  		for inst != nil && inst.params[elem] == nil {
   349  			inst = p.instances[inst.parent]
   350  		}
   351  		if inst == nil {
   352  			return "", false
   353  		}
   354  		v, _ := unwrap(inst.params[elem])
   355  		indir, ok := v.(indirect)
   356  		if !ok {
   357  			return "", false
   358  		}
   359  		inst = p.instances[string(indir)]
   360  		if inst == nil {
   361  			return "", false
   362  		}
   363  	}
   364  
   365  	for elem := elems[len(elems)-1]; inst != nil; inst = p.instances[inst.parent] {
   366  		if v, ok := inst.params[elem]; ok {
   367  			v, _ = unwrap(v)
   368  			return fmt.Sprintf("%#v", v), true
   369  		}
   370  	}
   371  	return "", false
   372  }
   373  
   374  // Merge merges the instance parameters in profile q into p,
   375  // so that parameters defined in q override those in p.
   376  func (p *Profile) Merge(q *Profile) {
   377  	defer lock(p, q)()
   378  	for _, inst := range q.instances {
   379  		p.instances.Merge(inst)
   380  	}
   381  }
   382  
   383  // Parse parses a profile from the provided reader into p. On
   384  // success, the instances defined by the profile in src are merged into
   385  // profile p. If the reader implements
   386  //
   387  //	Name() string
   388  //
   389  // then the result of calling Name is used as a filename to provide
   390  // positional information in errors.
   391  func (p *Profile) Parse(r io.Reader) error {
   392  	insts, err := parse(r)
   393  	if err != nil {
   394  		return err
   395  	}
   396  	p.mu.Lock()
   397  	defer p.mu.Unlock()
   398  	for _, inst := range insts {
   399  		p.instances.Merge(inst)
   400  	}
   401  	return nil
   402  }
   403  
   404  // InstanceNames returns the set of names of instances provided by p.
   405  func (p *Profile) InstanceNames() map[string]struct{} {
   406  	p.mu.Lock()
   407  	defer p.mu.Unlock()
   408  	names := make(map[string]struct{}, len(p.instances))
   409  	for name := range p.instances {
   410  		names[name] = struct{}{}
   411  	}
   412  	return names
   413  }
   414  
   415  // Instance retrieves the named instance from this profile into the
   416  // pointer ptr. All of its parameters are fully resolved and the
   417  // underlying global object is instantiated according to the desired
   418  // parameterization. Instance panics if ptr is not a pointer type. If the
   419  // type of the instance cannot be assigned to the value pointed to by
   420  // ptr, an error is returned. Since such errors may occur
   421  // transitively (e.g., the type of an instance required by another
   422  // instance may be wrong), the source location of the type mismatch
   423  // is included in the error to help with debugging. Instances are
   424  // cached and are only initialized the first time they are requested.
   425  //
   426  // If ptr is nil, the instance is created without populating the pointer.
   427  func (p *Profile) Instance(name string, ptr interface{}) error {
   428  	var ptrv reflect.Value
   429  	if ptr != nil {
   430  		ptrv = reflect.ValueOf(ptr)
   431  		if ptrv.Kind() != reflect.Ptr {
   432  			panic("profile.Get: not a pointer")
   433  		}
   434  	}
   435  	_, file, line, _ := runtime.Caller(1)
   436  	p.mu.Lock()
   437  	err := p.getLocked(name, ptrv, file, line)
   438  	p.mu.Unlock()
   439  	return err
   440  }
   441  
   442  func (p *Profile) PrintTo(w io.Writer) error {
   443  	p.mu.Lock()
   444  	defer p.mu.Unlock()
   445  	instances := p.sorted()
   446  	for _, inst := range instances {
   447  		if len(inst.params) == 0 && inst.parent == "" {
   448  			continue
   449  		}
   450  		if _, err := fmt.Fprintln(w, inst.SyntaxString(p.docs(inst))); err != nil {
   451  			return err
   452  		}
   453  	}
   454  	return nil
   455  }
   456  
   457  // docs collects the documentation strings for inst and its parameters.
   458  // Special key "" holds the documentation for the instance itself.
   459  // Remaining keys are inst's param names.
   460  func (p *Profile) docs(inst *instance) map[string]string {
   461  	global, ok := p.globals[inst.name]
   462  	if !ok {
   463  		return nil
   464  	}
   465  	docs := map[string]string{"": global.constructor.Doc}
   466  	for name, param := range global.constructor.params {
   467  		docs[name] = param.help
   468  
   469  		var paramType reflect.Type
   470  		if param.kind == paramInterface {
   471  			paramType = reflect.ValueOf(param.ifaceptr).Elem().Type()
   472  		} else {
   473  			paramType = reflect.TypeOf(param.Interface())
   474  		}
   475  		var insts []string
   476  		// TODO: This is asymptotically slow. Make it faster, perhaps with indexing.
   477  		for name, other := range p.globals {
   478  			if name != inst.name && other.typ != nil && other.typ.AssignableTo(paramType) {
   479  				insts = append(insts, name)
   480  			}
   481  		}
   482  		sort.Strings(insts)
   483  		if len(insts) > 0 {
   484  			docs[name] += "\n\nAvailable instances:\n\t" + strings.Join(insts, "\n\t")
   485  		}
   486  	}
   487  	return docs
   488  }
   489  
   490  func (p *Profile) getLocked(name string, ptr reflect.Value, file string, line int) error {
   491  	if v, ok := p.cached[name]; ok {
   492  		return assign(name, v, ptr, file, line)
   493  	}
   494  	inst := p.instances[name]
   495  	if inst == nil {
   496  		return fmt.Errorf("no instance named %q", name)
   497  	}
   498  
   499  	resolved := make(map[string]interface{})
   500  	for {
   501  		for k, v := range inst.params {
   502  			if _, ok := resolved[k]; !ok {
   503  				resolved[k] = v
   504  			}
   505  		}
   506  		if inst.parent == "" {
   507  			break
   508  		}
   509  		parent := p.instances[inst.parent]
   510  		if parent == nil {
   511  			return fmt.Errorf("no such instance: %q", inst.parent)
   512  		}
   513  		inst = parent
   514  	}
   515  
   516  	if _, ok := p.globals[inst.name]; !ok {
   517  		return fmt.Errorf("missing global instance: %q", inst.name)
   518  	}
   519  	// Even though we have a configured instance in globals, we create
   520  	// a new one to reduce the changes that multiple instances clobber
   521  	// each other.
   522  	globalsMu.Lock()
   523  	ct := globals[inst.name]
   524  	globalsMu.Unlock()
   525  	instance := newConstructor()
   526  	ct.configure(instance)
   527  
   528  	for pname, param := range instance.params {
   529  		val, ok := resolved[pname]
   530  		if !ok {
   531  			continue
   532  		}
   533  		// Skip defaults except for paramInterface since these need to be resolved.
   534  		if _, ok := val.(def); ok && param.kind != paramInterface {
   535  			continue
   536  		}
   537  		val, _ = unwrap(val)
   538  		if indir, ok := val.(indirect); ok {
   539  			if param.kind != paramInterface {
   540  				return fmt.Errorf("resolving %s.%s: cannot indirect parameters of type %T", name, pname, val)
   541  			}
   542  			if indir == "" {
   543  				typ := reflect.ValueOf(param.ifaceptr).Elem().Type()
   544  				if !isNilAssignable(typ) {
   545  					return fmt.Errorf("resolving %s.%s: cannot assign nil/empty to parameter of type %s", name, pname, typ)
   546  				}
   547  				continue // nil: skip
   548  			}
   549  			if err := p.getLocked(string(indir), reflect.ValueOf(param.ifaceptr), param.file, param.line); err != nil {
   550  				return err
   551  			}
   552  			continue
   553  		}
   554  
   555  		switch param.kind {
   556  		case paramInterface:
   557  			var (
   558  				dst = reflect.ValueOf(param.ifaceptr).Elem()
   559  				src = reflect.ValueOf(val)
   560  			)
   561  			// TODO: include embedded fields, etc?
   562  			if !src.Type().AssignableTo(dst.Type()) {
   563  				return fmt.Errorf("%s.%s: cannot assign value of type %s to type %s", name, pname, src.Type(), dst.Type())
   564  			}
   565  			dst.Set(src)
   566  		case paramInt:
   567  			ival, ok := val.(int)
   568  			if !ok {
   569  				return fmt.Errorf("%s.%s: wrong parameter type: expected int, got %T", name, pname, val)
   570  			}
   571  			*param.intptr = ival
   572  		case paramFloat:
   573  			switch tv := val.(type) {
   574  			case int:
   575  				*param.floatptr = float64(tv)
   576  			case float64:
   577  				*param.floatptr = tv
   578  			default:
   579  				return fmt.Errorf("%s.%s: wrong parameter type: expected float64, got %T", name, pname, val)
   580  			}
   581  		case paramString:
   582  			sval, ok := val.(string)
   583  			if !ok {
   584  				return fmt.Errorf("%s.%s: wrong parameter type: expected string, got %T", name, pname, val)
   585  			}
   586  			*param.strptr = sval
   587  		case paramBool:
   588  			bval, ok := val.(bool)
   589  			if !ok {
   590  				return fmt.Errorf("%s.%s: wrong parameter type: expected bool, got %T", name, pname, val)
   591  			}
   592  			*param.boolptr = bval
   593  		default:
   594  			panic(param.kind)
   595  		}
   596  	}
   597  
   598  	v, err := instance.New()
   599  	if err != nil {
   600  		return err
   601  	}
   602  	p.cached[name] = v
   603  	return assign(name, v, ptr, file, line)
   604  }
   605  
   606  func (p *Profile) sorted() []*instance {
   607  	instances := make([]*instance, 0, len(p.instances))
   608  	for _, inst := range p.instances {
   609  		instances = append(instances, inst)
   610  	}
   611  	sort.Slice(instances, func(i, j int) bool {
   612  		return instances[i].name < instances[j].name
   613  	})
   614  	return instances
   615  }
   616  
   617  var (
   618  	defaultInit     sync.Once
   619  	defaultInstance *Profile
   620  )
   621  
   622  // NewDefault is used to initialize the default profile. It can be
   623  // set by a program before the application profile has been created
   624  // in order to support asynchronous profile retrieval.
   625  var NewDefault = New
   626  
   627  // Application returns the default application profile. The default
   628  // instance is initialized during the first call to Application (and thus
   629  // of the package-level methods that operate on the default profile).
   630  // Because of this, Application (and the other package-level methods
   631  // operating on the default profile) should not be called during
   632  // package initialization as doing so means that some global objects
   633  // may not yet have been registered.
   634  func Application() *Profile {
   635  	// TODO(marius): freeze registration after this?
   636  	defaultInit.Do(func() {
   637  		defaultInstance = NewDefault()
   638  	})
   639  	return defaultInstance
   640  }
   641  
   642  // Merge merges profile p into the default profile.
   643  // See Profile.Merge for more details.
   644  func Merge(p *Profile) {
   645  	Application().Merge(p)
   646  }
   647  
   648  // Parse parses the profile in reader r into the default
   649  // profile. See Profile.Parse for more details.
   650  func Parse(r io.Reader) error {
   651  	return Application().Parse(r)
   652  }
   653  
   654  // Instance retrieves the instance with the provided name into the
   655  // provided pointer from the default profile. See Profile.Instance for
   656  // more details.
   657  func Instance(name string, ptr interface{}) error {
   658  	return Application().Instance(name, ptr)
   659  }
   660  
   661  // Set sets the value of the parameter named by the provided path on
   662  // the default profile. See Profile.Set for more details.
   663  func Set(path, value string) error {
   664  	return Application().Set(path, value)
   665  }
   666  
   667  // Get retrieves the value of the parameter named by the provided path
   668  // on the default profile.
   669  func Get(path string) (value string, ok bool) {
   670  	return Application().Get(path)
   671  }
   672  
   673  // Must is a version of get which calls log.Fatal on error.
   674  func Must(name string, ptr interface{}) {
   675  	if err := Instance(name, ptr); err != nil {
   676  		log.Fatal(err)
   677  	}
   678  }
   679  
   680  func assign(name string, instance interface{}, ptr reflect.Value, file string, line int) error {
   681  	if ptr == (reflect.Value{}) {
   682  		return nil
   683  	}
   684  	v := reflect.ValueOf(instance)
   685  	if !v.IsValid() {
   686  		ptr.Elem().Set(reflect.Zero(ptr.Elem().Type()))
   687  		return nil
   688  	}
   689  	if !v.Type().AssignableTo(ptr.Elem().Type()) {
   690  		return fmt.Errorf(
   691  			"%s:%d: instance %q of type %s is not assignable to provided pointer element type %s",
   692  			file, line, name, v.Type(), ptr.Elem().Type())
   693  	}
   694  	ptr.Elem().Set(v)
   695  	return nil
   696  }
   697  
   698  func lock(p, q *Profile) (unlock func()) {
   699  	if uintptr(unsafe.Pointer(q)) < uintptr(unsafe.Pointer(p)) {
   700  		p, q = q, p
   701  	}
   702  	p.mu.Lock()
   703  	q.mu.Lock()
   704  	return func() {
   705  		q.mu.Unlock()
   706  		p.mu.Unlock()
   707  	}
   708  }