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