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 }