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