Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
521ecfdab5 | ||
|
6d0fdf1a47 | ||
|
ffc54b028c | ||
|
420f7cf202 | ||
|
624df676d0 | ||
|
75ed8dacf9 | ||
|
dcaabe4d4a | ||
|
92c57423ba | ||
|
7447e133c9 | ||
|
4e466c12da | ||
|
333468dba3 | ||
|
55c3a793ad | ||
|
eca51031c8 | ||
|
19522bcb82 | ||
|
62248ea33d | ||
|
d2a19cc86d | ||
|
08131ffab1 | ||
|
4a0019c669 | ||
|
3275ead1ec | ||
|
32b6a55724 | ||
|
6c43644369 | ||
|
e6593d49e6 | ||
|
ab752b239f |
@@ -16,7 +16,7 @@ We've designed our implementation to allow the same cloud-config file to work ac
|
|||||||
|
|
||||||
The cloud-config file uses the [YAML][yaml] file format, which uses whitespace and new-lines to delimit lists, associative arrays, and values.
|
The cloud-config file uses the [YAML][yaml] file format, which uses whitespace and new-lines to delimit lists, associative arrays, and values.
|
||||||
|
|
||||||
A cloud-config file should contain `#cloud-config`, followed by an associative array which has zero or more of the following keys:
|
A cloud-config file must contain `#cloud-config`, followed by an associative array which has zero or more of the following keys:
|
||||||
|
|
||||||
- `coreos`
|
- `coreos`
|
||||||
- `ssh_authorized_keys`
|
- `ssh_authorized_keys`
|
||||||
@@ -46,13 +46,13 @@ If the platform environment supports the templating feature of coreos-cloudinit
|
|||||||
#cloud-config
|
#cloud-config
|
||||||
|
|
||||||
coreos:
|
coreos:
|
||||||
etcd:
|
etcd:
|
||||||
name: node001
|
name: node001
|
||||||
# generate a new token for each unique cluster from https://discovery.etcd.io/new
|
# generate a new token for each unique cluster from https://discovery.etcd.io/new
|
||||||
discovery: https://discovery.etcd.io/<token>
|
discovery: https://discovery.etcd.io/<token>
|
||||||
# multi-region and multi-cloud deployments need to use $public_ipv4
|
# multi-region and multi-cloud deployments need to use $public_ipv4
|
||||||
addr: $public_ipv4:4001
|
addr: $public_ipv4:4001
|
||||||
peer-addr: $private_ipv4:7001
|
peer-addr: $private_ipv4:7001
|
||||||
```
|
```
|
||||||
|
|
||||||
...will generate a systemd unit drop-in like this:
|
...will generate a systemd unit drop-in like this:
|
||||||
@@ -66,7 +66,6 @@ Environment="ETCD_PEER_ADDR=192.0.2.13:7001"
|
|||||||
```
|
```
|
||||||
|
|
||||||
For more information about the available configuration parameters, see the [etcd documentation][etcd-config].
|
For more information about the available configuration parameters, see the [etcd documentation][etcd-config].
|
||||||
Note that hyphens in the coreos.etcd.* keys are mapped to underscores.
|
|
||||||
|
|
||||||
_Note: The `$private_ipv4` and `$public_ipv4` substitution variables referenced in other documents are only supported on Amazon EC2, Google Compute Engine, OpenStack, Rackspace, DigitalOcean, and Vagrant._
|
_Note: The `$private_ipv4` and `$public_ipv4` substitution variables referenced in other documents are only supported on Amazon EC2, Google Compute Engine, OpenStack, Rackspace, DigitalOcean, and Vagrant._
|
||||||
|
|
||||||
@@ -80,9 +79,9 @@ The `coreos.fleet.*` parameters work very similarly to `coreos.etcd.*`, and allo
|
|||||||
#cloud-config
|
#cloud-config
|
||||||
|
|
||||||
coreos:
|
coreos:
|
||||||
fleet:
|
fleet:
|
||||||
public-ip: $public_ipv4
|
public-ip: $public_ipv4
|
||||||
metadata: region=us-west
|
metadata: region=us-west
|
||||||
```
|
```
|
||||||
|
|
||||||
...will generate a systemd unit drop-in like this:
|
...will generate a systemd unit drop-in like this:
|
||||||
@@ -105,8 +104,8 @@ The `coreos.flannel.*` parameters also work very similarly to `coreos.etcd.*` an
|
|||||||
#cloud-config
|
#cloud-config
|
||||||
|
|
||||||
coreos:
|
coreos:
|
||||||
flannel:
|
flannel:
|
||||||
etcd-prefix: /coreos.com/network2
|
etcd-prefix: /coreos.com/network2
|
||||||
```
|
```
|
||||||
|
|
||||||
...will generate systemd unit drop-in like so:
|
...will generate systemd unit drop-in like so:
|
||||||
@@ -158,6 +157,10 @@ Each item is an object with the following fields:
|
|||||||
- **content**: Plaintext string representing entire unit file. If no value is provided, the unit is assumed to exist already.
|
- **content**: Plaintext string representing entire unit file. If no value is provided, the unit is assumed to exist already.
|
||||||
- **command**: Command to execute on unit: start, stop, reload, restart, try-restart, reload-or-restart, reload-or-try-restart. The default behavior is to not execute any commands.
|
- **command**: Command to execute on unit: start, stop, reload, restart, try-restart, reload-or-restart, reload-or-try-restart. The default behavior is to not execute any commands.
|
||||||
- **mask**: Whether to mask the unit file by symlinking it to `/dev/null` (analogous to `systemctl mask <name>`). Note that unlike `systemctl mask`, **this will destructively remove any existing unit file** located at `/etc/systemd/system/<unit>`, to ensure that the mask succeeds. The default value is false.
|
- **mask**: Whether to mask the unit file by symlinking it to `/dev/null` (analogous to `systemctl mask <name>`). Note that unlike `systemctl mask`, **this will destructively remove any existing unit file** located at `/etc/systemd/system/<unit>`, to ensure that the mask succeeds. The default value is false.
|
||||||
|
- **drop-ins**: A list of unit drop-ins with the following fields:
|
||||||
|
- **name**: String representing unit's name. Required.
|
||||||
|
- **content**: Plaintext string representing entire file. Required.
|
||||||
|
|
||||||
|
|
||||||
**NOTE:** The command field is ignored for all network, netdev, and link units. The systemd-networkd.service unit will be restarted in their place.
|
**NOTE:** The command field is ignored for all network, netdev, and link units. The systemd-networkd.service unit will be restarted in their place.
|
||||||
|
|
||||||
@@ -169,19 +172,34 @@ Write a unit to disk, automatically starting it.
|
|||||||
#cloud-config
|
#cloud-config
|
||||||
|
|
||||||
coreos:
|
coreos:
|
||||||
units:
|
units:
|
||||||
- name: docker-redis.service
|
- name: docker-redis.service
|
||||||
command: start
|
command: start
|
||||||
content: |
|
content: |
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Redis container
|
Description=Redis container
|
||||||
Author=Me
|
Author=Me
|
||||||
After=docker.service
|
After=docker.service
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Restart=always
|
Restart=always
|
||||||
ExecStart=/usr/bin/docker start -a redis_server
|
ExecStart=/usr/bin/docker start -a redis_server
|
||||||
ExecStop=/usr/bin/docker stop -t 2 redis_server
|
ExecStop=/usr/bin/docker stop -t 2 redis_server
|
||||||
|
```
|
||||||
|
|
||||||
|
Add the DOCKER_OPTS environment variable to docker.service.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
#cloud-config
|
||||||
|
|
||||||
|
coreos:
|
||||||
|
units:
|
||||||
|
- name: docker.service
|
||||||
|
drop-ins:
|
||||||
|
- name: 50-insecure-registry.conf
|
||||||
|
content: |
|
||||||
|
[Service]
|
||||||
|
Environment=DOCKER_OPTS='--insecure-registry="10.0.1.0/24"'
|
||||||
```
|
```
|
||||||
|
|
||||||
Start the built-in `etcd` and `fleet` services:
|
Start the built-in `etcd` and `fleet` services:
|
||||||
@@ -190,11 +208,11 @@ Start the built-in `etcd` and `fleet` services:
|
|||||||
#cloud-config
|
#cloud-config
|
||||||
|
|
||||||
coreos:
|
coreos:
|
||||||
units:
|
units:
|
||||||
- name: etcd.service
|
- name: etcd.service
|
||||||
command: start
|
command: start
|
||||||
- name: fleet.service
|
- name: fleet.service
|
||||||
command: start
|
command: start
|
||||||
```
|
```
|
||||||
|
|
||||||
### ssh_authorized_keys
|
### ssh_authorized_keys
|
||||||
|
@@ -261,9 +261,6 @@ Address=10.209.171.177/19
|
|||||||
if u.Name != "50-eth0.network" {
|
if u.Name != "50-eth0.network" {
|
||||||
t.Errorf("Unit has incorrect name %s", u.Name)
|
t.Errorf("Unit has incorrect name %s", u.Name)
|
||||||
}
|
}
|
||||||
if u.Type() != "network" {
|
|
||||||
t.Errorf("Unit has incorrect type '%s'", u.Type())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Coreos.OEM.ID != "rackspace" {
|
if cfg.Coreos.OEM.ID != "rackspace" {
|
||||||
@@ -338,26 +335,6 @@ func TestCloudConfigSerializationHeader(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestDropInIgnored asserts that users are unable to set DropIn=True on units
|
|
||||||
func TestDropInIgnored(t *testing.T) {
|
|
||||||
contents := `
|
|
||||||
coreos:
|
|
||||||
units:
|
|
||||||
- name: test
|
|
||||||
dropin: true
|
|
||||||
`
|
|
||||||
cfg, err := NewCloudConfig(contents)
|
|
||||||
if err != nil || len(cfg.Coreos.Units) != 1 {
|
|
||||||
t.Fatalf("Encountered unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
if len(cfg.Coreos.Units) != 1 || cfg.Coreos.Units[0].Name != "test" {
|
|
||||||
t.Fatalf("Expected 1 unit, but got %d: %v", len(cfg.Coreos.Units), cfg.Coreos.Units)
|
|
||||||
}
|
|
||||||
if cfg.Coreos.Units[0].DropIn {
|
|
||||||
t.Errorf("dropin option on unit in cloud-config was not ignored!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCloudConfigUsers(t *testing.T) {
|
func TestCloudConfigUsers(t *testing.T) {
|
||||||
contents := `
|
contents := `
|
||||||
users:
|
users:
|
||||||
@@ -513,6 +490,7 @@ func TestNormalizeKeys(t *testing.T) {
|
|||||||
{"a:\n b:\n - key-name: the-value\n", "a:\n b:\n - key_name: the-value\n"},
|
{"a:\n b:\n - key-name: the-value\n", "a:\n b:\n - key_name: the-value\n"},
|
||||||
|
|
||||||
{"coreos:\n update:\n reboot-strategy: off\n", "coreos:\n update:\n reboot_strategy: false\n"},
|
{"coreos:\n update:\n reboot-strategy: off\n", "coreos:\n update:\n reboot_strategy: false\n"},
|
||||||
|
{"coreos:\n update:\n reboot-strategy: 'off'\n", "coreos:\n update:\n reboot_strategy: \"off\"\n"},
|
||||||
} {
|
} {
|
||||||
out, err := normalizeConfig(tt.in)
|
out, err := normalizeConfig(tt.in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@@ -17,32 +17,37 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
type Etcd struct {
|
type Etcd struct {
|
||||||
Addr string `yaml:"addr" env:"ETCD_ADDR"`
|
Addr string `yaml:"addr" env:"ETCD_ADDR"`
|
||||||
BindAddr string `yaml:"bind_addr" env:"ETCD_BIND_ADDR"`
|
BindAddr string `yaml:"bind_addr" env:"ETCD_BIND_ADDR"`
|
||||||
CAFile string `yaml:"ca_file" env:"ETCD_CA_FILE"`
|
CAFile string `yaml:"ca_file" env:"ETCD_CA_FILE"`
|
||||||
CertFile string `yaml:"cert_file" env:"ETCD_CERT_FILE"`
|
CertFile string `yaml:"cert_file" env:"ETCD_CERT_FILE"`
|
||||||
ClusterActiveSize string `yaml:"cluster_active_size" env:"ETCD_CLUSTER_ACTIVE_SIZE"`
|
ClusterActiveSize int `yaml:"cluster_active_size" env:"ETCD_CLUSTER_ACTIVE_SIZE"`
|
||||||
ClusterRemoveDelay string `yaml:"cluster_remove_delay" env:"ETCD_CLUSTER_REMOVE_DELAY"`
|
ClusterRemoveDelay float64 `yaml:"cluster_remove_delay" env:"ETCD_CLUSTER_REMOVE_DELAY"`
|
||||||
ClusterSyncInterval string `yaml:"cluster_sync_interval" env:"ETCD_CLUSTER_SYNC_INTERVAL"`
|
ClusterSyncInterval float64 `yaml:"cluster_sync_interval" env:"ETCD_CLUSTER_SYNC_INTERVAL"`
|
||||||
Cors string `yaml:"cors" env:"ETCD_CORS"`
|
CorsOrigins string `yaml:"cors" env:"ETCD_CORS"`
|
||||||
CPUProfileFile string `yaml:"cpu_profile_file" env:"ETCD_CPU_PROFILE_FILE"`
|
DataDir string `yaml:"data_dir" env:"ETCD_DATA_DIR"`
|
||||||
DataDir string `yaml:"data_dir" env:"ETCD_DATA_DIR"`
|
Discovery string `yaml:"discovery" env:"ETCD_DISCOVERY"`
|
||||||
Discovery string `yaml:"discovery" env:"ETCD_DISCOVERY"`
|
GraphiteHost string `yaml:"graphite_host" env:"ETCD_GRAPHITE_HOST"`
|
||||||
HTTPReadTimeout string `yaml:"http_read_timeout" env:"ETCD_HTTP_READ_TIMEOUT"`
|
HTTPReadTimeout float64 `yaml:"http_read_timeout" env:"ETCD_HTTP_READ_TIMEOUT"`
|
||||||
HTTPWriteTimeout string `yaml:"http_write_timeout" env:"ETCD_HTTP_WRITE_TIMEOUT"`
|
HTTPWriteTimeout float64 `yaml:"http_write_timeout" env:"ETCD_HTTP_WRITE_TIMEOUT"`
|
||||||
KeyFile string `yaml:"key_file" env:"ETCD_KEY_FILE"`
|
KeyFile string `yaml:"key_file" env:"ETCD_KEY_FILE"`
|
||||||
MaxClusterSize string `yaml:"max_cluster_size" env:"ETCD_MAX_CLUSTER_SIZE"`
|
MaxResultBuffer int `yaml:"max_result_buffer" env:"ETCD_MAX_RESULT_BUFFER"`
|
||||||
MaxResultBuffer string `yaml:"max_result_buffer" env:"ETCD_MAX_RESULT_BUFFER"`
|
MaxRetryAttempts int `yaml:"max_retry_attempts" env:"ETCD_MAX_RETRY_ATTEMPTS"`
|
||||||
MaxRetryAttempts string `yaml:"max_retry_attempts" env:"ETCD_MAX_RETRY_ATTEMPTS"`
|
Name string `yaml:"name" env:"ETCD_NAME"`
|
||||||
Name string `yaml:"name" env:"ETCD_NAME"`
|
PeerAddr string `yaml:"peer_addr" env:"ETCD_PEER_ADDR"`
|
||||||
PeerAddr string `yaml:"peer_addr" env:"ETCD_PEER_ADDR"`
|
PeerBindAddr string `yaml:"peer_bind_addr" env:"ETCD_PEER_BIND_ADDR"`
|
||||||
PeerBindAddr string `yaml:"peer_bind_addr" env:"ETCD_PEER_BIND_ADDR"`
|
PeerCAFile string `yaml:"peer_ca_file" env:"ETCD_PEER_CA_FILE"`
|
||||||
PeerCAFile string `yaml:"peer_ca_file" env:"ETCD_PEER_CA_FILE"`
|
PeerCertFile string `yaml:"peer_cert_file" env:"ETCD_PEER_CERT_FILE"`
|
||||||
PeerCertFile string `yaml:"peer_cert_file" env:"ETCD_PEER_CERT_FILE"`
|
PeerElectionTimeout int `yaml:"peer_election_timeout" env:"ETCD_PEER_ELECTION_TIMEOUT"`
|
||||||
PeerKeyFile string `yaml:"peer_key_file" env:"ETCD_PEER_KEY_FILE"`
|
PeerHeartbeatInterval int `yaml:"peer_heartbeat_interval" env:"ETCD_PEER_HEARTBEAT_INTERVAL"`
|
||||||
Peers string `yaml:"peers" env:"ETCD_PEERS"`
|
PeerKeyFile string `yaml:"peer_key_file" env:"ETCD_PEER_KEY_FILE"`
|
||||||
PeersFile string `yaml:"peers_file" env:"ETCD_PEERS_FILE"`
|
Peers string `yaml:"peers" env:"ETCD_PEERS"`
|
||||||
Snapshot string `yaml:"snapshot" env:"ETCD_SNAPSHOT"`
|
PeersFile string `yaml:"peers_file" env:"ETCD_PEERS_FILE"`
|
||||||
Verbose string `yaml:"verbose" env:"ETCD_VERBOSE"`
|
RetryInterval float64 `yaml:"retry_interval" env:"ETCD_RETRY_INTERVAL"`
|
||||||
VeryVerbose string `yaml:"very_verbose" env:"ETCD_VERY_VERBOSE"`
|
Snapshot bool `yaml:"snapshot" env:"ETCD_SNAPSHOT"`
|
||||||
|
SnapshotCount int `yaml:"snapshot_count" env:"ETCD_SNAPSHOTCOUNT"`
|
||||||
|
StrTrace string `yaml:"trace" env:"ETCD_TRACE"`
|
||||||
|
Verbose bool `yaml:"verbose" env:"ETCD_VERBOSE"`
|
||||||
|
VeryVerbose bool `yaml:"very_verbose" env:"ETCD_VERY_VERBOSE"`
|
||||||
|
VeryVeryVerbose bool `yaml:"very_very_verbose" env:"ETCD_VERY_VERY_VERBOSE"`
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
type Flannel struct {
|
type Flannel struct {
|
||||||
EtcdEndpoint string `yaml:"etcd-endpoint" env:"FLANNELD_ETCD_ENDPOINT"`
|
EtcdEndpoint string `yaml:"etcd_endpoint" env:"FLANNELD_ETCD_ENDPOINT"`
|
||||||
EtcdPrefix string `yaml:"etcd-prefix" env:"FLANNELD_ETCD_PREFIX"`
|
EtcdPrefix string `yaml:"etcd_prefix" env:"FLANNELD_ETCD_PREFIX"`
|
||||||
IPMasq string `yaml:"ip-masq" env:"FLANNELD_IP_MASQ"`
|
IPMasq string `yaml:"ip_masq" env:"FLANNELD_IP_MASQ"`
|
||||||
SubnetFile string `yaml:"subnet-file" env:"FLANNELD_SUBNET_FILE"`
|
SubnetFile string `yaml:"subnet_file" env:"FLANNELD_SUBNET_FILE"`
|
||||||
Iface string `yaml:"interface" env:"FLANNELD_IFACE"`
|
Iface string `yaml:"interface" env:"FLANNELD_IFACE"`
|
||||||
}
|
}
|
||||||
|
@@ -17,14 +17,15 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
type Fleet struct {
|
type Fleet struct {
|
||||||
AgentTTL string `yaml:"agent_ttl" env:"FLEET_AGENT_TTL"`
|
AgentTTL string `yaml:"agent_ttl" env:"FLEET_AGENT_TTL"`
|
||||||
EngineReconcileInterval string `yaml:"engine_reconcile_interval" env:"FLEET_ENGINE_RECONCILE_INTERVAL"`
|
EngineReconcileInterval float64 `yaml:"engine_reconcile_interval" env:"FLEET_ENGINE_RECONCILE_INTERVAL"`
|
||||||
EtcdCAFile string `yaml:"etcd_cafile" env:"FLEET_ETCD_CAFILE"`
|
EtcdCAFile string `yaml:"etcd_cafile" env:"FLEET_ETCD_CAFILE"`
|
||||||
EtcdCertFile string `yaml:"etcd_certfile" env:"FLEET_ETCD_CERTFILE"`
|
EtcdCertFile string `yaml:"etcd_certfile" env:"FLEET_ETCD_CERTFILE"`
|
||||||
EtcdKeyFile string `yaml:"etcd_keyfile" env:"FLEET_ETCD_KEYFILE"`
|
EtcdKeyFile string `yaml:"etcd_keyfile" env:"FLEET_ETCD_KEYFILE"`
|
||||||
EtcdRequestTimeout string `yaml:"etcd_request_timeout" env:"FLEET_ETCD_REQUEST_TIMEOUT"`
|
EtcdKeyPrefix string `yaml:"etcd_key_prefix" env:"FLEET_ETCD_KEY_PREFIX"`
|
||||||
EtcdServers string `yaml:"etcd_servers" env:"FLEET_ETCD_SERVERS"`
|
EtcdRequestTimeout float64 `yaml:"etcd_request_timeout" env:"FLEET_ETCD_REQUEST_TIMEOUT"`
|
||||||
Metadata string `yaml:"metadata" env:"FLEET_METADATA"`
|
EtcdServers string `yaml:"etcd_servers" env:"FLEET_ETCD_SERVERS"`
|
||||||
PublicIP string `yaml:"public_ip" env:"FLEET_PUBLIC_IP"`
|
Metadata string `yaml:"metadata" env:"FLEET_METADATA"`
|
||||||
Verbosity string `yaml:"verbosity" env:"FLEET_VERBOSITY"`
|
PublicIP string `yaml:"public_ip" env:"FLEET_PUBLIC_IP"`
|
||||||
|
Verbosity int `yaml:"verbosity" env:"FLEET_VERBOSITY"`
|
||||||
}
|
}
|
||||||
|
@@ -16,35 +16,17 @@
|
|||||||
|
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Unit struct {
|
type Unit struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Mask bool `yaml:"mask"`
|
||||||
|
Enable bool `yaml:"enable"`
|
||||||
|
Runtime bool `yaml:"runtime"`
|
||||||
|
Content string `yaml:"content"`
|
||||||
|
Command string `yaml:"command" valid:"start,stop,restart,reload,try-restart,reload-or-restart,reload-or-try-restart"`
|
||||||
|
DropIns []UnitDropIn `yaml:"drop_ins"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnitDropIn struct {
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Mask bool `yaml:"mask"`
|
|
||||||
Enable bool `yaml:"enable"`
|
|
||||||
Runtime bool `yaml:"runtime"`
|
|
||||||
Content string `yaml:"content"`
|
Content string `yaml:"content"`
|
||||||
Command string `yaml:"command" valid:"start,stop,restart,reload,try-restart,reload-or-restart,reload-or-try-restart"`
|
|
||||||
|
|
||||||
// For drop-in units, a cloudinit.conf is generated.
|
|
||||||
// This is currently unbound in YAML (and hence unsettable in cloud-config files)
|
|
||||||
// until the correct behaviour for multiple drop-in units is determined.
|
|
||||||
DropIn bool `yaml:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *Unit) Type() string {
|
|
||||||
ext := filepath.Ext(u.Name)
|
|
||||||
return strings.TrimLeft(ext, ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *Unit) Group() string {
|
|
||||||
switch u.Type() {
|
|
||||||
case "network", "netdev", "link":
|
|
||||||
return "network"
|
|
||||||
default:
|
|
||||||
return "system"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -17,7 +17,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
RebootStrategy string `yaml:"reboot_strategy" env:"REBOOT_STRATEGY" valid:"best-effort,etcd-lock,reboot,false"`
|
RebootStrategy string `yaml:"reboot_strategy" env:"REBOOT_STRATEGY" valid:"best-effort,etcd-lock,reboot,off,false"`
|
||||||
Group string `yaml:"group" env:"GROUP"`
|
Group string `yaml:"group" env:"GROUP"`
|
||||||
Server string `yaml:"server" env:"SERVER"`
|
Server string `yaml:"server" env:"SERVER"`
|
||||||
}
|
}
|
||||||
|
@@ -125,7 +125,7 @@ func toNode(v interface{}, c context, n *node) {
|
|||||||
n.children = append(n.children, cn)
|
n.children = append(n.children, cn)
|
||||||
c.Increment()
|
c.Increment()
|
||||||
}
|
}
|
||||||
case reflect.String, reflect.Int, reflect.Bool:
|
case reflect.String, reflect.Int, reflect.Bool, reflect.Float64:
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("toNode(): unhandled kind %s", vv.Kind()))
|
panic(fmt.Sprintf("toNode(): unhandled kind %s", vv.Kind()))
|
||||||
}
|
}
|
||||||
|
@@ -61,7 +61,7 @@ func checkNodeStructure(n, g node, r *Report) {
|
|||||||
toNode(reflect.New(c).Elem().Interface(), context{}, &cg)
|
toNode(reflect.New(c).Elem().Interface(), context{}, &cg)
|
||||||
checkNodeStructure(cn, cg, r)
|
checkNodeStructure(cn, cg, r)
|
||||||
}
|
}
|
||||||
case reflect.String, reflect.Int, reflect.Bool:
|
case reflect.String, reflect.Int, reflect.Float64, reflect.Bool:
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("checkNodeStructure(): unhandled kind %s", g.Kind()))
|
panic(fmt.Sprintf("checkNodeStructure(): unhandled kind %s", g.Kind()))
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@ func checkValidity(cfg node, report *Report) {
|
|||||||
|
|
||||||
func checkNodeValidity(n, g node, r *Report) {
|
func checkNodeValidity(n, g node, r *Report) {
|
||||||
if err := config.AssertValid(n.Value, g.field.Tag.Get("valid")); err != nil {
|
if err := config.AssertValid(n.Value, g.field.Tag.Get("valid")); err != nil {
|
||||||
r.Warning(n.line, fmt.Sprintf("invalid value %v", n.Value))
|
r.Error(n.line, fmt.Sprintf("invalid value %v", n.Value))
|
||||||
}
|
}
|
||||||
switch g.Kind() {
|
switch g.Kind() {
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
@@ -92,7 +92,7 @@ func checkNodeValidity(n, g node, r *Report) {
|
|||||||
toNode(reflect.New(c).Elem().Interface(), context{}, &cg)
|
toNode(reflect.New(c).Elem().Interface(), context{}, &cg)
|
||||||
checkNodeValidity(cn, cg, r)
|
checkNodeValidity(cn, cg, r)
|
||||||
}
|
}
|
||||||
case reflect.String, reflect.Int, reflect.Bool:
|
case reflect.String, reflect.Int, reflect.Float64, reflect.Bool:
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("checkNodeValidity(): unhandled kind %s", g.Kind()))
|
panic(fmt.Sprintf("checkNodeValidity(): unhandled kind %s", g.Kind()))
|
||||||
}
|
}
|
||||||
@@ -104,10 +104,10 @@ func checkNodeValidity(n, g node, r *Report) {
|
|||||||
func isCompatible(n, g reflect.Kind) bool {
|
func isCompatible(n, g reflect.Kind) bool {
|
||||||
switch g {
|
switch g {
|
||||||
case reflect.String:
|
case reflect.String:
|
||||||
return n == reflect.String || n == reflect.Int || n == reflect.Bool
|
return n == reflect.String || n == reflect.Int || n == reflect.Float64 || n == reflect.Bool
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
return n == reflect.Struct || n == reflect.Map
|
return n == reflect.Struct || n == reflect.Map
|
||||||
case reflect.Bool, reflect.Slice:
|
case reflect.Bool, reflect.Slice, reflect.Int, reflect.Float64:
|
||||||
return n == g
|
return n == g
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("isCompatible(): unhandled kind %s", g))
|
panic(fmt.Sprintf("isCompatible(): unhandled kind %s", g))
|
||||||
|
@@ -218,7 +218,7 @@ func TestCheckValidity(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
config: "coreos:\n units:\n - command: lol",
|
config: "coreos:\n units:\n - command: lol",
|
||||||
entries: []Entry{{entryWarning, "invalid value lol", 3}},
|
entries: []Entry{{entryError, "invalid value lol", 3}},
|
||||||
},
|
},
|
||||||
|
|
||||||
// struct
|
// struct
|
||||||
@@ -227,7 +227,7 @@ func TestCheckValidity(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
config: "coreos:\n update:\n reboot_strategy: always",
|
config: "coreos:\n update:\n reboot_strategy: always",
|
||||||
entries: []Entry{{entryWarning, "invalid value always", 3}},
|
entries: []Entry{{entryError, "invalid value always", 3}},
|
||||||
},
|
},
|
||||||
|
|
||||||
// unknown
|
// unknown
|
||||||
|
@@ -40,7 +40,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
version = "0.11.2"
|
version = "1.0.0"
|
||||||
datasourceInterval = 100 * time.Millisecond
|
datasourceInterval = 100 * time.Millisecond
|
||||||
datasourceMaxInterval = 30 * time.Second
|
datasourceMaxInterval = 30 * time.Second
|
||||||
datasourceTimeout = 5 * time.Minute
|
datasourceTimeout = 5 * time.Minute
|
||||||
|
@@ -195,66 +195,82 @@ func Apply(cfg config.CloudConfig, env *Environment) error {
|
|||||||
// commands against units. It returns any error encountered.
|
// commands against units. It returns any error encountered.
|
||||||
func processUnits(units []system.Unit, root string, um system.UnitManager) error {
|
func processUnits(units []system.Unit, root string, um system.UnitManager) error {
|
||||||
type action struct {
|
type action struct {
|
||||||
unit string
|
unit system.Unit
|
||||||
command string
|
command string
|
||||||
}
|
}
|
||||||
actions := make([]action, 0, len(units))
|
actions := make([]action, 0, len(units))
|
||||||
reload := false
|
reload := false
|
||||||
for _, unit := range units {
|
for _, unit := range units {
|
||||||
dst := unit.Destination(root)
|
if unit.Name == "" {
|
||||||
|
log.Printf("Skipping unit without name")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if unit.Content != "" {
|
if unit.Content != "" {
|
||||||
log.Printf("Writing unit %s to filesystem at path %s", unit.Name, dst)
|
log.Printf("Writing unit %q to filesystem", unit.Name)
|
||||||
if err := um.PlaceUnit(&unit, dst); err != nil {
|
if err := um.PlaceUnit(unit); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Printf("Placed unit %s at %s", unit.Name, dst)
|
log.Printf("Wrote unit %q", unit.Name)
|
||||||
reload = true
|
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 {
|
if unit.Mask {
|
||||||
log.Printf("Masking unit file %s", unit.Name)
|
log.Printf("Masking unit file %q", unit.Name)
|
||||||
if err := um.MaskUnit(&unit); err != nil {
|
if err := um.MaskUnit(unit); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else if unit.Runtime {
|
} else if unit.Runtime {
|
||||||
log.Printf("Ensuring runtime unit file %s is unmasked", unit.Name)
|
log.Printf("Ensuring runtime unit file %q is unmasked", unit.Name)
|
||||||
if err := um.UnmaskUnit(&unit); err != nil {
|
if err := um.UnmaskUnit(unit); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if unit.Enable {
|
if unit.Enable {
|
||||||
if unit.Group() != "network" {
|
if unit.Group() != "network" {
|
||||||
log.Printf("Enabling unit file %s", unit.Name)
|
log.Printf("Enabling unit file %q", unit.Name)
|
||||||
if err := um.EnableUnitFile(unit.Name, unit.Runtime); err != nil {
|
if err := um.EnableUnitFile(unit); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Printf("Enabled unit %s", unit.Name)
|
log.Printf("Enabled unit %q", unit.Name)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("Skipping enable for network-like unit %s", unit.Name)
|
log.Printf("Skipping enable for network-like unit %q", unit.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if unit.Group() == "network" {
|
if unit.Group() == "network" {
|
||||||
actions = append(actions, action{"systemd-networkd.service", "restart"})
|
networkd := system.Unit{Unit: config.Unit{Name: "systemd-networkd.service"}}
|
||||||
|
actions = append(actions, action{networkd, "restart"})
|
||||||
} else if unit.Command != "" {
|
} else if unit.Command != "" {
|
||||||
actions = append(actions, action{unit.Name, unit.Command})
|
actions = append(actions, action{unit, unit.Command})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if reload {
|
if reload {
|
||||||
if err := um.DaemonReload(); err != nil {
|
if err := um.DaemonReload(); err != nil {
|
||||||
return errors.New(fmt.Sprintf("failed systemd daemon-reload: %v", err))
|
return errors.New(fmt.Sprintf("failed systemd daemon-reload: %s", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, action := range actions {
|
for _, action := range actions {
|
||||||
log.Printf("Calling unit command '%s %s'", action.command, action.unit)
|
log.Printf("Calling unit command %q on %q'", action.command, action.unit.Name)
|
||||||
res, err := um.RunUnitCommand(action.command, action.unit)
|
res, err := um.RunUnitCommand(action.unit, action.command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Printf("Result of '%s %s': %s", action.command, action.unit, res)
|
log.Printf("Result of %q on %q': %s", action.command, action.unit.Name, res)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
package initialize
|
package initialize
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/config"
|
"github.com/coreos/coreos-cloudinit/config"
|
||||||
@@ -32,99 +33,171 @@ type TestUnitManager struct {
|
|||||||
reload bool
|
reload bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tum *TestUnitManager) PlaceUnit(unit *system.Unit, dst string) error {
|
func (tum *TestUnitManager) PlaceUnit(u system.Unit) error {
|
||||||
tum.placed = append(tum.placed, unit.Name)
|
tum.placed = append(tum.placed, u.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (tum *TestUnitManager) EnableUnitFile(unit string, runtime bool) error {
|
func (tum *TestUnitManager) PlaceUnitDropIn(u system.Unit, d config.UnitDropIn) error {
|
||||||
tum.enabled = append(tum.enabled, unit)
|
tum.placed = append(tum.placed, u.Name+".d/"+d.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (tum *TestUnitManager) RunUnitCommand(command, unit string) (string, error) {
|
func (tum *TestUnitManager) EnableUnitFile(u system.Unit) error {
|
||||||
|
tum.enabled = append(tum.enabled, u.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (tum *TestUnitManager) RunUnitCommand(u system.Unit, c string) (string, error) {
|
||||||
tum.commands = make(map[string]string)
|
tum.commands = make(map[string]string)
|
||||||
tum.commands[unit] = command
|
tum.commands[u.Name] = c
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
func (tum *TestUnitManager) DaemonReload() error {
|
func (tum *TestUnitManager) DaemonReload() error {
|
||||||
tum.reload = true
|
tum.reload = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (tum *TestUnitManager) MaskUnit(unit *system.Unit) error {
|
func (tum *TestUnitManager) MaskUnit(u system.Unit) error {
|
||||||
tum.masked = append(tum.masked, unit.Name)
|
tum.masked = append(tum.masked, u.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (tum *TestUnitManager) UnmaskUnit(unit *system.Unit) error {
|
func (tum *TestUnitManager) UnmaskUnit(u system.Unit) error {
|
||||||
tum.unmasked = append(tum.unmasked, unit.Name)
|
tum.unmasked = append(tum.unmasked, u.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessUnits(t *testing.T) {
|
func TestProcessUnits(t *testing.T) {
|
||||||
tum := &TestUnitManager{}
|
tests := []struct {
|
||||||
units := []system.Unit{
|
units []system.Unit
|
||||||
system.Unit{Unit: config.Unit{
|
|
||||||
Name: "foo",
|
result TestUnitManager
|
||||||
Mask: true,
|
}{
|
||||||
}},
|
{
|
||||||
}
|
units: []system.Unit{
|
||||||
if err := processUnits(units, "", tum); err != nil {
|
system.Unit{Unit: config.Unit{
|
||||||
t.Fatalf("unexpected error calling processUnits: %v", err)
|
Name: "foo",
|
||||||
}
|
Mask: true,
|
||||||
if len(tum.masked) != 1 || tum.masked[0] != "foo" {
|
}},
|
||||||
t.Errorf("expected foo to be masked, but found %v", tum.masked)
|
},
|
||||||
|
result: TestUnitManager{
|
||||||
|
masked: []string{"foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
units: []system.Unit{
|
||||||
|
system.Unit{Unit: config.Unit{
|
||||||
|
Name: "bar.network",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
result: TestUnitManager{
|
||||||
|
commands: map[string]string{
|
||||||
|
"systemd-networkd.service": "restart",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
units: []system.Unit{
|
||||||
|
system.Unit{Unit: config.Unit{
|
||||||
|
Name: "baz.service",
|
||||||
|
Content: "[Service]\nExecStart=/bin/true",
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
result: TestUnitManager{
|
||||||
|
placed: []string{"baz.service"},
|
||||||
|
reload: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
units: []system.Unit{
|
||||||
|
system.Unit{Unit: config.Unit{
|
||||||
|
Name: "locksmithd.service",
|
||||||
|
Runtime: true,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
result: TestUnitManager{
|
||||||
|
unmasked: []string{"locksmithd.service"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
units: []system.Unit{
|
||||||
|
system.Unit{Unit: config.Unit{
|
||||||
|
Name: "woof",
|
||||||
|
Enable: true,
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
result: TestUnitManager{
|
||||||
|
enabled: []string{"woof"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
units: []system.Unit{
|
||||||
|
system.Unit{Unit: config.Unit{
|
||||||
|
Name: "hi.service",
|
||||||
|
Runtime: true,
|
||||||
|
Content: "[Service]\nExecStart=/bin/echo hi",
|
||||||
|
DropIns: []config.UnitDropIn{
|
||||||
|
{
|
||||||
|
Name: "lo.conf",
|
||||||
|
Content: "[Service]\nExecStart=/bin/echo lo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "bye.conf",
|
||||||
|
Content: "[Service]\nExecStart=/bin/echo bye",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
result: TestUnitManager{
|
||||||
|
placed: []string{"hi.service", "hi.service.d/lo.conf", "hi.service.d/bye.conf"},
|
||||||
|
unmasked: []string{"hi.service"},
|
||||||
|
reload: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
units: []system.Unit{
|
||||||
|
system.Unit{Unit: config.Unit{
|
||||||
|
DropIns: []config.UnitDropIn{
|
||||||
|
{
|
||||||
|
Name: "lo.conf",
|
||||||
|
Content: "[Service]\nExecStart=/bin/echo lo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
result: TestUnitManager{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
units: []system.Unit{
|
||||||
|
system.Unit{Unit: config.Unit{
|
||||||
|
Name: "hi.service",
|
||||||
|
DropIns: []config.UnitDropIn{
|
||||||
|
{
|
||||||
|
Content: "[Service]\nExecStart=/bin/echo lo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
result: TestUnitManager{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
units: []system.Unit{
|
||||||
|
system.Unit{Unit: config.Unit{
|
||||||
|
Name: "hi.service",
|
||||||
|
DropIns: []config.UnitDropIn{
|
||||||
|
{
|
||||||
|
Name: "lo.conf",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
result: TestUnitManager{},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
tum = &TestUnitManager{}
|
for _, tt := range tests {
|
||||||
units = []system.Unit{
|
tum := &TestUnitManager{}
|
||||||
system.Unit{Unit: config.Unit{
|
if err := processUnits(tt.units, "", tum); err != nil {
|
||||||
Name: "bar.network",
|
t.Errorf("bad error (%+v): want nil, got %s", tt.units, err)
|
||||||
}},
|
}
|
||||||
}
|
if !reflect.DeepEqual(tt.result, *tum) {
|
||||||
if err := processUnits(units, "", tum); err != nil {
|
t.Errorf("bad result (%+v): want %+v, got %+v", tt.units, tt.result, tum)
|
||||||
t.Fatalf("unexpected error calling processUnits: %v", err)
|
}
|
||||||
}
|
|
||||||
if _, ok := tum.commands["systemd-networkd.service"]; !ok {
|
|
||||||
t.Errorf("expected systemd-networkd.service to be reloaded!")
|
|
||||||
}
|
|
||||||
|
|
||||||
tum = &TestUnitManager{}
|
|
||||||
units = []system.Unit{
|
|
||||||
system.Unit{Unit: config.Unit{
|
|
||||||
Name: "baz.service",
|
|
||||||
Content: "[Service]\nExecStart=/bin/true",
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
if err := processUnits(units, "", tum); err != nil {
|
|
||||||
t.Fatalf("unexpected error calling processUnits: %v", err)
|
|
||||||
}
|
|
||||||
if len(tum.placed) != 1 || tum.placed[0] != "baz.service" {
|
|
||||||
t.Fatalf("expected baz.service to be written, but got %v", tum.placed)
|
|
||||||
}
|
|
||||||
|
|
||||||
tum = &TestUnitManager{}
|
|
||||||
units = []system.Unit{
|
|
||||||
system.Unit{Unit: config.Unit{
|
|
||||||
Name: "locksmithd.service",
|
|
||||||
Runtime: true,
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
if err := processUnits(units, "", tum); err != nil {
|
|
||||||
t.Fatalf("unexpected error calling processUnits: %v", err)
|
|
||||||
}
|
|
||||||
if len(tum.unmasked) != 1 || tum.unmasked[0] != "locksmithd.service" {
|
|
||||||
t.Fatalf("expected locksmithd.service to be unmasked, but got %v", tum.unmasked)
|
|
||||||
}
|
|
||||||
|
|
||||||
tum = &TestUnitManager{}
|
|
||||||
units = []system.Unit{
|
|
||||||
system.Unit{Unit: config.Unit{
|
|
||||||
Name: "woof",
|
|
||||||
Enable: true,
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
if err := processUnits(units, "", tum); err != nil {
|
|
||||||
t.Fatalf("unexpected error calling processUnits: %v", err)
|
|
||||||
}
|
|
||||||
if len(tum.enabled) != 1 || tum.enabled[0] != "woof" {
|
|
||||||
t.Fatalf("expected woof to be enabled, but got %v", tum.enabled)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -25,15 +25,15 @@ import (
|
|||||||
|
|
||||||
// dropinContents generates the contents for a drop-in unit given the config.
|
// dropinContents generates the contents for a drop-in unit given the config.
|
||||||
// The argument must be a struct from the 'config' package.
|
// The argument must be a struct from the 'config' package.
|
||||||
func dropinContents(e interface{}) string {
|
func serviceContents(e interface{}) string {
|
||||||
et := reflect.TypeOf(e)
|
et := reflect.TypeOf(e)
|
||||||
ev := reflect.ValueOf(e)
|
ev := reflect.ValueOf(e)
|
||||||
|
|
||||||
var out string
|
var out string
|
||||||
for i := 0; i < et.NumField(); i++ {
|
for i := 0; i < et.NumField(); i++ {
|
||||||
if val := ev.Field(i).String(); val != "" {
|
if val := ev.Field(i).Interface(); !config.IsZero(val) {
|
||||||
key := et.Field(i).Tag.Get("env")
|
key := et.Field(i).Tag.Get("env")
|
||||||
out += fmt.Sprintf("Environment=\"%s=%s\"\n", key, val)
|
out += fmt.Sprintf("Environment=\"%s=%v\"\n", key, val)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,16 +42,3 @@ func dropinContents(e interface{}) string {
|
|||||||
}
|
}
|
||||||
return "[Service]\n" + out
|
return "[Service]\n" + out
|
||||||
}
|
}
|
||||||
|
|
||||||
func dropinFromConfig(cfg interface{}, name string) []Unit {
|
|
||||||
content := dropinContents(cfg)
|
|
||||||
if content == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return []Unit{{config.Unit{
|
|
||||||
Name: name,
|
|
||||||
Runtime: true,
|
|
||||||
DropIn: true,
|
|
||||||
Content: content,
|
|
||||||
}}}
|
|
||||||
}
|
|
||||||
|
55
system/env_test.go
Normal file
55
system/env_test.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package system
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestServiceContents(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Config interface{}
|
||||||
|
Contents string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
struct{}{},
|
||||||
|
"",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
struct {
|
||||||
|
A string `env:"A"`
|
||||||
|
B int `env:"B"`
|
||||||
|
C bool `env:"C"`
|
||||||
|
D float64 `env:"D"`
|
||||||
|
}{
|
||||||
|
"hi", 1, true, 0.12345,
|
||||||
|
},
|
||||||
|
`[Service]
|
||||||
|
Environment="A=hi"
|
||||||
|
Environment="B=1"
|
||||||
|
Environment="C=true"
|
||||||
|
Environment="D=0.12345"
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
struct {
|
||||||
|
A float64 `env:"A"`
|
||||||
|
B float64 `env:"B"`
|
||||||
|
C float64 `env:"C"`
|
||||||
|
D float64 `env:"D"`
|
||||||
|
}{
|
||||||
|
0.000001, 1, 0.9999999, 0.1,
|
||||||
|
},
|
||||||
|
`[Service]
|
||||||
|
Environment="A=1e-06"
|
||||||
|
Environment="B=1"
|
||||||
|
Environment="C=0.9999999"
|
||||||
|
Environment="D=0.1"
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
if c := serviceContents(tt.Config); c != tt.Contents {
|
||||||
|
t.Errorf("bad contents (%+v): want %q, got %q", tt, tt.Contents, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -28,5 +28,12 @@ type Etcd struct {
|
|||||||
|
|
||||||
// Units creates a Unit file drop-in for etcd, using any configured options.
|
// Units creates a Unit file drop-in for etcd, using any configured options.
|
||||||
func (ee Etcd) Units() []Unit {
|
func (ee Etcd) Units() []Unit {
|
||||||
return dropinFromConfig(ee.Etcd, "etcd.service")
|
return []Unit{{config.Unit{
|
||||||
|
Name: "etcd.service",
|
||||||
|
Runtime: true,
|
||||||
|
DropIns: []config.UnitDropIn{{
|
||||||
|
Name: "20-cloudinit.conf",
|
||||||
|
Content: serviceContents(ee.Etcd),
|
||||||
|
}},
|
||||||
|
}}}
|
||||||
}
|
}
|
||||||
|
@@ -30,7 +30,11 @@ func TestEtcdUnits(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
config.Etcd{},
|
config.Etcd{},
|
||||||
nil,
|
[]Unit{{config.Unit{
|
||||||
|
Name: "etcd.service",
|
||||||
|
Runtime: true,
|
||||||
|
DropIns: []config.UnitDropIn{{Name: "20-cloudinit.conf"}},
|
||||||
|
}}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
config.Etcd{
|
config.Etcd{
|
||||||
@@ -40,11 +44,13 @@ func TestEtcdUnits(t *testing.T) {
|
|||||||
[]Unit{{config.Unit{
|
[]Unit{{config.Unit{
|
||||||
Name: "etcd.service",
|
Name: "etcd.service",
|
||||||
Runtime: true,
|
Runtime: true,
|
||||||
DropIn: true,
|
DropIns: []config.UnitDropIn{{
|
||||||
Content: `[Service]
|
Name: "20-cloudinit.conf",
|
||||||
|
Content: `[Service]
|
||||||
Environment="ETCD_DISCOVERY=http://disco.example.com/foobar"
|
Environment="ETCD_DISCOVERY=http://disco.example.com/foobar"
|
||||||
Environment="ETCD_PEER_BIND_ADDR=127.0.0.1:7002"
|
Environment="ETCD_PEER_BIND_ADDR=127.0.0.1:7002"
|
||||||
`,
|
`,
|
||||||
|
}},
|
||||||
}}},
|
}}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -56,18 +62,20 @@ Environment="ETCD_PEER_BIND_ADDR=127.0.0.1:7002"
|
|||||||
[]Unit{{config.Unit{
|
[]Unit{{config.Unit{
|
||||||
Name: "etcd.service",
|
Name: "etcd.service",
|
||||||
Runtime: true,
|
Runtime: true,
|
||||||
DropIn: true,
|
DropIns: []config.UnitDropIn{{
|
||||||
Content: `[Service]
|
Name: "20-cloudinit.conf",
|
||||||
|
Content: `[Service]
|
||||||
Environment="ETCD_DISCOVERY=http://disco.example.com/foobar"
|
Environment="ETCD_DISCOVERY=http://disco.example.com/foobar"
|
||||||
Environment="ETCD_NAME=node001"
|
Environment="ETCD_NAME=node001"
|
||||||
Environment="ETCD_PEER_BIND_ADDR=127.0.0.1:7002"
|
Environment="ETCD_PEER_BIND_ADDR=127.0.0.1:7002"
|
||||||
`,
|
`,
|
||||||
|
}},
|
||||||
}}},
|
}}},
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
units := Etcd{tt.config}.Units()
|
units := Etcd{tt.config}.Units()
|
||||||
if !reflect.DeepEqual(tt.units, units) {
|
if !reflect.DeepEqual(tt.units, units) {
|
||||||
t.Errorf("bad units (%q): want %#v, got %#v", tt.config, tt.units, units)
|
t.Errorf("bad units (%+v): want %#v, got %#v", tt.config, tt.units, units)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,6 +19,7 @@ package system
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
@@ -47,13 +48,14 @@ func (f *File) Permissions() (os.FileMode, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func WriteFile(f *File, root string) (string, error) {
|
func WriteFile(f *File, root string) (string, error) {
|
||||||
|
fullpath := path.Join(root, f.Path)
|
||||||
|
dir := path.Dir(fullpath)
|
||||||
|
log.Printf("Writing file to %q", fullpath)
|
||||||
|
|
||||||
if f.Encoding != "" {
|
if f.Encoding != "" {
|
||||||
return "", fmt.Errorf("Unable to write file with encoding %s", f.Encoding)
|
return "", fmt.Errorf("Unable to write file with encoding %s", f.Encoding)
|
||||||
}
|
}
|
||||||
|
|
||||||
fullpath := path.Join(root, f.Path)
|
|
||||||
dir := path.Dir(fullpath)
|
|
||||||
|
|
||||||
if err := EnsureDirectoryExists(dir); err != nil {
|
if err := EnsureDirectoryExists(dir); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -94,6 +96,7 @@ func WriteFile(f *File, root string) (string, error) {
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("Wrote file to %q", fullpath)
|
||||||
return fullpath, nil
|
return fullpath, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -13,5 +13,12 @@ type Flannel struct {
|
|||||||
// Units generates a Unit file drop-in for flannel, if any flannel options were
|
// Units generates a Unit file drop-in for flannel, if any flannel options were
|
||||||
// configured in cloud-config
|
// configured in cloud-config
|
||||||
func (fl Flannel) Units() []Unit {
|
func (fl Flannel) Units() []Unit {
|
||||||
return dropinFromConfig(fl.Flannel, "flannel.service")
|
return []Unit{{config.Unit{
|
||||||
|
Name: "flanneld.service",
|
||||||
|
Runtime: true,
|
||||||
|
DropIns: []config.UnitDropIn{{
|
||||||
|
Name: "20-cloudinit.conf",
|
||||||
|
Content: serviceContents(fl.Flannel),
|
||||||
|
}},
|
||||||
|
}}}
|
||||||
}
|
}
|
||||||
|
@@ -14,7 +14,11 @@ func TestFlannelUnits(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
config.Flannel{},
|
config.Flannel{},
|
||||||
nil,
|
[]Unit{{config.Unit{
|
||||||
|
Name: "flanneld.service",
|
||||||
|
Runtime: true,
|
||||||
|
DropIns: []config.UnitDropIn{{Name: "20-cloudinit.conf"}},
|
||||||
|
}}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
config.Flannel{
|
config.Flannel{
|
||||||
@@ -22,13 +26,15 @@ func TestFlannelUnits(t *testing.T) {
|
|||||||
EtcdPrefix: "/coreos.com/network/tenant1",
|
EtcdPrefix: "/coreos.com/network/tenant1",
|
||||||
},
|
},
|
||||||
[]Unit{{config.Unit{
|
[]Unit{{config.Unit{
|
||||||
Name: "flannel.service",
|
Name: "flanneld.service",
|
||||||
Content: `[Service]
|
Runtime: true,
|
||||||
|
DropIns: []config.UnitDropIn{{
|
||||||
|
Name: "20-cloudinit.conf",
|
||||||
|
Content: `[Service]
|
||||||
Environment="FLANNELD_ETCD_ENDPOINT=http://12.34.56.78:4001"
|
Environment="FLANNELD_ETCD_ENDPOINT=http://12.34.56.78:4001"
|
||||||
Environment="FLANNELD_ETCD_PREFIX=/coreos.com/network/tenant1"
|
Environment="FLANNELD_ETCD_PREFIX=/coreos.com/network/tenant1"
|
||||||
`,
|
`,
|
||||||
Runtime: true,
|
}},
|
||||||
DropIn: true,
|
|
||||||
}}},
|
}}},
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
|
@@ -29,5 +29,12 @@ type Fleet struct {
|
|||||||
// Units generates a Unit file drop-in for fleet, if any fleet options were
|
// Units generates a Unit file drop-in for fleet, if any fleet options were
|
||||||
// configured in cloud-config
|
// configured in cloud-config
|
||||||
func (fe Fleet) Units() []Unit {
|
func (fe Fleet) Units() []Unit {
|
||||||
return dropinFromConfig(fe.Fleet, "fleet.service")
|
return []Unit{{config.Unit{
|
||||||
|
Name: "fleet.service",
|
||||||
|
Runtime: true,
|
||||||
|
DropIns: []config.UnitDropIn{{
|
||||||
|
Name: "20-cloudinit.conf",
|
||||||
|
Content: serviceContents(fe.Fleet),
|
||||||
|
}},
|
||||||
|
}}}
|
||||||
}
|
}
|
||||||
|
@@ -30,25 +30,31 @@ func TestFleetUnits(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
config.Fleet{},
|
config.Fleet{},
|
||||||
nil,
|
[]Unit{{config.Unit{
|
||||||
|
Name: "fleet.service",
|
||||||
|
Runtime: true,
|
||||||
|
DropIns: []config.UnitDropIn{{Name: "20-cloudinit.conf"}},
|
||||||
|
}}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
config.Fleet{
|
config.Fleet{
|
||||||
PublicIP: "12.34.56.78",
|
PublicIP: "12.34.56.78",
|
||||||
},
|
},
|
||||||
[]Unit{{config.Unit{
|
[]Unit{{config.Unit{
|
||||||
Name: "fleet.service",
|
Name: "fleet.service",
|
||||||
Content: `[Service]
|
Runtime: true,
|
||||||
|
DropIns: []config.UnitDropIn{{
|
||||||
|
Name: "20-cloudinit.conf",
|
||||||
|
Content: `[Service]
|
||||||
Environment="FLEET_PUBLIC_IP=12.34.56.78"
|
Environment="FLEET_PUBLIC_IP=12.34.56.78"
|
||||||
`,
|
`,
|
||||||
Runtime: true,
|
}},
|
||||||
DropIn: true,
|
|
||||||
}}},
|
}}},
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
units := Fleet{tt.config}.Units()
|
units := Fleet{tt.config}.Units()
|
||||||
if !reflect.DeepEqual(units, tt.units) {
|
if !reflect.DeepEqual(units, tt.units) {
|
||||||
t.Errorf("bad units (%q): want %#v, got %#v", tt.config, tt.units, units)
|
t.Errorf("bad units (%+v): want %#v, got %#v", tt.config, tt.units, units)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -96,7 +96,8 @@ func maybeProbeBonding(interfaces []network.InterfaceGenerator) error {
|
|||||||
|
|
||||||
func restartNetworkd() error {
|
func restartNetworkd() error {
|
||||||
log.Printf("Restarting networkd.service\n")
|
log.Printf("Restarting networkd.service\n")
|
||||||
_, err := NewUnitManager("").RunUnitCommand("restart", "systemd-networkd.service")
|
networkd := Unit{config.Unit{Name: "systemd-networkd.service"}}
|
||||||
|
_, err := NewUnitManager("").RunUnitCommand(networkd, "restart")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -23,7 +23,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus"
|
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/coreos/go-systemd/dbus"
|
||||||
@@ -42,49 +41,51 @@ type systemd struct {
|
|||||||
// never be used as a true MachineID
|
// never be used as a true MachineID
|
||||||
const fakeMachineID = "42000000000000000000000000000042"
|
const fakeMachineID = "42000000000000000000000000000042"
|
||||||
|
|
||||||
// PlaceUnit writes a unit file at the provided destination, creating
|
// PlaceUnit writes a unit file at its desired destination, creating parent
|
||||||
// parent directories as necessary.
|
// directories as necessary.
|
||||||
func (s *systemd) PlaceUnit(u *Unit, dst string) error {
|
func (s *systemd) PlaceUnit(u Unit) error {
|
||||||
dir := filepath.Dir(dst)
|
|
||||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
|
||||||
if err := os.MkdirAll(dir, os.FileMode(0755)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
file := File{config.File{
|
file := File{config.File{
|
||||||
Path: filepath.Base(dst),
|
Path: u.Destination(s.root),
|
||||||
Content: u.Content,
|
Content: u.Content,
|
||||||
RawFilePermissions: "0644",
|
RawFilePermissions: "0644",
|
||||||
}}
|
}}
|
||||||
|
|
||||||
_, err := WriteFile(&file, dir)
|
_, err := WriteFile(&file, "/")
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *systemd) EnableUnitFile(unit string, runtime bool) error {
|
// 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()
|
conn, err := dbus.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
units := []string{unit}
|
units := []string{u.Name}
|
||||||
_, _, err = conn.EnableUnitFiles(units, runtime, true)
|
_, _, err = conn.EnableUnitFiles(units, u.Runtime, true)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *systemd) RunUnitCommand(command, unit string) (string, error) {
|
func (s *systemd) RunUnitCommand(u Unit, c string) (string, error) {
|
||||||
conn, err := dbus.New()
|
conn, err := dbus.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
var fn func(string, string) (string, error)
|
var fn func(string, string) (string, error)
|
||||||
switch command {
|
switch c {
|
||||||
case "start":
|
case "start":
|
||||||
fn = conn.StartUnit
|
fn = conn.StartUnit
|
||||||
case "stop":
|
case "stop":
|
||||||
@@ -100,10 +101,10 @@ func (s *systemd) RunUnitCommand(command, unit string) (string, error) {
|
|||||||
case "reload-or-try-restart":
|
case "reload-or-try-restart":
|
||||||
fn = conn.ReloadOrTryRestartUnit
|
fn = conn.ReloadOrTryRestartUnit
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("Unsupported systemd command %q", command)
|
return "", fmt.Errorf("Unsupported systemd command %q", c)
|
||||||
}
|
}
|
||||||
|
|
||||||
return fn(unit, "replace")
|
return fn(u.Name, "replace")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *systemd) DaemonReload() error {
|
func (s *systemd) DaemonReload() error {
|
||||||
@@ -119,8 +120,8 @@ func (s *systemd) DaemonReload() error {
|
|||||||
// /dev/null, analogous to `systemctl mask`.
|
// /dev/null, analogous to `systemctl mask`.
|
||||||
// N.B.: Unlike `systemctl mask`, this function will *remove any existing unit
|
// N.B.: Unlike `systemctl mask`, this function will *remove any existing unit
|
||||||
// file at the location*, to ensure that the mask will succeed.
|
// file at the location*, to ensure that the mask will succeed.
|
||||||
func (s *systemd) MaskUnit(unit *Unit) error {
|
func (s *systemd) MaskUnit(u Unit) error {
|
||||||
masked := unit.Destination(s.root)
|
masked := u.Destination(s.root)
|
||||||
if _, err := os.Stat(masked); os.IsNotExist(err) {
|
if _, err := os.Stat(masked); os.IsNotExist(err) {
|
||||||
if err := os.MkdirAll(path.Dir(masked), os.FileMode(0755)); err != nil {
|
if err := os.MkdirAll(path.Dir(masked), os.FileMode(0755)); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -134,8 +135,8 @@ func (s *systemd) MaskUnit(unit *Unit) error {
|
|||||||
// UnmaskUnit is analogous to systemd's unit_file_unmask. If the file
|
// 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
|
// associated with the given Unit is empty or appears to be a symlink to
|
||||||
// /dev/null, it is removed.
|
// /dev/null, it is removed.
|
||||||
func (s *systemd) UnmaskUnit(unit *Unit) error {
|
func (s *systemd) UnmaskUnit(u Unit) error {
|
||||||
masked := unit.Destination(s.root)
|
masked := u.Destination(s.root)
|
||||||
ne, err := nullOrEmpty(masked)
|
ne, err := nullOrEmpty(masked)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return nil
|
return nil
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
package system
|
package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@@ -25,133 +26,109 @@ import (
|
|||||||
"github.com/coreos/coreos-cloudinit/config"
|
"github.com/coreos/coreos-cloudinit/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPlaceNetworkUnit(t *testing.T) {
|
func TestPlaceUnit(t *testing.T) {
|
||||||
u := Unit{config.Unit{
|
tests := []config.Unit{
|
||||||
Name: "50-eth0.network",
|
{
|
||||||
Runtime: true,
|
Name: "50-eth0.network",
|
||||||
Content: `[Match]
|
Runtime: true,
|
||||||
Name=eth47
|
Content: "[Match]\nName=eth47\n\n[Network]\nAddress=10.209.171.177/19\n",
|
||||||
|
},
|
||||||
[Network]
|
{
|
||||||
Address=10.209.171.177/19
|
Name: "media-state.mount",
|
||||||
`,
|
Content: "[Mount]\nWhat=/dev/sdb1\nWhere=/media/state\n",
|
||||||
}}
|
},
|
||||||
|
|
||||||
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}
|
|
||||||
|
|
||||||
dst := u.Destination(dir)
|
|
||||||
expectDst := path.Join(dir, "run", "systemd", "network", "50-eth0.network")
|
|
||||||
if dst != expectDst {
|
|
||||||
t.Fatalf("unit.Destination returned %s, expected %s", dst, expectDst)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := sd.PlaceUnit(&u, dst); err != nil {
|
for _, tt := range tests {
|
||||||
t.Fatalf("PlaceUnit failed: %v", err)
|
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||||
}
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Unable to create tempdir: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
fi, err := os.Stat(dst)
|
u := Unit{tt}
|
||||||
if err != nil {
|
sd := &systemd{dir}
|
||||||
t.Fatalf("Unable to stat file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if fi.Mode() != os.FileMode(0644) {
|
if err := sd.PlaceUnit(u); err != nil {
|
||||||
t.Errorf("File has incorrect mode: %v", fi.Mode())
|
t.Fatalf("PlaceUnit(): bad error (%+v): want nil, got %s", tt, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
contents, err := ioutil.ReadFile(dst)
|
fi, err := os.Stat(u.Destination(dir))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to read expected file: %v", err)
|
t.Fatalf("Stat(): bad error (%+v): want nil, got %s", tt, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
expectContents := `[Match]
|
if mode := fi.Mode(); mode != os.FileMode(0644) {
|
||||||
Name=eth47
|
t.Errorf("bad filemode (%+v): want %v, got %v", tt, os.FileMode(0644), mode)
|
||||||
|
}
|
||||||
|
|
||||||
[Network]
|
c, err := ioutil.ReadFile(u.Destination(dir))
|
||||||
Address=10.209.171.177/19
|
if err != nil {
|
||||||
`
|
t.Fatalf("ReadFile(): bad error (%+v): want nil, got %s", tt, err)
|
||||||
if string(contents) != expectContents {
|
}
|
||||||
t.Fatalf("File has incorrect contents '%s'.\nExpected '%s'", string(contents), expectContents)
|
|
||||||
|
if string(c) != tt.Content {
|
||||||
|
t.Errorf("bad contents (%+v): want %q, got %q", tt, tt.Content, string(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
os.RemoveAll(dir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUnitDestination(t *testing.T) {
|
func TestPlaceUnitDropIn(t *testing.T) {
|
||||||
dir := "/some/dir"
|
tests := []config.Unit{
|
||||||
name := "foobar.service"
|
{
|
||||||
|
Name: "false.service",
|
||||||
u := Unit{config.Unit{
|
Runtime: true,
|
||||||
Name: name,
|
DropIns: []config.UnitDropIn{
|
||||||
DropIn: false,
|
{
|
||||||
}}
|
Name: "00-true.conf",
|
||||||
|
Content: "[Service]\nExecStart=\nExecStart=/usr/bin/true\n",
|
||||||
dst := u.Destination(dir)
|
},
|
||||||
expectDst := path.Join(dir, "etc", "systemd", "system", "foobar.service")
|
},
|
||||||
if dst != expectDst {
|
},
|
||||||
t.Errorf("unit.Destination returned %s, expected %s", dst, expectDst)
|
{
|
||||||
|
Name: "true.service",
|
||||||
|
DropIns: []config.UnitDropIn{
|
||||||
|
{
|
||||||
|
Name: "00-false.conf",
|
||||||
|
Content: "[Service]\nExecStart=\nExecStart=/usr/bin/false\n",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
u.DropIn = true
|
for _, tt := range tests {
|
||||||
|
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Unable to create tempdir: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
dst = u.Destination(dir)
|
u := Unit{tt}
|
||||||
expectDst = path.Join(dir, "etc", "systemd", "system", "foobar.service.d", cloudConfigDropIn)
|
sd := &systemd{dir}
|
||||||
if dst != expectDst {
|
|
||||||
t.Errorf("unit.Destination returned %s, expected %s", dst, expectDst)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPlaceMountUnit(t *testing.T) {
|
if err := sd.PlaceUnitDropIn(u, u.DropIns[0]); err != nil {
|
||||||
u := Unit{config.Unit{
|
t.Fatalf("PlaceUnit(): bad error (%+v): want nil, got %s", tt, err)
|
||||||
Name: "media-state.mount",
|
}
|
||||||
Runtime: false,
|
|
||||||
Content: `[Mount]
|
|
||||||
What=/dev/sdb1
|
|
||||||
Where=/media/state
|
|
||||||
`,
|
|
||||||
}}
|
|
||||||
|
|
||||||
dir, err := ioutil.TempDir(os.TempDir(), "coreos-cloudinit-")
|
fi, err := os.Stat(u.DropInDestination(dir, u.DropIns[0]))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to create tempdir: %v", err)
|
t.Fatalf("Stat(): bad error (%+v): want nil, got %s", tt, err)
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(dir)
|
|
||||||
|
|
||||||
sd := &systemd{dir}
|
if mode := fi.Mode(); mode != os.FileMode(0644) {
|
||||||
|
t.Errorf("bad filemode (%+v): want %v, got %v", tt, os.FileMode(0644), mode)
|
||||||
|
}
|
||||||
|
|
||||||
dst := u.Destination(dir)
|
c, err := ioutil.ReadFile(u.DropInDestination(dir, u.DropIns[0]))
|
||||||
expectDst := path.Join(dir, "etc", "systemd", "system", "media-state.mount")
|
if err != nil {
|
||||||
if dst != expectDst {
|
t.Fatalf("ReadFile(): bad error (%+v): want nil, got %s", tt, err)
|
||||||
t.Fatalf("unit.Destination returned %s, expected %s", dst, expectDst)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if err := sd.PlaceUnit(&u, dst); err != nil {
|
if string(c) != u.DropIns[0].Content {
|
||||||
t.Fatalf("PlaceUnit failed: %v", err)
|
t.Errorf("bad contents (%+v): want %q, got %q", tt, u.DropIns[0].Content, string(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
fi, err := os.Stat(dst)
|
os.RemoveAll(dir)
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to stat file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if fi.Mode() != os.FileMode(0644) {
|
|
||||||
t.Errorf("File has incorrect mode: %v", fi.Mode())
|
|
||||||
}
|
|
||||||
|
|
||||||
contents, err := ioutil.ReadFile(dst)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Unable to read expected file: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expectContents := `[Mount]
|
|
||||||
What=/dev/sdb1
|
|
||||||
Where=/media/state
|
|
||||||
`
|
|
||||||
if string(contents) != expectContents {
|
|
||||||
t.Fatalf("File has incorrect contents '%s'.\nExpected '%s'", string(contents), expectContents)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +157,7 @@ func TestMaskUnit(t *testing.T) {
|
|||||||
sd := &systemd{dir}
|
sd := &systemd{dir}
|
||||||
|
|
||||||
// Ensure mask works with units that do not currently exist
|
// Ensure mask works with units that do not currently exist
|
||||||
uf := &Unit{config.Unit{Name: "foo.service"}}
|
uf := Unit{config.Unit{Name: "foo.service"}}
|
||||||
if err := sd.MaskUnit(uf); err != nil {
|
if err := sd.MaskUnit(uf); err != nil {
|
||||||
t.Fatalf("Unable to mask new unit: %v", err)
|
t.Fatalf("Unable to mask new unit: %v", err)
|
||||||
}
|
}
|
||||||
@@ -194,7 +171,7 @@ func TestMaskUnit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure mask works with unit files that already exist
|
// Ensure mask works with unit files that already exist
|
||||||
ub := &Unit{config.Unit{Name: "bar.service"}}
|
ub := Unit{config.Unit{Name: "bar.service"}}
|
||||||
barPath := path.Join(dir, "etc", "systemd", "system", "bar.service")
|
barPath := path.Join(dir, "etc", "systemd", "system", "bar.service")
|
||||||
if _, err := os.Create(barPath); err != nil {
|
if _, err := os.Create(barPath); err != nil {
|
||||||
t.Fatalf("Error creating new unit file: %v", err)
|
t.Fatalf("Error creating new unit file: %v", err)
|
||||||
@@ -220,12 +197,12 @@ func TestUnmaskUnit(t *testing.T) {
|
|||||||
|
|
||||||
sd := &systemd{dir}
|
sd := &systemd{dir}
|
||||||
|
|
||||||
nilUnit := &Unit{config.Unit{Name: "null.service"}}
|
nilUnit := Unit{config.Unit{Name: "null.service"}}
|
||||||
if err := sd.UnmaskUnit(nilUnit); err != nil {
|
if err := sd.UnmaskUnit(nilUnit); err != nil {
|
||||||
t.Errorf("unexpected error from unmasking nonexistent unit: %v", err)
|
t.Errorf("unexpected error from unmasking nonexistent unit: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
uf := &Unit{config.Unit{Name: "foo.service", Content: "[Service]\nExecStart=/bin/true"}}
|
uf := Unit{config.Unit{Name: "foo.service", Content: "[Service]\nExecStart=/bin/true"}}
|
||||||
dst := uf.Destination(dir)
|
dst := uf.Destination(dir)
|
||||||
if err := os.MkdirAll(path.Dir(dst), os.FileMode(0755)); err != nil {
|
if err := os.MkdirAll(path.Dir(dst), os.FileMode(0755)); err != nil {
|
||||||
t.Fatalf("Unable to create unit directory: %v", err)
|
t.Fatalf("Unable to create unit directory: %v", err)
|
||||||
@@ -245,7 +222,7 @@ func TestUnmaskUnit(t *testing.T) {
|
|||||||
t.Errorf("unmask of non-empty unit mutated unit contents unexpectedly")
|
t.Errorf("unmask of non-empty unit mutated unit contents unexpectedly")
|
||||||
}
|
}
|
||||||
|
|
||||||
ub := &Unit{config.Unit{Name: "bar.service"}}
|
ub := Unit{config.Unit{Name: "bar.service"}}
|
||||||
dst = ub.Destination(dir)
|
dst = ub.Destination(dir)
|
||||||
if err := os.Symlink("/dev/null", dst); err != nil {
|
if err := os.Symlink("/dev/null", dst); err != nil {
|
||||||
t.Fatalf("Unable to create masked unit: %v", err)
|
t.Fatalf("Unable to create masked unit: %v", err)
|
||||||
|
@@ -19,40 +19,66 @@ package system
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/config"
|
"github.com/coreos/coreos-cloudinit/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Name for drop-in service configuration files created by cloudconfig
|
|
||||||
const cloudConfigDropIn = "20-cloudinit.conf"
|
|
||||||
|
|
||||||
type UnitManager interface {
|
type UnitManager interface {
|
||||||
PlaceUnit(unit *Unit, dst string) error
|
PlaceUnit(unit Unit) error
|
||||||
EnableUnitFile(unit string, runtime bool) error
|
PlaceUnitDropIn(unit Unit, dropIn config.UnitDropIn) error
|
||||||
RunUnitCommand(command, unit string) (string, error)
|
EnableUnitFile(unit Unit) error
|
||||||
|
RunUnitCommand(unit Unit, command string) (string, error)
|
||||||
|
MaskUnit(unit Unit) error
|
||||||
|
UnmaskUnit(unit Unit) error
|
||||||
DaemonReload() error
|
DaemonReload() error
|
||||||
MaskUnit(unit *Unit) error
|
|
||||||
UnmaskUnit(unit *Unit) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unit is a top-level structure which embeds its underlying configuration,
|
// Unit is a top-level structure which embeds its underlying configuration,
|
||||||
// config.Unit, and provides the system-specific Destination().
|
// config.Unit, and provides the system-specific Destination(), Type(), and
|
||||||
|
// Group().
|
||||||
type Unit struct {
|
type Unit struct {
|
||||||
config.Unit
|
config.Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destination builds the appropriate absolute file path for
|
// Type returns the extension of the unit (everything that follows the final
|
||||||
// the Unit. The root argument indicates the effective base
|
// period).
|
||||||
// directory of the system (similar to a chroot).
|
func (u Unit) Type() string {
|
||||||
func (u *Unit) Destination(root string) string {
|
ext := filepath.Ext(u.Name)
|
||||||
|
return strings.TrimLeft(ext, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group returns "network" or "system" depending on whether or not the unit is
|
||||||
|
// a network unit or otherwise.
|
||||||
|
func (u Unit) Group() string {
|
||||||
|
switch u.Type() {
|
||||||
|
case "network", "netdev", "link":
|
||||||
|
return "network"
|
||||||
|
default:
|
||||||
|
return "system"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destination builds the appropriate absolute file path for the Unit. The root
|
||||||
|
// argument indicates the effective base directory of the system (similar to a
|
||||||
|
// chroot).
|
||||||
|
func (u Unit) Destination(root string) string {
|
||||||
|
return path.Join(u.prefix(root), u.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DropInDestination builds the appropriate absolute file path for the
|
||||||
|
// UnitDropIn. The root argument indicates the effective base directory of the
|
||||||
|
// system (similar to a chroot) and the dropIn argument is the UnitDropIn for
|
||||||
|
// which the destination is being calculated.
|
||||||
|
func (u Unit) DropInDestination(root string, dropIn config.UnitDropIn) string {
|
||||||
|
return path.Join(u.prefix(root), fmt.Sprintf("%s.d", u.Name), dropIn.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u Unit) prefix(root string) string {
|
||||||
dir := "etc"
|
dir := "etc"
|
||||||
if u.Runtime {
|
if u.Runtime {
|
||||||
dir = "run"
|
dir = "run"
|
||||||
}
|
}
|
||||||
|
return path.Join(root, dir, "systemd", u.Group())
|
||||||
if u.DropIn {
|
|
||||||
return path.Join(root, dir, "systemd", u.Group(), fmt.Sprintf("%s.d", u.Name), cloudConfigDropIn)
|
|
||||||
} else {
|
|
||||||
return path.Join(root, dir, "systemd", u.Group(), u.Name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
138
system/unit_test.go
Normal file
138
system/unit_test.go
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 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 (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/coreos/coreos-cloudinit/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestType(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
|
||||||
|
typ string
|
||||||
|
}{
|
||||||
|
{},
|
||||||
|
{"test.service", "service"},
|
||||||
|
{"hello", ""},
|
||||||
|
{"lots.of.dots", "dots"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
u := Unit{config.Unit{
|
||||||
|
Name: tt.name,
|
||||||
|
}}
|
||||||
|
if typ := u.Type(); tt.typ != typ {
|
||||||
|
t.Errorf("bad type (%+v): want %q, got %q", tt, tt.typ, typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGroup(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
|
||||||
|
group string
|
||||||
|
}{
|
||||||
|
{"test.service", "system"},
|
||||||
|
{"test.link", "network"},
|
||||||
|
{"test.network", "network"},
|
||||||
|
{"test.netdev", "network"},
|
||||||
|
{"test.conf", "system"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
u := Unit{config.Unit{
|
||||||
|
Name: tt.name,
|
||||||
|
}}
|
||||||
|
if group := u.Group(); tt.group != group {
|
||||||
|
t.Errorf("bad group (%+v): want %q, got %q", tt, tt.group, group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDestination(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
root string
|
||||||
|
name string
|
||||||
|
runtime bool
|
||||||
|
|
||||||
|
destination string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
root: "/some/dir",
|
||||||
|
name: "foobar.service",
|
||||||
|
destination: "/some/dir/etc/systemd/system/foobar.service",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: "/some/dir",
|
||||||
|
name: "foobar.service",
|
||||||
|
runtime: true,
|
||||||
|
destination: "/some/dir/run/systemd/system/foobar.service",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
u := Unit{config.Unit{
|
||||||
|
Name: tt.name,
|
||||||
|
Runtime: tt.runtime,
|
||||||
|
}}
|
||||||
|
if d := u.Destination(tt.root); tt.destination != d {
|
||||||
|
t.Errorf("bad destination (%+v): want %q, got %q", tt, tt.destination, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDropInDestination(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
root string
|
||||||
|
unitName string
|
||||||
|
dropInName string
|
||||||
|
runtime bool
|
||||||
|
|
||||||
|
destination string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
root: "/some/dir",
|
||||||
|
unitName: "foo.service",
|
||||||
|
dropInName: "bar.conf",
|
||||||
|
destination: "/some/dir/etc/systemd/system/foo.service.d/bar.conf",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
root: "/some/dir",
|
||||||
|
unitName: "foo.service",
|
||||||
|
dropInName: "bar.conf",
|
||||||
|
runtime: true,
|
||||||
|
destination: "/some/dir/run/systemd/system/foo.service.d/bar.conf",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
u := Unit{config.Unit{
|
||||||
|
Name: tt.unitName,
|
||||||
|
Runtime: tt.runtime,
|
||||||
|
DropIns: []config.UnitDropIn{{
|
||||||
|
Name: tt.dropInName,
|
||||||
|
}},
|
||||||
|
}}
|
||||||
|
if d := u.DropInDestination(tt.root, u.DropIns[0]); tt.destination != d {
|
||||||
|
t.Errorf("bad destination (%+v): want %q, got %q", tt, tt.destination, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -126,7 +126,7 @@ func (uc Update) Units() []Unit {
|
|||||||
Runtime: true,
|
Runtime: true,
|
||||||
}}
|
}}
|
||||||
|
|
||||||
if uc.Update.RebootStrategy == "false" {
|
if uc.Update.RebootStrategy == "false" || uc.Update.RebootStrategy == "off" {
|
||||||
ls.Command = "stop"
|
ls.Command = "stop"
|
||||||
ls.Mask = true
|
ls.Mask = true
|
||||||
}
|
}
|
||||||
|
@@ -80,6 +80,15 @@ func TestUpdateUnits(t *testing.T) {
|
|||||||
Mask: true,
|
Mask: true,
|
||||||
}}},
|
}}},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
config: config.Update{RebootStrategy: "off"},
|
||||||
|
units: []Unit{{config.Unit{
|
||||||
|
Name: "locksmithd.service",
|
||||||
|
Command: "stop",
|
||||||
|
Runtime: true,
|
||||||
|
Mask: true,
|
||||||
|
}}},
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
units := Update{Update: tt.config, ReadConfig: testReadConfig("")}.Units()
|
units := Update{Update: tt.config, ReadConfig: testReadConfig("")}.Units()
|
||||||
if !reflect.DeepEqual(tt.units, units) {
|
if !reflect.DeepEqual(tt.units, units) {
|
||||||
@@ -100,7 +109,7 @@ func TestUpdateFile(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
config: config.Update{RebootStrategy: "wizzlewazzle"},
|
config: config.Update{RebootStrategy: "wizzlewazzle"},
|
||||||
err: &config.ErrorValid{Value: "wizzlewazzle", Field: "RebootStrategy", Valid: []string{"best-effort", "etcd-lock", "reboot", "false"}},
|
err: &config.ErrorValid{Value: "wizzlewazzle", Field: "RebootStrategy", Valid: []string{"best-effort", "etcd-lock", "reboot", "off", "false"}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
config: config.Update{Group: "master", Server: "http://foo.com"},
|
config: config.Update{Group: "master", Server: "http://foo.com"},
|
||||||
@@ -142,6 +151,14 @@ func TestUpdateFile(t *testing.T) {
|
|||||||
RawFilePermissions: "0644",
|
RawFilePermissions: "0644",
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
config: config.Update{RebootStrategy: "off"},
|
||||||
|
file: &File{config.File{
|
||||||
|
Content: "REBOOT_STRATEGY=off\n",
|
||||||
|
Path: "etc/coreos/update.conf",
|
||||||
|
RawFilePermissions: "0644",
|
||||||
|
}},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
config: config.Update{RebootStrategy: "etcd-lock"},
|
config: config.Update{RebootStrategy: "etcd-lock"},
|
||||||
orig: "SERVER=https://example.com\nGROUP=thegroupc\nREBOOT_STRATEGY=awesome",
|
orig: "SERVER=https://example.com\nGROUP=thegroupc\nREBOOT_STRATEGY=awesome",
|
||||||
|
Reference in New Issue
Block a user