mirror of
https://github.com/rancher/os.git
synced 2025-09-25 12:47:20 +00:00
remove systemd things that so we can build ros
Signed-off-by: Sven Dowideit <SvenDowideit@home.org.au>
This commit is contained in:
@@ -1,433 +0,0 @@
|
|||||||
// Copyright 2015 CoreOS, Inc.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"compress/gzip"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/rancher/os/config/cloudinit/config"
|
|
||||||
"github.com/rancher/os/config/cloudinit/config/validate"
|
|
||||||
"github.com/rancher/os/config/cloudinit/datasource"
|
|
||||||
"github.com/rancher/os/config/cloudinit/datasource/configdrive"
|
|
||||||
"github.com/rancher/os/config/cloudinit/datasource/file"
|
|
||||||
"github.com/rancher/os/config/cloudinit/datasource/metadata/cloudsigma"
|
|
||||||
"github.com/rancher/os/config/cloudinit/datasource/metadata/digitalocean"
|
|
||||||
"github.com/rancher/os/config/cloudinit/datasource/metadata/ec2"
|
|
||||||
"github.com/rancher/os/config/cloudinit/datasource/metadata/gce"
|
|
||||||
"github.com/rancher/os/config/cloudinit/datasource/metadata/packet"
|
|
||||||
"github.com/rancher/os/config/cloudinit/datasource/proc_cmdline"
|
|
||||||
"github.com/rancher/os/config/cloudinit/datasource/url"
|
|
||||||
"github.com/rancher/os/config/cloudinit/datasource/vmware"
|
|
||||||
"github.com/rancher/os/config/cloudinit/datasource/waagent"
|
|
||||||
"github.com/rancher/os/config/cloudinit/initialize"
|
|
||||||
"github.com/rancher/os/config/cloudinit/network"
|
|
||||||
"github.com/rancher/os/config/cloudinit/pkg"
|
|
||||||
"github.com/rancher/os/config/cloudinit/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
datasourceInterval = 100 * time.Millisecond
|
|
||||||
datasourceMaxInterval = 30 * time.Second
|
|
||||||
datasourceTimeout = 5 * time.Minute
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
flags = struct {
|
|
||||||
printVersion bool
|
|
||||||
ignoreFailure bool
|
|
||||||
sources struct {
|
|
||||||
file string
|
|
||||||
configDrive string
|
|
||||||
waagent string
|
|
||||||
metadataService bool
|
|
||||||
ec2MetadataService string
|
|
||||||
gceMetadataService string
|
|
||||||
cloudSigmaMetadataService bool
|
|
||||||
digitalOceanMetadataService string
|
|
||||||
packetMetadataService string
|
|
||||||
url string
|
|
||||||
procCmdLine bool
|
|
||||||
vmware bool
|
|
||||||
ovfEnv string
|
|
||||||
}
|
|
||||||
convertNetconf string
|
|
||||||
workspace string
|
|
||||||
sshKeyName string
|
|
||||||
oem string
|
|
||||||
validate bool
|
|
||||||
}{}
|
|
||||||
version = "was not built properly"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
flag.BoolVar(&flags.printVersion, "version", false, "Print the version and exit")
|
|
||||||
flag.BoolVar(&flags.ignoreFailure, "ignore-failure", false, "Exits with 0 status in the event of malformed input from user-data")
|
|
||||||
flag.StringVar(&flags.sources.file, "from-file", "", "Read user-data from provided file")
|
|
||||||
flag.StringVar(&flags.sources.configDrive, "from-configdrive", "", "Read data from provided cloud-drive directory")
|
|
||||||
flag.StringVar(&flags.sources.waagent, "from-waagent", "", "Read data from provided waagent directory")
|
|
||||||
flag.BoolVar(&flags.sources.metadataService, "from-metadata-service", false, "[DEPRECATED - Use -from-ec2-metadata] Download data from metadata service")
|
|
||||||
flag.StringVar(&flags.sources.ec2MetadataService, "from-ec2-metadata", "", "Download EC2 data from the provided url")
|
|
||||||
flag.StringVar(&flags.sources.gceMetadataService, "from-gce-metadata", "", "Download GCE data from the provided url")
|
|
||||||
flag.BoolVar(&flags.sources.cloudSigmaMetadataService, "from-cloudsigma-metadata", false, "Download data from CloudSigma server context")
|
|
||||||
flag.StringVar(&flags.sources.digitalOceanMetadataService, "from-digitalocean-metadata", "", "Download DigitalOcean data from the provided url")
|
|
||||||
flag.StringVar(&flags.sources.packetMetadataService, "from-packet-metadata", "", "Download Packet data from metadata service")
|
|
||||||
flag.StringVar(&flags.sources.url, "from-url", "", "Download user-data from provided url")
|
|
||||||
flag.BoolVar(&flags.sources.procCmdLine, "from-proc-cmdline", false, fmt.Sprintf("Parse %s for '%s=<url>', using the cloud-config served by an HTTP GET to <url>", proc_cmdline.ProcCmdlineLocation, proc_cmdline.ProcCmdlineCloudConfigFlag))
|
|
||||||
flag.BoolVar(&flags.sources.vmware, "from-vmware-guestinfo", false, "Read data from VMware guestinfo")
|
|
||||||
flag.StringVar(&flags.sources.ovfEnv, "from-vmware-ovf-env", "", "Read data from OVF Environment")
|
|
||||||
flag.StringVar(&flags.oem, "oem", "", "Use the settings specific to the provided OEM")
|
|
||||||
flag.StringVar(&flags.convertNetconf, "convert-netconf", "", "Read the network config provided in cloud-drive and translate it from the specified format into networkd unit files")
|
|
||||||
flag.StringVar(&flags.workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data")
|
|
||||||
flag.StringVar(&flags.sshKeyName, "ssh-key-name", initialize.DefaultSSHKeyName, "Add SSH keys to the system with the given name")
|
|
||||||
flag.BoolVar(&flags.validate, "validate", false, "[EXPERIMENTAL] Validate the user-data but do not apply it to the system")
|
|
||||||
}
|
|
||||||
|
|
||||||
type oemConfig map[string]string
|
|
||||||
|
|
||||||
var (
|
|
||||||
oemConfigs = map[string]oemConfig{
|
|
||||||
"digitalocean": {
|
|
||||||
"from-digitalocean-metadata": "http://169.254.169.254/",
|
|
||||||
},
|
|
||||||
"ec2-compat": {
|
|
||||||
"from-ec2-metadata": "http://169.254.169.254/",
|
|
||||||
"from-configdrive": "/media/configdrive",
|
|
||||||
},
|
|
||||||
"gce": {
|
|
||||||
"from-gce-metadata": "http://metadata.google.internal/",
|
|
||||||
},
|
|
||||||
"rackspace-onmetal": {
|
|
||||||
"from-configdrive": "/media/configdrive",
|
|
||||||
"convert-netconf": "debian",
|
|
||||||
},
|
|
||||||
"azure": {
|
|
||||||
"from-waagent": "/var/lib/waagent",
|
|
||||||
},
|
|
||||||
"cloudsigma": {
|
|
||||||
"from-cloudsigma-metadata": "true",
|
|
||||||
},
|
|
||||||
"packet": {
|
|
||||||
"from-packet-metadata": "https://metadata.packet.net/",
|
|
||||||
},
|
|
||||||
"vmware": {
|
|
||||||
"from-vmware-guestinfo": "true",
|
|
||||||
"convert-netconf": "vmware",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
failure := false
|
|
||||||
|
|
||||||
// Conservative Go 1.5 upgrade strategy:
|
|
||||||
// keep GOMAXPROCS' default at 1 for now.
|
|
||||||
if os.Getenv("GOMAXPROCS") == "" {
|
|
||||||
runtime.GOMAXPROCS(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if c, ok := oemConfigs[flags.oem]; ok {
|
|
||||||
for k, v := range c {
|
|
||||||
flag.Set(k, v)
|
|
||||||
}
|
|
||||||
} else if flags.oem != "" {
|
|
||||||
oems := make([]string, 0, len(oemConfigs))
|
|
||||||
for k := range oemConfigs {
|
|
||||||
oems = append(oems, k)
|
|
||||||
}
|
|
||||||
fmt.Printf("Invalid option to -oem: %q. Supported options: %q\n", flags.oem, oems)
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
if flags.printVersion == true {
|
|
||||||
fmt.Printf("coreos-cloudinit %s\n", version)
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch flags.convertNetconf {
|
|
||||||
case "":
|
|
||||||
case "debian":
|
|
||||||
case "packet":
|
|
||||||
case "vmware":
|
|
||||||
default:
|
|
||||||
fmt.Printf("Invalid option to -convert-netconf: '%s'. Supported options: 'debian, packet, vmware'\n", flags.convertNetconf)
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
dss := getDatasources()
|
|
||||||
if len(dss) == 0 {
|
|
||||||
fmt.Println("Provide at least one of --from-file, --from-configdrive, --from-ec2-metadata, --from-gce-metadata, --from-cloudsigma-metadata, --from-packet-metadata, --from-digitalocean-metadata, --from-vmware-guestinfo, --from-waagent, --from-url or --from-proc-cmdline")
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
|
|
||||||
ds := selectDatasource(dss)
|
|
||||||
if ds == nil {
|
|
||||||
log.Println("No datasources available in time")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Fetching user-data from datasource of type %q\n", ds.Type())
|
|
||||||
userdataBytes, err := ds.FetchUserdata()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed fetching user-data from datasource: %v. Continuing...\n", err)
|
|
||||||
failure = true
|
|
||||||
}
|
|
||||||
userdataBytes, err = decompressIfGzip(userdataBytes)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed decompressing user-data from datasource: %v. Continuing...\n", err)
|
|
||||||
failure = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if report, err := validate.Validate(userdataBytes); err == nil {
|
|
||||||
ret := 0
|
|
||||||
for _, e := range report.Entries() {
|
|
||||||
log.Println(e)
|
|
||||||
ret = 1
|
|
||||||
}
|
|
||||||
if flags.validate {
|
|
||||||
os.Exit(ret)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Printf("Failed while validating user_data (%q)\n", err)
|
|
||||||
if flags.validate {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Fetching meta-data from datasource of type %q\n", ds.Type())
|
|
||||||
metadata, err := ds.FetchMetadata()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed fetching meta-data from datasource: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply environment to user-data
|
|
||||||
env := initialize.NewEnvironment("/", ds.ConfigRoot(), flags.workspace, flags.sshKeyName, metadata)
|
|
||||||
userdata := env.Apply(string(userdataBytes))
|
|
||||||
|
|
||||||
var ccu *config.CloudConfig
|
|
||||||
var script *config.Script
|
|
||||||
switch ud, err := initialize.ParseUserData(userdata); err {
|
|
||||||
case initialize.ErrIgnitionConfig:
|
|
||||||
fmt.Printf("Detected an Ignition config. Exiting...")
|
|
||||||
os.Exit(0)
|
|
||||||
case nil:
|
|
||||||
switch t := ud.(type) {
|
|
||||||
case *config.CloudConfig:
|
|
||||||
ccu = t
|
|
||||||
case *config.Script:
|
|
||||||
script = t
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
fmt.Printf("Failed to parse user-data: %v\nContinuing...\n", err)
|
|
||||||
failure = true
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Merging cloud-config from meta-data and user-data")
|
|
||||||
cc := mergeConfigs(ccu, metadata)
|
|
||||||
|
|
||||||
var ifaces []network.InterfaceGenerator
|
|
||||||
if flags.convertNetconf != "" {
|
|
||||||
var err error
|
|
||||||
switch flags.convertNetconf {
|
|
||||||
case "debian":
|
|
||||||
ifaces, err = network.ProcessDebianNetconf(metadata.NetworkConfig.([]byte))
|
|
||||||
case "packet":
|
|
||||||
ifaces, err = network.ProcessPacketNetconf(metadata.NetworkConfig.(packet.NetworkData))
|
|
||||||
case "vmware":
|
|
||||||
ifaces, err = network.ProcessVMwareNetconf(metadata.NetworkConfig.(map[string]string))
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("Unsupported network config format %q", flags.convertNetconf)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed to generate interfaces: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = initialize.Apply(cc, ifaces, env); err != nil {
|
|
||||||
log.Printf("Failed to apply cloud-config: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if script != nil {
|
|
||||||
if err = runScript(*script, env); err != nil {
|
|
||||||
log.Printf("Failed to run script: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if failure && !flags.ignoreFailure {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// mergeConfigs merges certain options from md (meta-data from the datasource)
|
|
||||||
// onto cc (a CloudConfig derived from user-data), if they are not already set
|
|
||||||
// on cc (i.e. user-data always takes precedence)
|
|
||||||
func mergeConfigs(cc *config.CloudConfig, md datasource.Metadata) (out config.CloudConfig) {
|
|
||||||
if cc != nil {
|
|
||||||
out = *cc
|
|
||||||
}
|
|
||||||
|
|
||||||
if md.Hostname != "" {
|
|
||||||
if out.Hostname != "" {
|
|
||||||
log.Printf("Warning: user-data hostname (%s) overrides metadata hostname (%s)\n", out.Hostname, md.Hostname)
|
|
||||||
} else {
|
|
||||||
out.Hostname = md.Hostname
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, key := range md.SSHPublicKeys {
|
|
||||||
out.SSHAuthorizedKeys = append(out.SSHAuthorizedKeys, key)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// getDatasources creates a slice of possible Datasources for cloudinit based
|
|
||||||
// on the different source command-line flags.
|
|
||||||
func getDatasources() []datasource.Datasource {
|
|
||||||
dss := make([]datasource.Datasource, 0, 5)
|
|
||||||
if flags.sources.file != "" {
|
|
||||||
dss = append(dss, file.NewDatasource(flags.sources.file))
|
|
||||||
}
|
|
||||||
if flags.sources.url != "" {
|
|
||||||
dss = append(dss, url.NewDatasource(flags.sources.url))
|
|
||||||
}
|
|
||||||
if flags.sources.configDrive != "" {
|
|
||||||
dss = append(dss, configdrive.NewDatasource(flags.sources.configDrive))
|
|
||||||
}
|
|
||||||
if flags.sources.metadataService {
|
|
||||||
dss = append(dss, ec2.NewDatasource(ec2.DefaultAddress))
|
|
||||||
}
|
|
||||||
if flags.sources.ec2MetadataService != "" {
|
|
||||||
dss = append(dss, ec2.NewDatasource(flags.sources.ec2MetadataService))
|
|
||||||
}
|
|
||||||
if flags.sources.gceMetadataService != "" {
|
|
||||||
dss = append(dss, gce.NewDatasource(flags.sources.gceMetadataService))
|
|
||||||
}
|
|
||||||
if flags.sources.cloudSigmaMetadataService {
|
|
||||||
dss = append(dss, cloudsigma.NewServerContextService())
|
|
||||||
}
|
|
||||||
if flags.sources.digitalOceanMetadataService != "" {
|
|
||||||
dss = append(dss, digitalocean.NewDatasource(flags.sources.digitalOceanMetadataService))
|
|
||||||
}
|
|
||||||
if flags.sources.waagent != "" {
|
|
||||||
dss = append(dss, waagent.NewDatasource(flags.sources.waagent))
|
|
||||||
}
|
|
||||||
if flags.sources.packetMetadataService != "" {
|
|
||||||
dss = append(dss, packet.NewDatasource(flags.sources.packetMetadataService))
|
|
||||||
}
|
|
||||||
if flags.sources.procCmdLine {
|
|
||||||
dss = append(dss, proc_cmdline.NewDatasource())
|
|
||||||
}
|
|
||||||
if flags.sources.vmware {
|
|
||||||
dss = append(dss, vmware.NewDatasource(""))
|
|
||||||
}
|
|
||||||
if flags.sources.ovfEnv != "" {
|
|
||||||
dss = append(dss, vmware.NewDatasource(flags.sources.ovfEnv))
|
|
||||||
}
|
|
||||||
return dss
|
|
||||||
}
|
|
||||||
|
|
||||||
// selectDatasource attempts to choose a valid Datasource to use based on its
|
|
||||||
// current availability. The first Datasource to report to be available is
|
|
||||||
// returned. Datasources will be retried if possible if they are not
|
|
||||||
// immediately available. If all Datasources are permanently unavailable or
|
|
||||||
// datasourceTimeout is reached before one becomes available, nil is returned.
|
|
||||||
func selectDatasource(sources []datasource.Datasource) datasource.Datasource {
|
|
||||||
ds := make(chan datasource.Datasource)
|
|
||||||
stop := make(chan struct{})
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
for _, s := range sources {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(s datasource.Datasource) {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
duration := datasourceInterval
|
|
||||||
for {
|
|
||||||
log.Printf("Checking availability of %q\n", s.Type())
|
|
||||||
if s.IsAvailable() {
|
|
||||||
ds <- s
|
|
||||||
return
|
|
||||||
} else if !s.AvailabilityChanges() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-stop:
|
|
||||||
return
|
|
||||||
case <-time.After(duration):
|
|
||||||
duration = pkg.ExpBackoff(duration, datasourceMaxInterval)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
go func() {
|
|
||||||
wg.Wait()
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
|
|
||||||
var s datasource.Datasource
|
|
||||||
select {
|
|
||||||
case s = <-ds:
|
|
||||||
case <-done:
|
|
||||||
case <-time.After(datasourceTimeout):
|
|
||||||
}
|
|
||||||
|
|
||||||
close(stop)
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(jonboulle): this should probably be refactored and moved into a different module
|
|
||||||
func runScript(script config.Script, env *initialize.Environment) error {
|
|
||||||
err := initialize.PrepWorkspace(env.Workspace())
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Failed preparing workspace: %v\n", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
path, err := initialize.PersistScriptInWorkspace(script, env.Workspace())
|
|
||||||
if err == nil {
|
|
||||||
var name string
|
|
||||||
name, err = system.ExecuteScript(path)
|
|
||||||
initialize.PersistUnitNameInWorkspace(name, env.Workspace())
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
const gzipMagicBytes = "\x1f\x8b"
|
|
||||||
|
|
||||||
func decompressIfGzip(userdataBytes []byte) ([]byte, error) {
|
|
||||||
if !bytes.HasPrefix(userdataBytes, []byte(gzipMagicBytes)) {
|
|
||||||
return userdataBytes, nil
|
|
||||||
}
|
|
||||||
gzr, err := gzip.NewReader(bytes.NewReader(userdataBytes))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer gzr.Close()
|
|
||||||
return ioutil.ReadAll(gzr)
|
|
||||||
}
|
|
@@ -1,294 +0,0 @@
|
|||||||
// Copyright 2015 CoreOS, Inc.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package initialize
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"github.com/rancher/os/config/cloudinit/config"
|
|
||||||
"github.com/rancher/os/config/cloudinit/network"
|
|
||||||
"github.com/rancher/os/config/cloudinit/system"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CloudConfigFile represents a CoreOS specific configuration option that can generate
|
|
||||||
// an associated system.File to be written to disk
|
|
||||||
type CloudConfigFile interface {
|
|
||||||
// File should either return (*system.File, error), or (nil, nil) if nothing
|
|
||||||
// needs to be done for this configuration option.
|
|
||||||
File() (*system.File, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloudConfigUnit represents a CoreOS specific configuration option that can generate
|
|
||||||
// associated system.Units to be created/enabled appropriately
|
|
||||||
type CloudConfigUnit interface {
|
|
||||||
Units() []system.Unit
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply renders a CloudConfig to an Environment. This can involve things like
|
|
||||||
// configuring the hostname, adding new users, writing various configuration
|
|
||||||
// files to disk, and manipulating systemd services.
|
|
||||||
func Apply(cfg config.CloudConfig, ifaces []network.InterfaceGenerator, env *Environment) error {
|
|
||||||
if cfg.Hostname != "" {
|
|
||||||
if err := system.SetHostname(cfg.Hostname); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Printf("Set hostname to %s", cfg.Hostname)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, user := range cfg.Users {
|
|
||||||
if user.Name == "" {
|
|
||||||
log.Printf("User object has no 'name' field, skipping")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if system.UserExists(&user) {
|
|
||||||
log.Printf("User '%s' exists, ignoring creation-time fields", user.Name)
|
|
||||||
if user.PasswordHash != "" {
|
|
||||||
log.Printf("Setting '%s' user's password", user.Name)
|
|
||||||
if err := system.SetUserPassword(user.Name, user.PasswordHash); err != nil {
|
|
||||||
log.Printf("Failed setting '%s' user's password: %v", user.Name, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Printf("Creating user '%s'", user.Name)
|
|
||||||
if err := system.CreateUser(&user); err != nil {
|
|
||||||
log.Printf("Failed creating user '%s': %v", user.Name, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(user.SSHAuthorizedKeys) > 0 {
|
|
||||||
log.Printf("Authorizing %d SSH keys for user '%s'", len(user.SSHAuthorizedKeys), user.Name)
|
|
||||||
if err := system.AuthorizeSSHKeys(user.Name, env.SSHKeyName(), user.SSHAuthorizedKeys); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if user.SSHImportGithubUser != "" {
|
|
||||||
log.Printf("Authorizing github user %s SSH keys for CoreOS user '%s'", user.SSHImportGithubUser, user.Name)
|
|
||||||
if err := SSHImportGithubUser(user.Name, user.SSHImportGithubUser); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, u := range user.SSHImportGithubUsers {
|
|
||||||
log.Printf("Authorizing github user %s SSH keys for CoreOS user '%s'", u, user.Name)
|
|
||||||
if err := SSHImportGithubUser(user.Name, u); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if user.SSHImportURL != "" {
|
|
||||||
log.Printf("Authorizing SSH keys for CoreOS user '%s' from '%s'", user.Name, user.SSHImportURL)
|
|
||||||
if err := SSHImportKeysFromURL(user.Name, user.SSHImportURL); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(cfg.SSHAuthorizedKeys) > 0 {
|
|
||||||
err := system.AuthorizeSSHKeys("core", env.SSHKeyName(), cfg.SSHAuthorizedKeys)
|
|
||||||
if err == nil {
|
|
||||||
log.Printf("Authorized SSH keys for core user")
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var writeFiles []system.File
|
|
||||||
for _, file := range cfg.WriteFiles {
|
|
||||||
writeFiles = append(writeFiles, system.File{File: file})
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ccf := range []CloudConfigFile{
|
|
||||||
system.OEM{OEM: cfg.CoreOS.OEM},
|
|
||||||
system.Update{Update: cfg.CoreOS.Update, ReadConfig: system.DefaultReadConfig},
|
|
||||||
system.EtcHosts{EtcHosts: cfg.ManageEtcHosts},
|
|
||||||
system.Flannel{Flannel: cfg.CoreOS.Flannel},
|
|
||||||
} {
|
|
||||||
f, err := ccf.File()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if f != nil {
|
|
||||||
writeFiles = append(writeFiles, *f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var units []system.Unit
|
|
||||||
for _, u := range cfg.CoreOS.Units {
|
|
||||||
units = append(units, system.Unit{Unit: u})
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ccu := range []CloudConfigUnit{
|
|
||||||
system.Etcd{Etcd: cfg.CoreOS.Etcd},
|
|
||||||
system.Etcd2{Etcd2: cfg.CoreOS.Etcd2},
|
|
||||||
system.Fleet{Fleet: cfg.CoreOS.Fleet},
|
|
||||||
system.Locksmith{Locksmith: cfg.CoreOS.Locksmith},
|
|
||||||
system.Update{Update: cfg.CoreOS.Update, ReadConfig: system.DefaultReadConfig},
|
|
||||||
} {
|
|
||||||
units = append(units, ccu.Units()...)
|
|
||||||
}
|
|
||||||
|
|
||||||
wroteEnvironment := false
|
|
||||||
for _, file := range writeFiles {
|
|
||||||
fullPath, err := system.WriteFile(&file, env.Root())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if path.Clean(file.Path) == "/etc/environment" {
|
|
||||||
wroteEnvironment = true
|
|
||||||
}
|
|
||||||
log.Printf("Wrote file %s to filesystem", fullPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !wroteEnvironment {
|
|
||||||
ef := env.DefaultEnvironmentFile()
|
|
||||||
if ef != nil {
|
|
||||||
err := system.WriteEnvFile(ef, env.Root())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Printf("Updated /etc/environment")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ifaces) > 0 {
|
|
||||||
units = append(units, createNetworkingUnits(ifaces)...)
|
|
||||||
if err := system.RestartNetwork(ifaces); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
um := system.NewUnitManager(env.Root())
|
|
||||||
return processUnits(units, env.Root(), um)
|
|
||||||
}
|
|
||||||
|
|
||||||
func createNetworkingUnits(interfaces []network.InterfaceGenerator) (units []system.Unit) {
|
|
||||||
appendNewUnit := func(units []system.Unit, name, content string) []system.Unit {
|
|
||||||
if content == "" {
|
|
||||||
return units
|
|
||||||
}
|
|
||||||
return append(units, system.Unit{Unit: config.Unit{
|
|
||||||
Name: name,
|
|
||||||
Runtime: true,
|
|
||||||
Content: content,
|
|
||||||
}})
|
|
||||||
}
|
|
||||||
for _, i := range interfaces {
|
|
||||||
units = appendNewUnit(units, fmt.Sprintf("%s.netdev", i.Filename()), i.Netdev())
|
|
||||||
units = appendNewUnit(units, fmt.Sprintf("%s.link", i.Filename()), i.Link())
|
|
||||||
units = appendNewUnit(units, fmt.Sprintf("%s.network", i.Filename()), i.Network())
|
|
||||||
}
|
|
||||||
return units
|
|
||||||
}
|
|
||||||
|
|
||||||
// processUnits takes a set of Units and applies them to the given root using
|
|
||||||
// the given UnitManager. This can involve things like writing unit files to
|
|
||||||
// disk, masking/unmasking units, or invoking systemd
|
|
||||||
// commands against units. It returns any error encountered.
|
|
||||||
func processUnits(units []system.Unit, root string, um system.UnitManager) error {
|
|
||||||
type action struct {
|
|
||||||
unit system.Unit
|
|
||||||
command string
|
|
||||||
}
|
|
||||||
actions := make([]action, 0, len(units))
|
|
||||||
reload := false
|
|
||||||
restartNetworkd := false
|
|
||||||
for _, unit := range units {
|
|
||||||
if unit.Name == "" {
|
|
||||||
log.Printf("Skipping unit without name")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if unit.Content != "" {
|
|
||||||
log.Printf("Writing unit %q to filesystem", unit.Name)
|
|
||||||
if err := um.PlaceUnit(unit); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Printf("Wrote unit %q", unit.Name)
|
|
||||||
reload = true
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, dropin := range unit.DropIns {
|
|
||||||
if dropin.Name != "" && dropin.Content != "" {
|
|
||||||
log.Printf("Writing drop-in unit %q to filesystem", dropin.Name)
|
|
||||||
if err := um.PlaceUnitDropIn(unit, dropin); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Printf("Wrote drop-in unit %q", dropin.Name)
|
|
||||||
reload = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if unit.Mask {
|
|
||||||
log.Printf("Masking unit file %q", unit.Name)
|
|
||||||
if err := um.MaskUnit(unit); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if unit.Runtime {
|
|
||||||
log.Printf("Ensuring runtime unit file %q is unmasked", unit.Name)
|
|
||||||
if err := um.UnmaskUnit(unit); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if unit.Enable {
|
|
||||||
if unit.Group() != "network" {
|
|
||||||
log.Printf("Enabling unit file %q", unit.Name)
|
|
||||||
if err := um.EnableUnitFile(unit); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Printf("Enabled unit %q", unit.Name)
|
|
||||||
} else {
|
|
||||||
log.Printf("Skipping enable for network-like unit %q", unit.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if unit.Group() == "network" {
|
|
||||||
restartNetworkd = true
|
|
||||||
} else if unit.Command != "" {
|
|
||||||
actions = append(actions, action{unit, unit.Command})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if reload {
|
|
||||||
if err := um.DaemonReload(); err != nil {
|
|
||||||
return errors.New(fmt.Sprintf("failed systemd daemon-reload: %s", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if restartNetworkd {
|
|
||||||
log.Printf("Restarting systemd-networkd")
|
|
||||||
networkd := system.Unit{Unit: config.Unit{Name: "systemd-networkd.service"}}
|
|
||||||
res, err := um.RunUnitCommand(networkd, "restart")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Printf("Restarted systemd-networkd (%s)", res)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, action := range actions {
|
|
||||||
log.Printf("Calling unit command %q on %q", action.command, action.unit.Name)
|
|
||||||
res, err := um.RunUnitCommand(action.unit, action.command)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
log.Printf("Result of %q on %q: %s", action.command, action.unit.Name, res)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@@ -1,95 +0,0 @@
|
|||||||
// Copyright 2015 CoreOS, Inc.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/rancher/os/config/cloudinit/config"
|
|
||||||
"github.com/rancher/os/config/cloudinit/network"
|
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/netlink"
|
|
||||||
)
|
|
||||||
|
|
||||||
func RestartNetwork(interfaces []network.InterfaceGenerator) (err error) {
|
|
||||||
defer func() {
|
|
||||||
if e := restartNetworkd(); e != nil {
|
|
||||||
err = e
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err = downNetworkInterfaces(interfaces); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = maybeProbe8012q(interfaces); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return maybeProbeBonding(interfaces)
|
|
||||||
}
|
|
||||||
|
|
||||||
func downNetworkInterfaces(interfaces []network.InterfaceGenerator) error {
|
|
||||||
sysInterfaceMap := make(map[string]*net.Interface)
|
|
||||||
if systemInterfaces, err := net.Interfaces(); err == nil {
|
|
||||||
for _, iface := range systemInterfaces {
|
|
||||||
iface := iface
|
|
||||||
sysInterfaceMap[iface.Name] = &iface
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, iface := range interfaces {
|
|
||||||
if systemInterface, ok := sysInterfaceMap[iface.Name()]; ok {
|
|
||||||
log.Printf("Taking down interface %q\n", systemInterface.Name)
|
|
||||||
if err := netlink.NetworkLinkDown(systemInterface); err != nil {
|
|
||||||
log.Printf("Error while downing interface %q (%s). Continuing...\n", systemInterface.Name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func maybeProbe8012q(interfaces []network.InterfaceGenerator) error {
|
|
||||||
for _, iface := range interfaces {
|
|
||||||
if iface.Type() == "vlan" {
|
|
||||||
log.Printf("Probing LKM %q (%q)\n", "8021q", "8021q")
|
|
||||||
return exec.Command("modprobe", "8021q").Run()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func maybeProbeBonding(interfaces []network.InterfaceGenerator) error {
|
|
||||||
for _, iface := range interfaces {
|
|
||||||
if iface.Type() == "bond" {
|
|
||||||
args := append([]string{"bonding"}, strings.Split(iface.ModprobeParams(), " ")...)
|
|
||||||
log.Printf("Probing LKM %q (%q)\n", "bonding", args)
|
|
||||||
return exec.Command("modprobe", args...).Run()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func restartNetworkd() error {
|
|
||||||
log.Printf("Restarting networkd.service\n")
|
|
||||||
networkd := Unit{config.Unit{Name: "systemd-networkd.service"}}
|
|
||||||
_, err := NewUnitManager("").RunUnitCommand(networkd, "restart")
|
|
||||||
return err
|
|
||||||
}
|
|
@@ -1,205 +0,0 @@
|
|||||||
// Copyright 2015 CoreOS, Inc.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/rancher/os/config/cloudinit/config"
|
|
||||||
"github.com/coreos/go-systemd/dbus"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewUnitManager(root string) UnitManager {
|
|
||||||
return &systemd{root}
|
|
||||||
}
|
|
||||||
|
|
||||||
type systemd struct {
|
|
||||||
root string
|
|
||||||
}
|
|
||||||
|
|
||||||
// fakeMachineID is placed on non-usr CoreOS images and should
|
|
||||||
// never be used as a true MachineID
|
|
||||||
const fakeMachineID = "42000000000000000000000000000042"
|
|
||||||
|
|
||||||
// PlaceUnit writes a unit file at its desired destination, creating parent
|
|
||||||
// directories as necessary.
|
|
||||||
func (s *systemd) PlaceUnit(u Unit) error {
|
|
||||||
file := File{config.File{
|
|
||||||
Path: u.Destination(s.root),
|
|
||||||
Content: u.Content,
|
|
||||||
RawFilePermissions: "0644",
|
|
||||||
}}
|
|
||||||
|
|
||||||
_, err := WriteFile(&file, "/")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// PlaceUnitDropIn writes a unit drop-in file at its desired destination,
|
|
||||||
// creating parent directories as necessary.
|
|
||||||
func (s *systemd) PlaceUnitDropIn(u Unit, d config.UnitDropIn) error {
|
|
||||||
file := File{config.File{
|
|
||||||
Path: u.DropInDestination(s.root, d),
|
|
||||||
Content: d.Content,
|
|
||||||
RawFilePermissions: "0644",
|
|
||||||
}}
|
|
||||||
|
|
||||||
_, err := WriteFile(&file, "/")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *systemd) EnableUnitFile(u Unit) error {
|
|
||||||
conn, err := dbus.New()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
units := []string{u.Name}
|
|
||||||
_, _, err = conn.EnableUnitFiles(units, u.Runtime, true)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *systemd) RunUnitCommand(u Unit, c string) (string, error) {
|
|
||||||
conn, err := dbus.New()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var fn func(string, string) (string, error)
|
|
||||||
switch c {
|
|
||||||
case "start":
|
|
||||||
fn = conn.StartUnit
|
|
||||||
case "stop":
|
|
||||||
fn = conn.StopUnit
|
|
||||||
case "restart":
|
|
||||||
fn = conn.RestartUnit
|
|
||||||
case "reload":
|
|
||||||
fn = conn.ReloadUnit
|
|
||||||
case "try-restart":
|
|
||||||
fn = conn.TryRestartUnit
|
|
||||||
case "reload-or-restart":
|
|
||||||
fn = conn.ReloadOrRestartUnit
|
|
||||||
case "reload-or-try-restart":
|
|
||||||
fn = conn.ReloadOrTryRestartUnit
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("Unsupported systemd command %q", c)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fn(u.Name, "replace")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *systemd) DaemonReload() error {
|
|
||||||
conn, err := dbus.New()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn.Reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaskUnit masks the given Unit by symlinking its unit file to
|
|
||||||
// /dev/null, analogous to `systemctl mask`.
|
|
||||||
// N.B.: Unlike `systemctl mask`, this function will *remove any existing unit
|
|
||||||
// file at the location*, to ensure that the mask will succeed.
|
|
||||||
func (s *systemd) MaskUnit(u Unit) error {
|
|
||||||
masked := u.Destination(s.root)
|
|
||||||
if _, err := os.Stat(masked); os.IsNotExist(err) {
|
|
||||||
if err := os.MkdirAll(path.Dir(masked), os.FileMode(0755)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else if err := os.Remove(masked); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return os.Symlink("/dev/null", masked)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmaskUnit is analogous to systemd's unit_file_unmask. If the file
|
|
||||||
// associated with the given Unit is empty or appears to be a symlink to
|
|
||||||
// /dev/null, it is removed.
|
|
||||||
func (s *systemd) UnmaskUnit(u Unit) error {
|
|
||||||
masked := u.Destination(s.root)
|
|
||||||
ne, err := nullOrEmpty(masked)
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return nil
|
|
||||||
} else if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !ne {
|
|
||||||
log.Printf("%s is not null or empty, refusing to unmask", masked)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return os.Remove(masked)
|
|
||||||
}
|
|
||||||
|
|
||||||
// nullOrEmpty checks whether a given path appears to be an empty regular file
|
|
||||||
// or a symlink to /dev/null
|
|
||||||
func nullOrEmpty(path string) (bool, error) {
|
|
||||||
fi, err := os.Stat(path)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
m := fi.Mode()
|
|
||||||
if m.IsRegular() && fi.Size() <= 0 {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
if m&os.ModeCharDevice > 0 {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExecuteScript(scriptPath string) (string, error) {
|
|
||||||
props := []dbus.Property{
|
|
||||||
dbus.PropDescription("Unit generated and executed by coreos-cloudinit on behalf of user"),
|
|
||||||
dbus.PropExecStart([]string{"/bin/bash", scriptPath}, false),
|
|
||||||
}
|
|
||||||
|
|
||||||
base := path.Base(scriptPath)
|
|
||||||
name := fmt.Sprintf("coreos-cloudinit-%s.service", base)
|
|
||||||
|
|
||||||
log.Printf("Creating transient systemd unit '%s'", name)
|
|
||||||
|
|
||||||
conn, err := dbus.New()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = conn.StartTransientUnit(name, "replace", props...)
|
|
||||||
return name, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetHostname(hostname string) error {
|
|
||||||
return exec.Command("hostnamectl", "set-hostname", hostname).Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func Hostname() (string, error) {
|
|
||||||
return os.Hostname()
|
|
||||||
}
|
|
||||||
|
|
||||||
func MachineID(root string) string {
|
|
||||||
contents, _ := ioutil.ReadFile(path.Join(root, "etc", "machine-id"))
|
|
||||||
id := strings.TrimSpace(string(contents))
|
|
||||||
|
|
||||||
if id == fakeMachineID {
|
|
||||||
id = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return id
|
|
||||||
}
|
|
@@ -1,280 +0,0 @@
|
|||||||
// Copyright 2015 CoreOS, Inc.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package system
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/rancher/os/config/cloudinit/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestPlaceUnit(t *testing.T) {
|
|
||||||
tests := []config.Unit{
|
|
||||||
{
|
|
||||||
Name: "50-eth0.network",
|
|
||||||
Runtime: true,
|
|
||||||
Content: "[Match]\nName=eth47\n\n[Network]\nAddress=10.209.171.177/19\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "media-state.mount",
|
|
||||||
Content: "[Mount]\nWhat=/dev/sdb1\nWhere=/media/state\n",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("Unable to create tempdir: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
u := Unit{tt}
|
|
||||||
sd := &systemd{dir}
|
|
||||||
|
|
||||||
if err := sd.PlaceUnit(u); err != nil {
|
|
||||||
t.Fatalf("PlaceUnit(): bad error (%+v): want nil, got %s", tt, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fi, err := os.Stat(u.Destination(dir))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Stat(): bad error (%+v): want nil, got %s", tt, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if mode := fi.Mode(); mode != os.FileMode(0644) {
|
|
||||||
t.Errorf("bad filemode (%+v): want %v, got %v", tt, os.FileMode(0644), mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := ioutil.ReadFile(u.Destination(dir))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ReadFile(): bad error (%+v): want nil, got %s", tt, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(c) != tt.Content {
|
|
||||||
t.Errorf("bad contents (%+v): want %q, got %q", tt, tt.Content, string(c))
|
|
||||||
}
|
|
||||||
|
|
||||||
os.RemoveAll(dir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPlaceUnitDropIn(t *testing.T) {
|
|
||||||
tests := []config.Unit{
|
|
||||||
{
|
|
||||||
Name: "false.service",
|
|
||||||
Runtime: true,
|
|
||||||
DropIns: []config.UnitDropIn{
|
|
||||||
{
|
|
||||||
Name: "00-true.conf",
|
|
||||||
Content: "[Service]\nExecStart=\nExecStart=/usr/bin/true\n",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "true.service",
|
|
||||||
DropIns: []config.UnitDropIn{
|
|
||||||
{
|
|
||||||
Name: "00-false.conf",
|
|
||||||
Content: "[Service]\nExecStart=\nExecStart=/usr/bin/false\n",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
|
||||||
if err != nil {
|
|
||||||
panic(fmt.Sprintf("Unable to create tempdir: %v", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
u := Unit{tt}
|
|
||||||
sd := &systemd{dir}
|
|
||||||
|
|
||||||
if err := sd.PlaceUnitDropIn(u, u.DropIns[0]); err != nil {
|
|
||||||
t.Fatalf("PlaceUnit(): bad error (%+v): want nil, got %s", tt, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fi, err := os.Stat(u.DropInDestination(dir, u.DropIns[0]))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Stat(): bad error (%+v): want nil, got %s", tt, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if mode := fi.Mode(); mode != os.FileMode(0644) {
|
|
||||||
t.Errorf("bad filemode (%+v): want %v, got %v", tt, os.FileMode(0644), mode)
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := ioutil.ReadFile(u.DropInDestination(dir, u.DropIns[0]))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ReadFile(): bad error (%+v): want nil, got %s", tt, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if string(c) != u.DropIns[0].Content {
|
|
||||||
t.Errorf("bad contents (%+v): want %q, got %q", tt, u.DropIns[0].Content, string(c))
|
|
||||||
}
|
|
||||||
|
|
||||||
os.RemoveAll(dir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMachineID(t *testing.T) {
|
|
||||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to create tempdir: %v", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
os.Mkdir(path.Join(dir, "etc"), os.FileMode(0755))
|
|
||||||
ioutil.WriteFile(path.Join(dir, "etc", "machine-id"), []byte("node007\n"), os.FileMode(0444))
|
|
||||||
|
|
||||||
if MachineID(dir) != "node007" {
|
|
||||||
t.Fatalf("File has incorrect contents")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMaskUnit(t *testing.T) {
|
|
||||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to create tempdir: %v", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
sd := &systemd{dir}
|
|
||||||
|
|
||||||
// Ensure mask works with units that do not currently exist
|
|
||||||
uf := Unit{config.Unit{Name: "foo.service"}}
|
|
||||||
if err := sd.MaskUnit(uf); err != nil {
|
|
||||||
t.Fatalf("Unable to mask new unit: %v", err)
|
|
||||||
}
|
|
||||||
fooPath := path.Join(dir, "etc", "systemd", "system", "foo.service")
|
|
||||||
fooTgt, err := os.Readlink(fooPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Unable to read link", err)
|
|
||||||
}
|
|
||||||
if fooTgt != "/dev/null" {
|
|
||||||
t.Fatal("unit not masked, got unit target", fooTgt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure mask works with unit files that already exist
|
|
||||||
ub := Unit{config.Unit{Name: "bar.service"}}
|
|
||||||
barPath := path.Join(dir, "etc", "systemd", "system", "bar.service")
|
|
||||||
if _, err := os.Create(barPath); err != nil {
|
|
||||||
t.Fatalf("Error creating new unit file: %v", err)
|
|
||||||
}
|
|
||||||
if err := sd.MaskUnit(ub); err != nil {
|
|
||||||
t.Fatalf("Unable to mask existing unit: %v", err)
|
|
||||||
}
|
|
||||||
barTgt, err := os.Readlink(barPath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Unable to read link", err)
|
|
||||||
}
|
|
||||||
if barTgt != "/dev/null" {
|
|
||||||
t.Fatal("unit not masked, got unit target", barTgt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUnmaskUnit(t *testing.T) {
|
|
||||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to create tempdir: %v", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
sd := &systemd{dir}
|
|
||||||
|
|
||||||
nilUnit := Unit{config.Unit{Name: "null.service"}}
|
|
||||||
if err := sd.UnmaskUnit(nilUnit); err != nil {
|
|
||||||
t.Errorf("unexpected error from unmasking nonexistent unit: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
uf := Unit{config.Unit{Name: "foo.service", Content: "[Service]\nExecStart=/bin/true"}}
|
|
||||||
dst := uf.Destination(dir)
|
|
||||||
if err := os.MkdirAll(path.Dir(dst), os.FileMode(0755)); err != nil {
|
|
||||||
t.Fatalf("Unable to create unit directory: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := os.Create(dst); err != nil {
|
|
||||||
t.Fatalf("Unable to write unit file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ioutil.WriteFile(dst, []byte(uf.Content), 700); err != nil {
|
|
||||||
t.Fatalf("Unable to write unit file: %v", err)
|
|
||||||
}
|
|
||||||
if err := sd.UnmaskUnit(uf); err != nil {
|
|
||||||
t.Errorf("unmask of non-empty unit returned unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
got, _ := ioutil.ReadFile(dst)
|
|
||||||
if string(got) != uf.Content {
|
|
||||||
t.Errorf("unmask of non-empty unit mutated unit contents unexpectedly")
|
|
||||||
}
|
|
||||||
|
|
||||||
ub := Unit{config.Unit{Name: "bar.service"}}
|
|
||||||
dst = ub.Destination(dir)
|
|
||||||
if err := os.Symlink("/dev/null", dst); err != nil {
|
|
||||||
t.Fatalf("Unable to create masked unit: %v", err)
|
|
||||||
}
|
|
||||||
if err := sd.UnmaskUnit(ub); err != nil {
|
|
||||||
t.Errorf("unmask of unit returned unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if _, err := os.Stat(dst); !os.IsNotExist(err) {
|
|
||||||
t.Errorf("expected %s to not exist after unmask, but got err: %s", dst, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNullOrEmpty(t *testing.T) {
|
|
||||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to create tempdir: %v", err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
non := path.Join(dir, "does_not_exist")
|
|
||||||
ne, err := nullOrEmpty(non)
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
t.Errorf("nullOrEmpty on nonexistent file returned bad error: %v", err)
|
|
||||||
}
|
|
||||||
if ne {
|
|
||||||
t.Errorf("nullOrEmpty returned true unxpectedly")
|
|
||||||
}
|
|
||||||
|
|
||||||
regEmpty := path.Join(dir, "regular_empty_file")
|
|
||||||
_, err = os.Create(regEmpty)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to create tempfile: %v", err)
|
|
||||||
}
|
|
||||||
gotNe, gotErr := nullOrEmpty(regEmpty)
|
|
||||||
if !gotNe || gotErr != nil {
|
|
||||||
t.Errorf("nullOrEmpty of regular empty file returned %t, %v - want true, nil", gotNe, gotErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
reg := path.Join(dir, "regular_file")
|
|
||||||
if err := ioutil.WriteFile(reg, []byte("asdf"), 700); err != nil {
|
|
||||||
t.Fatalf("Unable to create tempfile: %v", err)
|
|
||||||
}
|
|
||||||
gotNe, gotErr = nullOrEmpty(reg)
|
|
||||||
if gotNe || gotErr != nil {
|
|
||||||
t.Errorf("nullOrEmpty of regular file returned %t, %v - want false, nil", gotNe, gotErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
null := path.Join(dir, "null")
|
|
||||||
if err := os.Symlink(os.DevNull, null); err != nil {
|
|
||||||
t.Fatalf("Unable to create /dev/null link: %s", err)
|
|
||||||
}
|
|
||||||
gotNe, gotErr = nullOrEmpty(null)
|
|
||||||
if !gotNe || gotErr != nil {
|
|
||||||
t.Errorf("nullOrEmpty of null symlink returned %t, %v - want true, nil", gotNe, gotErr)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Reference in New Issue
Block a user