| // Package config handles configuration state passed across instances of the |
| // device manager. |
| // |
| // The State object captures setting that the device manager needs to be aware |
| // of when it starts. This is passed to the first invocation of the device |
| // manager, and then passed down from old device manager to new device manager |
| // upon update. The device manager has an implementation-dependent mechanism |
| // for parsing and passing state, which is encapsulated by the state sub-package |
| // (currently, the mechanism uses environment variables). When instantiating a |
| // new instance of the device manager service, the developer needs to pass in a |
| // copy of State. They can obtain this by calling Load, which captures any |
| // config state passed by a previous version of device manager during update. |
| // Any new version of the device manager must be able to decode a previous |
| // version's config state, even if the new version changes the mechanism for |
| // passing this state (that is, device manager implementations must be |
| // backward-compatible as far as accepting and passing config state goes). |
| // TODO(caprita): add config state versioning? |
| package config |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "os" |
| "path/filepath" |
| "strings" |
| |
| "v.io/core/veyron/lib/flags/consts" |
| "v.io/core/veyron2/services/mgmt/application" |
| ) |
| |
| // State specifies how the device manager is configured. This should |
| // encapsulate what the device manager needs to know and/or be able to mutate |
| // about its environment. |
| type State struct { |
| // Name is the device manager's object name. Must be non-empty. |
| Name string |
| // Envelope is the device manager's application envelope. If nil, any |
| // envelope fetched from the application repository will trigger an |
| // update. |
| Envelope *application.Envelope |
| // Previous holds the local path to the previous version of the device |
| // manager. If empty, revert is disabled. |
| Previous string |
| // Root is the directory on the local filesystem that contains |
| // the applications' workspaces. Must be non-empty. |
| Root string |
| // Origin is the application repository object name for the device |
| // manager application. If empty, update is disabled. |
| Origin string |
| // CurrentLink is the local filesystem soft link that should point to |
| // the version of the device manager binary/script through which device |
| // manager is started. Device manager is expected to mutate this during |
| // a self-update. Must be non-empty. |
| CurrentLink string |
| // Helper is the path to the setuid helper for running applications as |
| // specific users. |
| Helper string |
| } |
| |
| // Validate checks the config state. |
| func (c *State) Validate() error { |
| if c.Name == "" { |
| return fmt.Errorf("Name cannot be empty") |
| } |
| if c.Root == "" { |
| return fmt.Errorf("Root cannot be empty") |
| } |
| if c.CurrentLink == "" { |
| return fmt.Errorf("CurrentLink cannot be empty") |
| } |
| if c.Helper == "" { |
| return fmt.Errorf("Helper must be specified") |
| } |
| return nil |
| } |
| |
| // Load reconstructs the config state passed to the device manager (presumably |
| // by the parent device manager during an update). Currently, this is done via |
| // environment variables. |
| func Load() (*State, error) { |
| var env *application.Envelope |
| if jsonEnvelope := os.Getenv(EnvelopeEnv); jsonEnvelope != "" { |
| env = new(application.Envelope) |
| if err := json.Unmarshal([]byte(jsonEnvelope), env); err != nil { |
| return nil, fmt.Errorf("failed to decode envelope from %v: %v", jsonEnvelope, err) |
| } |
| } |
| return &State{ |
| Envelope: env, |
| Previous: os.Getenv(PreviousEnv), |
| Root: os.Getenv(RootEnv), |
| Origin: os.Getenv(OriginEnv), |
| CurrentLink: os.Getenv(CurrentLinkEnv), |
| Helper: os.Getenv(HelperEnv), |
| }, nil |
| } |
| |
| // Save serializes the config state meant to be passed to a child device manager |
| // during an update, returning a slice of "key=value" strings, which are |
| // expected to be stuffed into environment variable settings by the caller. |
| func (c *State) Save(envelope *application.Envelope) ([]string, error) { |
| jsonEnvelope, err := json.Marshal(envelope) |
| if err != nil { |
| return nil, fmt.Errorf("failed to encode envelope %v: %v", envelope, err) |
| } |
| currScript, err := filepath.EvalSymlinks(c.CurrentLink) |
| if err != nil { |
| return nil, fmt.Errorf("EvalSymlink failed: %v", err) |
| } |
| settings := map[string]string{ |
| EnvelopeEnv: string(jsonEnvelope), |
| PreviousEnv: currScript, |
| RootEnv: c.Root, |
| OriginEnv: c.Origin, |
| CurrentLinkEnv: c.CurrentLink, |
| HelperEnv: c.Helper, |
| } |
| // We need to manually pass the namespace roots to the child, since we |
| // currently don't have a way for the child to obtain this information |
| // from a config service at start-up. |
| for _, ev := range os.Environ() { |
| p := strings.SplitN(ev, "=", 2) |
| if len(p) != 2 { |
| continue |
| } |
| k, v := p[0], p[1] |
| if strings.HasPrefix(k, consts.NamespaceRootPrefix) { |
| settings[k] = v |
| } |
| } |
| var ret []string |
| for k, v := range settings { |
| ret = append(ret, k+"="+v) |
| } |
| return ret, nil |
| } |
| |
| // QuoteEnv wraps environment variable values in double quotes, making them |
| // suitable for inclusion in a bash script. |
| func QuoteEnv(env []string) (ret []string) { |
| for _, e := range env { |
| if eqIdx := strings.Index(e, "="); eqIdx > 0 { |
| ret = append(ret, fmt.Sprintf("%s=%q", e[:eqIdx], e[eqIdx+1:])) |
| } else { |
| ret = append(ret, e) |
| } |
| } |
| return |
| } |