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