environ config storage sketch

roger peppe roger.peppe at canonical.com
Fri Aug 30 22:48:18 UTC 2013


I mentioned in the standup that I had an idea how
the environment storage stuff might be structured,
so here's my sketch of some types and functions
that might work.

This decouples the configuration storage from
any necessary association with $HOME (or
local files for that matter) enabling us to fetch
configuration data from the network if we want.

I think that the mooted juju.conf structure
is not a great distance away from this.

http://paste.ubuntu.com/6045956/

Naming and other suggestions very welcome.

  cheers,
    rog.

PS I'm away next week.
-------------- next part --------------
package environs (?)

// EnvironInfo holds provider-independent
// information on how to connect to an environment.
// XXX the extra config attributes are not a member
// of this type because they only make sense in the
// context of a client that's bootstrapping - this
// structure makes sense to pass around the
// network on its own.
type EnvironInfo struct {
	// APIAddress holds a list of API addresses.
	// It may not be current, and it will be empty
	// if the environment has not been bootstrapped.
	APIAddresses []string

	// CACert holds the CA certificate that
	// signed the API server's key.
	CACert []byte

	// NeedSecrets stores whether the environment's
	// secrets have yet been pushed up with the first
	// state connection.
	NeedSecrets bool

	// User holds the name of the user to connect as.
	// XXX just one? should we store this elsewhere in fact?
	User string
	Password string
}

// ConfigStorage stores environment configuration data.
type ConfigStorage interface {
	// DefaultName returns the name of the default environment to use.
	DefaultName() (string, error)

	// EnvironConfig returns base environment configuration
	// for the environment with the given name.
	// The configuration does not include attributes
	// added by the environment when it is prepared.
	// (conventionally EnvironConfig will return data
	// read from $HOME/.juju/environments.yaml)
	EnvironConfig(envName string) (*config.Config, error)

	// EnvironInfo returns information on the environment
	// with the given name previously stored with WriteEnvironInfo.
	// (conventionally EnvironInfo will return data read from
	// $HOME/.juju/.environments/$envName.yaml)
	EnvironInfo(envName string) (*EnvironInfo, map[string] interface{}, error)

	// WriteEnvironInfo writes information on the environment
	// with the given name. The info parameter holds information
	// on how to connect to the environment; extra holds
	// any configuration attributes that have been added to the
	// environment's configuration when it was prepared.
	// (conventionally EnvironInfo will write to 
	// $HOME/.juju/.environments/$envName.yaml)
	WriteEnvironInfo(envName string, info *EnvironInfo, extra map[string] interface{}) error
}

-----------------------------------------

package juju (?)

// Prepare ensures that the environment with the given name is
// prepared for use, fetching and updating environment configuration
// attributes from the given store. It returns that environment.
func Prepare(envName string, store ConfigStorage) (environs.Environ, error) {
	info, cfg, err := environInfo(envName, store)
	if err != nil {
		return nil, err
	}
	if info == nil {
		env, err := environs.Prepare(cfg)
		if err != nil {
			return nil, err
		}
		info := environs.EnvironInfo{
			CACert: cfg.CACert(),
			NeedSecrets: true,
			User: "admin",
			Password: cfg.AdminSecret(),
		}
		extra := all attributes in env.Config() but not in cfg
		if err := store.WriteEnvironInfo(envName, info, extra); err != nil {
			return nil, err
		}
		return env, nil
	}
	return environs.Open(cfg)
}

// Open opens the environment with the given name, reading configuration
// attributes from the given store. It does not update any configuration
// attributes. 
// XXX This function is of limited use - will only be applicable while command-line
// applications need access to an Environ.
func Open(envName string, store environs.ConfigStorage) (environs.Environ, error) {
	_, cfg, err := environInfo(envName, store)
	if err != nil {
		return nil, err
	}
	return environs.Open(cfg)
}

// OpenAPI connects to the API of the environment with the given name,
// reading configuration attributes from the given store, and
// updating them with attributes acquired from the newly opened state.
func OpenAPI(envName string, store environs.ConfigStorage) (*api.State, error) {
	info, cfg, err := environInfo(envName, store)
	if err != nil {
		return nil, err
	}
	if info == nil {
		return nil, errors.New("not prepared")
	}
	var st *api.State
	go {
		st, err = api.Open(api.Info{
			Addrs: info.APIAddresses,
			CACert: info.CACert,
			Tag: "user-" + info.User,
			Password: info.Password,
		})
	}
	go {
		time.Sleep(a little while to enable the naive attempt to succeed first)
		env, err := environs.Open(cfg)
		check err
		_, apiInfo, err := env.StateInfo()
		check err
		st, err = api.Open(apiInfo)
	}

	if both failed {
		return nil, err
	}
	if info.NeedSecrets {
		// push secret attributes up to environment.
		st.SetEnvironConfig(secretAttrs(cfg))
		info.NeedSecrets = false
	}
	info.APIAddresses, err = st.APIAddresses()
	check err
	if info has changed {
		// update most recent API address info
		err = storage.WriteEnvironInfo(info, extra)
		check err
	}
	return st, nil
}


// environInfo returns information on the environment with the given
// name. The returned Config holds all the known configuration attributes
// for that environment. If there is no currently stored environment
// information in addition to the base configuration, the
// returned EnvironInfo may be nil.
func environInfo(envName string, store environs.ConfigStorage) (*environs.EnvironInfo, *config.Config, error) {
	cfg, err := store.EnvironConfig(envName)
	if err != nil {
		return nil, nil, err
	}
	info, extra, err := store.EnvironInfo(envName)
	if err != nil && !errors.IsNotFoundError(err) {
		return nil, nil, err
	}
	cfg1, err := cfg.Apply(extra)
	if err != nil {
		return nil, nil, err
	}
	return cfg1, info, nil
}


More information about the Juju-dev mailing list