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 }