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 }