github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/environs/cloudinit.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package environs
     5  
     6  import (
     7  	"fmt"
     8  	"path"
     9  
    10  	"github.com/errgo/errgo"
    11  
    12  	"launchpad.net/juju-core/agent"
    13  	coreCloudinit "launchpad.net/juju-core/cloudinit"
    14  	"launchpad.net/juju-core/constraints"
    15  	"launchpad.net/juju-core/environs/cloudinit"
    16  	"launchpad.net/juju-core/environs/config"
    17  	"launchpad.net/juju-core/juju/osenv"
    18  	"launchpad.net/juju-core/names"
    19  	"launchpad.net/juju-core/state"
    20  	"launchpad.net/juju-core/state/api"
    21  	"launchpad.net/juju-core/state/api/params"
    22  	"launchpad.net/juju-core/utils"
    23  )
    24  
    25  // DataDir is the default data directory.
    26  // Tests can override this where needed, so they don't need to mess with global
    27  // system state.
    28  var DataDir = path.Join(osenv.LibDir, "juju")
    29  
    30  // CloudInitOutputLog is the default cloud-init-output.log file path.
    31  var CloudInitOutputLog = path.Join(osenv.LogDir, "cloud-init-output.log")
    32  
    33  // MongoServiceName is the default Upstart service name for Mongo.
    34  const MongoServiceName = "juju-db"
    35  
    36  // NewMachineConfig sets up a basic machine configuration, for a non-bootstrap
    37  // node.  You'll still need to supply more information, but this takes care of
    38  // the fixed entries and the ones that are always needed.
    39  func NewMachineConfig(machineID, machineNonce string, machineSeries string,
    40  	stateInfo *state.Info, apiInfo *api.Info) *cloudinit.MachineConfig {
    41  
    42  	localDataDir := DataDir
    43  	localLogDir := agent.DefaultLogDir
    44  	if len(machineSeries) > 3 && machineSeries[:3] == "win"{
    45  		localDataDir = osenv.WinDataDir
    46  		localLogDir = osenv.WinLogDir
    47  	}
    48  	return &cloudinit.MachineConfig{
    49  		// Fixed entries.
    50  		DataDir:                 localDataDir,
    51  		LogDir:                  localLogDir,
    52  		Jobs:                    []params.MachineJob{params.JobHostUnits},
    53  		CloudInitOutputLog:      CloudInitOutputLog,
    54  		MachineAgentServiceName: "jujud-" + names.MachineTag(machineID),
    55  		MongoServiceName:        MongoServiceName,
    56  
    57  		// Parameter entries.
    58  		MachineId:    machineID,
    59  		MachineNonce: machineNonce,
    60  		StateInfo:    stateInfo,
    61  		APIInfo:      apiInfo,
    62  	}
    63  }
    64  
    65  // NewBootstrapMachineConfig sets up a basic machine configuration for a
    66  // bootstrap node.  You'll still need to supply more information, but this
    67  // takes care of the fixed entries and the ones that are always needed.
    68  // stateInfoURL is the storage URL for the environment's state file.
    69  func NewBootstrapMachineConfig(stateInfoURL string, privateSystemSSHKey string) *cloudinit.MachineConfig {
    70  	// For a bootstrap instance, FinishMachineConfig will provide the
    71  	// state.Info and the api.Info. The machine id must *always* be "0".
    72  	mcfg := NewMachineConfig("0", state.BootstrapNonce, "", nil, nil)
    73  	mcfg.StateServer = true
    74  	mcfg.StateInfoURL = stateInfoURL
    75  	mcfg.SystemPrivateSSHKey = privateSystemSSHKey
    76  	mcfg.Jobs = []params.MachineJob{params.JobManageEnviron, params.JobHostUnits}
    77  	return mcfg
    78  }
    79  
    80  // PopulateMachineConfig is called both from the FinishMachineConfig below,
    81  // which does have access to the environment config, and from the container
    82  // provisioners, which don't have access to the environment config. Everything
    83  // that is needed to provision a container needs to be returned to the
    84  // provisioner in the ContainerConfig structure. Those values are then used to
    85  // call this function.
    86  func PopulateMachineConfig(mcfg *cloudinit.MachineConfig,
    87  	providerType, authorizedKeys string,
    88  	sslHostnameVerification bool,
    89  	proxy, aptProxy osenv.ProxySettings,
    90  ) error {
    91  	if authorizedKeys == "" {
    92  		return fmt.Errorf("environment configuration has no authorized-keys")
    93  	}
    94  	mcfg.AuthorizedKeys = authorizedKeys
    95  	if mcfg.AgentEnvironment == nil {
    96  		mcfg.AgentEnvironment = make(map[string]string)
    97  	}
    98  	mcfg.AgentEnvironment[agent.ProviderType] = providerType
    99  	mcfg.AgentEnvironment[agent.ContainerType] = string(mcfg.MachineContainerType)
   100  	mcfg.DisableSSLHostnameVerification = !sslHostnameVerification
   101  	mcfg.ProxySettings = proxy
   102  	mcfg.AptProxySettings = aptProxy
   103  	return nil
   104  }
   105  
   106  // FinishMachineConfig sets fields on a MachineConfig that can be determined by
   107  // inspecting a plain config.Config and the machine constraints at the last
   108  // moment before bootstrapping. It assumes that the supplied Config comes from
   109  // an environment that has passed through all the validation checks in the
   110  // Bootstrap func, and that has set an agent-version (via finding the tools to,
   111  // use for bootstrap, or otherwise).
   112  // TODO(fwereade) This function is not meant to be "good" in any serious way:
   113  // it is better that this functionality be collected in one place here than
   114  // that it be spread out across 3 or 4 providers, but this is its only
   115  // redeeming feature.
   116  func FinishMachineConfig(mcfg *cloudinit.MachineConfig, cfg *config.Config, cons constraints.Value) (err error) {
   117  	defer utils.ErrorContextf(&err, "cannot complete machine configuration")
   118  
   119  	if err := PopulateMachineConfig(
   120  		mcfg,
   121  		cfg.Type(),
   122  		cfg.AuthorizedKeys(),
   123  		cfg.SSLHostnameVerification(),
   124  		cfg.ProxySettings(),
   125  		cfg.AptProxySettings(),
   126  	); err != nil {
   127  		return err
   128  	}
   129  
   130  	// The following settings are only appropriate at bootstrap time. At the
   131  	// moment, the only state server is the bootstrap node, but this
   132  	// will probably change.
   133  	if !mcfg.StateServer {
   134  		return nil
   135  	}
   136  	if mcfg.APIInfo != nil || mcfg.StateInfo != nil {
   137  		return fmt.Errorf("machine configuration already has api/state info")
   138  	}
   139  	caCert, hasCACert := cfg.CACert()
   140  	if !hasCACert {
   141  		return fmt.Errorf("environment configuration has no ca-cert")
   142  	}
   143  	password := cfg.AdminSecret()
   144  	if password == "" {
   145  		return fmt.Errorf("environment configuration has no admin-secret")
   146  	}
   147  	passwordHash := utils.UserPasswordHash(password, utils.CompatSalt)
   148  	mcfg.APIInfo = &api.Info{Password: passwordHash, CACert: caCert}
   149  	mcfg.StateInfo = &state.Info{Password: passwordHash, CACert: caCert}
   150  	mcfg.StatePort = cfg.StatePort()
   151  	mcfg.APIPort = cfg.APIPort()
   152  	mcfg.Constraints = cons
   153  	if mcfg.Config, err = BootstrapConfig(cfg); err != nil {
   154  		return err
   155  	}
   156  
   157  	// These really are directly relevant to running a state server.
   158  	cert, key, err := cfg.GenerateStateServerCertAndKey()
   159  	if err != nil {
   160  		return errgo.Annotate(err, "cannot generate state server certificate")
   161  	}
   162  	mcfg.StateServerCert = cert
   163  	mcfg.StateServerKey = key
   164  	return nil
   165  }
   166  
   167  // ComposeUserData puts together a binary (gzipped) blob of user data.
   168  // The additionalScripts are additional command lines that you need cloudinit
   169  // to run on the instance; they are executed before all other cloud-init
   170  // runcmds.  Use with care.
   171  func ComposeUserData(cfg *cloudinit.MachineConfig, additionalScripts ...string) ([]byte, error) {
   172  	cloudcfg := coreCloudinit.New()
   173  	for _, script := range additionalScripts {
   174  		if cfg.Tools.Version.Series[:3] == "win"{
   175  			cloudcfg.AddPSCmd(script)
   176  		}else{
   177  			cloudcfg.AddRunCmd(script)
   178  		}
   179  	}
   180  	// When bootstrapping, we only want to apt-get update/upgrade
   181  	// and setup the SSH keys. The rest we leave to cloudinit/sshinit.
   182  	if cfg.StateServer {
   183  		if err := cloudinit.ConfigureBasic(cfg, cloudcfg); err != nil {
   184  			return nil, err
   185  		}
   186  	} else {
   187  		if err := cloudinit.Configure(cfg, cloudcfg); err != nil {
   188  			return nil, err
   189  		}
   190  	}
   191  	var data []byte
   192  	var err error
   193  	if cfg.Tools.Version.Series[:3] == "win"{
   194  		data, err = cloudcfg.RenderWin()
   195  	}else{
   196  		data, err = cloudcfg.Render()
   197  	}
   198  	logger.Tracef("Generated cloud init:\n%s", string(data))
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  	return utils.Gzip(data), nil
   203  }