github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/cloudconfig/containerinit/container_userdata.go (about) 1 // Copyright 2013, 2015 Canonical Ltd. 2 // Copyright 2015 Cloudbase Solutions SRL 3 // Licensed under the AGPLv3, see LICENCE file for details. 4 5 package containerinit 6 7 import ( 8 "bytes" 9 "fmt" 10 "io/ioutil" 11 "path/filepath" 12 "strings" 13 "text/template" 14 15 "github.com/juju/errors" 16 "github.com/juju/loggo" 17 "github.com/juju/utils/proxy" 18 19 "github.com/juju/juju/cloudconfig" 20 "github.com/juju/juju/cloudconfig/cloudinit" 21 "github.com/juju/juju/cloudconfig/instancecfg" 22 "github.com/juju/juju/container" 23 "github.com/juju/juju/environs" 24 "github.com/juju/juju/network" 25 "github.com/juju/juju/service" 26 "github.com/juju/juju/service/common" 27 ) 28 29 var ( 30 logger = loggo.GetLogger("juju.cloudconfig.containerinit") 31 ) 32 33 // WriteUserData generates the cloud-init user-data using the 34 // specified machine and network config for a container, and writes 35 // the serialized form out to a cloud-init file in the directory 36 // specified. 37 func WriteUserData( 38 instanceConfig *instancecfg.InstanceConfig, 39 networkConfig *container.NetworkConfig, 40 directory string, 41 ) (string, error) { 42 userData, err := CloudInitUserData(instanceConfig, networkConfig) 43 if err != nil { 44 logger.Errorf("failed to create user data: %v", err) 45 return "", err 46 } 47 return WriteCloudInitFile(directory, userData) 48 } 49 50 // WriteCloudInitFile writes the data out to a cloud-init file in the 51 // directory specified, and returns the filename. 52 func WriteCloudInitFile(directory string, userData []byte) (string, error) { 53 userDataFilename := filepath.Join(directory, "cloud-init") 54 if err := ioutil.WriteFile(userDataFilename, userData, 0644); err != nil { 55 logger.Errorf("failed to write user data: %v", err) 56 return "", err 57 } 58 return userDataFilename, nil 59 } 60 61 // networkConfigTemplate defines how to render /etc/network/interfaces 62 // file for a container with one or more NICs. 63 const networkConfigTemplate = ` 64 # loopback interface 65 auto lo 66 iface lo inet loopback{{define "static"}} 67 {{.InterfaceName | printf "# interface %q"}}{{if not .NoAutoStart}} 68 auto {{.InterfaceName}}{{end}} 69 iface {{.InterfaceName}} inet manual{{if gt (len .DNSServers) 0}} 70 dns-nameservers{{range $dns := .DNSServers}} {{$dns.Value}}{{end}}{{end}}{{if gt (len .DNSSearch) 0}} 71 dns-search {{.DNSSearch}}{{end}} 72 pre-up ip address add {{.Address.Value}}/32 dev {{.InterfaceName}} &> /dev/null || true 73 up ip route replace {{.GatewayAddress.Value}} dev {{.InterfaceName}} 74 up ip route replace default via {{.GatewayAddress.Value}} 75 down ip route del default via {{.GatewayAddress.Value}} &> /dev/null || true 76 down ip route del {{.GatewayAddress.Value}} dev {{.InterfaceName}} &> /dev/null || true 77 post-down ip address del {{.Address.Value}}/32 dev {{.InterfaceName}} &> /dev/null || true 78 {{end}}{{define "dhcp"}} 79 {{.InterfaceName | printf "# interface %q"}}{{if not .NoAutoStart}} 80 auto {{.InterfaceName}}{{end}} 81 iface {{.InterfaceName}} inet dhcp 82 {{end}}{{range $nic := . }}{{if eq $nic.ConfigType "static"}} 83 {{template "static" $nic}}{{else}}{{template "dhcp" $nic}}{{end}}{{end}}` 84 85 // multiBridgeNetworkConfigTemplate defines how to render /etc/network/interfaces 86 // file for a multi-NIC container. 87 const multiBridgeNetworkConfigTemplate = ` 88 auto lo 89 iface lo inet loopback 90 {{range $nic := .}}{{template "single" $nic}}{{end}} 91 {{define "single"}}{{if not .NoAutoStart}} 92 auto {{.InterfaceName}}{{end}} 93 iface {{.InterfaceName}} inet manual{{if .DNSServers}} 94 dns-nameservers{{range $srv := .DNSServers}} {{$srv.Value}}{{end}}{{end}}{{if .DNSSearchDomains}} 95 dns-search{{range $dom := .DNSSearchDomains}} {{$dom}}{{end}}{{end}} 96 pre-up ip address add {{.CIDRAddress}} dev {{.InterfaceName}} || true 97 up ip route replace {{.CIDR}} dev {{.InterfaceName}} || true 98 down ip route del {{.CIDR}} dev {{.InterfaceName}} || true 99 post-down address del {{.CIDRAddress}} dev {{.InterfaceName}} || true{{if .GatewayAddress.Value}} 100 up ip route replace default via {{.GatewayAddress.Value}} || true 101 down ip route del default via {{.GatewayAddress.Value}} || true{{end}} 102 {{end}}` 103 104 var networkInterfacesFile = "/etc/network/interfaces.d/00-juju.cfg" 105 106 // GenerateNetworkConfig renders a network config for one or more 107 // network interfaces, using the given non-nil networkConfig 108 // containing a non-empty Interfaces field. 109 func GenerateNetworkConfig(networkConfig *container.NetworkConfig) (string, error) { 110 if networkConfig == nil || len(networkConfig.Interfaces) == 0 { 111 // Don't generate networking config. 112 logger.Tracef("no network config to generate") 113 return "", nil 114 } 115 logger.Debugf("generating network config from %#v", *networkConfig) 116 117 // Copy the InterfaceInfo before modifying the original. 118 interfacesCopy := make([]network.InterfaceInfo, len(networkConfig.Interfaces)) 119 copy(interfacesCopy, networkConfig.Interfaces) 120 for i, info := range interfacesCopy { 121 if info.MACAddress != "" { 122 info.MACAddress = "" 123 } 124 if info.InterfaceName != "eth0" { 125 info.GatewayAddress = network.Address{} 126 } 127 interfacesCopy[i] = info 128 } 129 130 // Render the config first. 131 tmpl, err := template.New("config").Parse(multiBridgeNetworkConfigTemplate) 132 if err != nil { 133 return "", errors.Annotate(err, "cannot parse network config template") 134 } 135 136 var buf bytes.Buffer 137 if err := tmpl.Execute(&buf, interfacesCopy); err != nil { 138 return "", errors.Annotate(err, "cannot render network config") 139 } 140 141 generatedConfig := buf.String() 142 logger.Debugf("generated network config from %#v\nusing%#v:\n%s", interfacesCopy, networkConfig.Interfaces, generatedConfig) 143 144 return generatedConfig, nil 145 } 146 147 // newCloudInitConfigWithNetworks creates a cloud-init config which 148 // might include per-interface networking config if both networkConfig 149 // is not nil and its Interfaces field is not empty. 150 func newCloudInitConfigWithNetworks(series string, networkConfig *container.NetworkConfig) (cloudinit.CloudConfig, error) { 151 cloudConfig, err := cloudinit.New(series) 152 if err != nil { 153 return nil, errors.Trace(err) 154 } 155 config, err := GenerateNetworkConfig(networkConfig) 156 if err != nil || len(config) == 0 { 157 return cloudConfig, errors.Trace(err) 158 } 159 160 cloudConfig.AddBootTextFile(networkInterfacesFile, config, 0644) 161 cloudConfig.AddRunCmd("ifup -a || true") 162 return cloudConfig, nil 163 } 164 165 func CloudInitUserData( 166 instanceConfig *instancecfg.InstanceConfig, 167 networkConfig *container.NetworkConfig, 168 ) ([]byte, error) { 169 cloudConfig, err := newCloudInitConfigWithNetworks(instanceConfig.Series, networkConfig) 170 if err != nil { 171 return nil, errors.Trace(err) 172 } 173 udata, err := cloudconfig.NewUserdataConfig(instanceConfig, cloudConfig) 174 if err != nil { 175 return nil, errors.Trace(err) 176 } 177 if err = udata.Configure(); err != nil { 178 return nil, errors.Trace(err) 179 } 180 // Run ifconfig to get the addresses of the internal container at least 181 // logged in the host. 182 cloudConfig.AddRunCmd("ifconfig") 183 184 if instanceConfig.MachineContainerHostname != "" { 185 cloudConfig.SetAttr("hostname", instanceConfig.MachineContainerHostname) 186 } 187 188 data, err := cloudConfig.RenderYAML() 189 if err != nil { 190 return nil, errors.Trace(err) 191 } 192 return data, nil 193 } 194 195 // TemplateUserData returns a minimal user data necessary for the template. 196 // This should have the authorized keys, base packages, the cloud archive if 197 // necessary, initial apt proxy config, and it should do the apt-get 198 // update/upgrade initially. 199 func TemplateUserData( 200 series string, 201 authorizedKeys string, 202 aptProxy proxy.Settings, 203 aptMirror string, 204 enablePackageUpdates bool, 205 enableOSUpgrades bool, 206 networkConfig *container.NetworkConfig, 207 ) ([]byte, error) { 208 var config cloudinit.CloudConfig 209 var err error 210 if networkConfig != nil { 211 config, err = newCloudInitConfigWithNetworks(series, networkConfig) 212 if err != nil { 213 return nil, errors.Trace(err) 214 } 215 } else { 216 config, err = cloudinit.New(series) 217 if err != nil { 218 return nil, errors.Trace(err) 219 } 220 } 221 cloudconfig.SetUbuntuUser(config, authorizedKeys) 222 config.AddScripts( 223 "set -xe", // ensure we run all the scripts or abort. 224 ) 225 // For LTS series which need support for the cloud-tools archive, 226 // we need to enable apt-get update regardless of the environ 227 // setting, otherwise provisioning will fail. 228 if series == "precise" && !enablePackageUpdates { 229 logger.Warningf("series %q requires cloud-tools archive: enabling updates", series) 230 enablePackageUpdates = true 231 } 232 233 if enablePackageUpdates && config.RequiresCloudArchiveCloudTools() { 234 config.AddCloudArchiveCloudTools() 235 } 236 config.AddPackageCommands(aptProxy, aptMirror, enablePackageUpdates, enableOSUpgrades) 237 238 initSystem, err := service.VersionInitSystem(series) 239 if err != nil { 240 return nil, errors.Trace(err) 241 } 242 cmds, err := shutdownInitCommands(initSystem, series) 243 if err != nil { 244 return nil, errors.Trace(err) 245 } 246 config.AddScripts(strings.Join(cmds, "\n")) 247 248 data, err := config.RenderYAML() 249 if err != nil { 250 return nil, err 251 } 252 return data, nil 253 } 254 255 // defaultEtcNetworkInterfaces is the contents of 256 // /etc/network/interfaces file which is left on the template LXC 257 // container on shutdown. This is needed to allow cloned containers to 258 // start in case no network config is provided during cloud-init, e.g. 259 // when AUFS is used. 260 const defaultEtcNetworkInterfaces = ` 261 # loopback interface 262 auto lo 263 iface lo inet loopback 264 265 # primary interface 266 auto eth0 267 iface eth0 inet dhcp 268 ` 269 270 func shutdownInitCommands(initSystem, series string) ([]string, error) { 271 // These files are removed just before the template shuts down. 272 cleanupOnShutdown := []string{ 273 // We remove any dhclient lease files so there's no chance a 274 // clone to reuse a lease from the template it was cloned 275 // from. 276 "/var/lib/dhcp/dhclient*", 277 // Both of these sets of files below are recreated on boot and 278 // if we leave them in the template's rootfs boot logs coming 279 // from cloned containers will be appended. It's better to 280 // keep clean logs for diagnosing issues / debugging. 281 "/var/log/cloud-init*.log", 282 } 283 284 // Using EOC below as the template shutdown script is itself 285 // passed through cat > ... < EOF. 286 replaceNetConfCmd := fmt.Sprintf( 287 "/bin/cat > /etc/network/interfaces << EOC%sEOC\n ", 288 defaultEtcNetworkInterfaces, 289 ) 290 paths := strings.Join(cleanupOnShutdown, " ") 291 removeCmd := fmt.Sprintf("/bin/rm -fr %s\n ", paths) 292 shutdownCmd := "/sbin/shutdown -h now" 293 name := "juju-template-restart" 294 desc := "juju shutdown job" 295 296 execStart := shutdownCmd 297 if environs.AddressAllocationEnabled("") { // we only care the provider is not MAAS here. 298 // Only do the cleanup and replacement of /e/n/i when address 299 // allocation feature flag is enabled. 300 execStart = replaceNetConfCmd + removeCmd + shutdownCmd 301 } 302 303 conf := common.Conf{ 304 Desc: desc, 305 Transient: true, 306 AfterStopped: "cloud-final", 307 ExecStart: execStart, 308 } 309 // systemd uses targets for synchronization of services 310 if initSystem == service.InitSystemSystemd { 311 conf.AfterStopped = "cloud-config.target" 312 } 313 314 svc, err := service.NewService(name, conf, series) 315 if err != nil { 316 return nil, errors.Trace(err) 317 } 318 319 cmds, err := svc.InstallCommands() 320 if err != nil { 321 return nil, errors.Trace(err) 322 } 323 324 startCommands, err := svc.StartCommands() 325 if err != nil { 326 return nil, errors.Trace(err) 327 } 328 cmds = append(cmds, startCommands...) 329 330 return cmds, nil 331 }