launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/cmd/jujud/bootstrap.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package main
     5  
     6  import (
     7  	"encoding/base64"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"strings"
    11  
    12  	"launchpad.net/gnuflag"
    13  	"launchpad.net/goyaml"
    14  
    15  	"launchpad.net/juju-core/agent"
    16  	"launchpad.net/juju-core/cmd"
    17  	"launchpad.net/juju-core/constraints"
    18  	"launchpad.net/juju-core/environs/bootstrap"
    19  	"launchpad.net/juju-core/environs/cloudinit"
    20  	"launchpad.net/juju-core/environs/config"
    21  	"launchpad.net/juju-core/instance"
    22  	"launchpad.net/juju-core/state"
    23  )
    24  
    25  // Cloud-init write the URL to be used to load the bootstrap state into this file.
    26  // A variable is used here to allow tests to override.
    27  var providerStateURLFile = cloudinit.BootstrapStateURLFile
    28  
    29  type BootstrapCommand struct {
    30  	cmd.CommandBase
    31  	Conf        AgentConf
    32  	EnvConfig   map[string]interface{}
    33  	Constraints constraints.Value
    34  }
    35  
    36  // Info returns a decription of the command.
    37  func (c *BootstrapCommand) Info() *cmd.Info {
    38  	return &cmd.Info{
    39  		Name:    "bootstrap-state",
    40  		Purpose: "initialize juju state",
    41  	}
    42  }
    43  
    44  func (c *BootstrapCommand) SetFlags(f *gnuflag.FlagSet) {
    45  	c.Conf.addFlags(f)
    46  	yamlBase64Var(f, &c.EnvConfig, "env-config", "", "initial environment configuration (yaml, base64 encoded)")
    47  	f.Var(constraints.ConstraintsValue{&c.Constraints}, "constraints", "initial environment constraints (space-separated strings)")
    48  }
    49  
    50  // Init initializes the command for running.
    51  func (c *BootstrapCommand) Init(args []string) error {
    52  	if len(c.EnvConfig) == 0 {
    53  		return requiredError("env-config")
    54  	}
    55  	return c.Conf.checkArgs(args)
    56  }
    57  
    58  // Run initializes state for an environment.
    59  func (c *BootstrapCommand) Run(_ *cmd.Context) error {
    60  	data, err := ioutil.ReadFile(providerStateURLFile)
    61  	if err != nil {
    62  		return fmt.Errorf("cannot read provider-state-url file: %v", err)
    63  	}
    64  	stateInfoURL := strings.Split(string(data), "\n")[0]
    65  	bsState, err := bootstrap.LoadStateFromURL(stateInfoURL)
    66  	if err != nil {
    67  		return fmt.Errorf("cannot load state from URL %q (read from %q): %v", stateInfoURL, providerStateURLFile, err)
    68  	}
    69  	err = c.Conf.read("machine-0")
    70  	if err != nil {
    71  		return err
    72  	}
    73  	envCfg, err := config.New(config.NoDefaults, c.EnvConfig)
    74  	if err != nil {
    75  		return err
    76  	}
    77  	// agent.BootstrapJobs is an optional field in the agent
    78  	// config, and was introduced after 1.17.2. We default to
    79  	// allowing units on machine-0 if missing.
    80  	jobs := []state.MachineJob{
    81  		state.JobManageEnviron,
    82  		state.JobHostUnits,
    83  	}
    84  	if bootstrapJobs := c.Conf.config.Value(agent.BootstrapJobs); bootstrapJobs != "" {
    85  		jobs, err = agent.UnmarshalBootstrapJobs(bootstrapJobs)
    86  		if err != nil {
    87  			return err
    88  		}
    89  	}
    90  	var characteristics instance.HardwareCharacteristics
    91  	if len(bsState.Characteristics) > 0 {
    92  		characteristics = bsState.Characteristics[0]
    93  	}
    94  	st, _, err := c.Conf.config.InitializeState(envCfg, agent.BootstrapMachineConfig{
    95  		Constraints:     c.Constraints,
    96  		Jobs:            jobs,
    97  		InstanceId:      bsState.StateInstances[0],
    98  		Characteristics: characteristics,
    99  	}, state.DefaultDialOpts())
   100  	if err != nil {
   101  		return err
   102  	}
   103  	st.Close()
   104  	return nil
   105  }
   106  
   107  // yamlBase64Value implements gnuflag.Value on a map[string]interface{}.
   108  type yamlBase64Value map[string]interface{}
   109  
   110  // Set decodes the base64 value into yaml then expands that into a map.
   111  func (v *yamlBase64Value) Set(value string) error {
   112  	decoded, err := base64.StdEncoding.DecodeString(value)
   113  	if err != nil {
   114  		return err
   115  	}
   116  	return goyaml.Unmarshal(decoded, v)
   117  }
   118  
   119  func (v *yamlBase64Value) String() string {
   120  	return fmt.Sprintf("%v", *v)
   121  }
   122  
   123  // yamlBase64Var sets up a gnuflag flag analogous to the FlagSet.*Var methods.
   124  func yamlBase64Var(fs *gnuflag.FlagSet, target *map[string]interface{}, name string, value string, usage string) {
   125  	fs.Var((*yamlBase64Value)(target), name, usage)
   126  }