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