Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
31c46c7051 | ||
|
66ec7d805c | ||
|
2563896f89 | ||
|
94a242cc58 | ||
|
5b159fcf56 | ||
|
a9e8940132 | ||
|
cf194ab85e | ||
|
33bc5fc63d | ||
|
09f6a279ef | ||
|
e8c8b811fe | ||
|
f5ecc05d62 | ||
|
66a2f00679 | ||
|
14cad6f7c3 | ||
|
6f188bd5d4 | ||
|
41832ab19e | ||
|
672e4c07af | ||
|
be53013431 | ||
|
c30fc51b03 | ||
|
b429eaab84 | ||
|
e0104e6d93 | ||
|
7bf9712724 | ||
|
78b0f82918 | ||
|
987aa21883 | ||
|
47ac4f6931 | ||
|
f8aa7a43b8 |
@@ -39,7 +39,7 @@ CoreOS tries to conform to each platform's native method to provide user data. E
|
||||
|
||||
### coreos
|
||||
|
||||
#### etcd
|
||||
#### etcd (deprecated. see etcd2)
|
||||
|
||||
The `coreos.etcd.*` parameters will be translated to a partial systemd unit acting as an etcd configuration file.
|
||||
If the platform environment supports the templating feature of coreos-cloudinit it is possible to automate etcd configuration with the `$private_ipv4` and `$public_ipv4` fields. For example, the following cloud-config document...
|
||||
@@ -49,15 +49,15 @@ If the platform environment supports the templating feature of coreos-cloudinit
|
||||
|
||||
coreos:
|
||||
etcd:
|
||||
name: node001
|
||||
# generate a new token for each unique cluster from https://discovery.etcd.io/new
|
||||
discovery: https://discovery.etcd.io/<token>
|
||||
# multi-region and multi-cloud deployments need to use $public_ipv4
|
||||
addr: $public_ipv4:4001
|
||||
peer-addr: $private_ipv4:7001
|
||||
name: node001
|
||||
# generate a new token for each unique cluster from https://discovery.etcd.io/new
|
||||
discovery: https://discovery.etcd.io/<token>
|
||||
# multi-region and multi-cloud deployments need to use $public_ipv4
|
||||
addr: $public_ipv4:4001
|
||||
peer-addr: $private_ipv4:7001
|
||||
```
|
||||
|
||||
...will generate a systemd unit drop-in like this:
|
||||
...will generate a systemd unit drop-in for etcd.service with the following contents:
|
||||
|
||||
```yaml
|
||||
[Service]
|
||||
@@ -71,11 +71,49 @@ For more information about the available configuration parameters, see the [etcd
|
||||
|
||||
_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._
|
||||
|
||||
[etcd-config]: https://github.com/coreos/etcd/blob/master/Documentation/configuration.md
|
||||
[etcd-config]: https://github.com/coreos/etcd/blob/9fa3bea5a22265151f0d5063ce38a79c5b5d0271/Documentation/configuration.md
|
||||
|
||||
#### etcd2
|
||||
|
||||
The `coreos.etcd2.*` parameters will be translated to a partial systemd unit acting as an etcd configuration file.
|
||||
If the platform environment supports the templating feature of coreos-cloudinit it is possible to automate etcd configuration with the `$private_ipv4` and `$public_ipv4` fields. For example, the following cloud-config document...
|
||||
|
||||
```yaml
|
||||
#cloud-config
|
||||
|
||||
coreos:
|
||||
etcd2:
|
||||
# generate a new token for each unique cluster from https://discovery.etcd.io/new
|
||||
discovery: https://discovery.etcd.io/<token>
|
||||
# multi-region and multi-cloud deployments need to use $public_ipv4
|
||||
advertise-client-urls: http://$public_ipv4:2379
|
||||
initial-advertise-peer-urls: http://$private_ipv4:2380
|
||||
# listen on both the official ports and the legacy ports
|
||||
# legacy ports can be omitted if your application doesn't depend on them
|
||||
listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001
|
||||
listen-peer-urls: http://$private_ipv4:2380,http://$private_ipv4:7001
|
||||
```
|
||||
|
||||
...will generate a systemd unit drop-in for etcd2.service with the following contents:
|
||||
|
||||
```yaml
|
||||
[Service]
|
||||
Environment="ETCD_DISCOVERY=https://discovery.etcd.io/<token>"
|
||||
Environment="ETCD_ADVERTISE_CLIENT_URLS=http://203.0.113.29:2379"
|
||||
Environment="ETCD_INITIAL_ADVERTISE_PEER_URLS=http://192.0.2.13:2380"
|
||||
Environment="ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379,http://0.0.0.0:4001"
|
||||
Environment="ETCD_LISTEN_PEERS_URLS=http://192.0.2.13:2380,http://192.0.2.13:7001"
|
||||
```
|
||||
|
||||
For more information about the available configuration parameters, see the [etcd documentation][etcd-config].
|
||||
|
||||
_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._
|
||||
|
||||
[etcd-config]: https://github.com/coreos/etcd/blob/86e616c6e974828fc9119c1eb0f6439577a9ce0b/Documentation/configuration.md
|
||||
|
||||
#### fleet
|
||||
|
||||
The `coreos.fleet.*` parameters work very similarly to `coreos.etcd.*`, and allow for the configuration of fleet through environment variables. For example, the following cloud-config document...
|
||||
The `coreos.fleet.*` parameters work very similarly to `coreos.etcd2.*`, and allow for the configuration of fleet through environment variables. For example, the following cloud-config document...
|
||||
|
||||
```yaml
|
||||
#cloud-config
|
||||
@@ -100,7 +138,7 @@ For more information on fleet configuration, see the [fleet documentation][fleet
|
||||
|
||||
#### flannel
|
||||
|
||||
The `coreos.flannel.*` parameters also work very similarly to `coreos.etcd.*`
|
||||
The `coreos.flannel.*` parameters also work very similarly to `coreos.etcd2.*`
|
||||
and `coreos.fleet.*`. They can be used to set environment variables for
|
||||
flanneld. For example, the following cloud-config...
|
||||
|
||||
@@ -120,11 +158,12 @@ Environment="FLANNELD_ETCD_PREFIX=/coreos.com/network2"
|
||||
```
|
||||
|
||||
List of flannel configuration parameters:
|
||||
|
||||
- **etcd_endpoints**: Comma separated list of etcd endpoints
|
||||
- **etcd_cafile**: Path to CA file used for TLS communication with etcd
|
||||
- **etcd_certfile**: Path to certificate file used for TLS communication with etcd
|
||||
- **etcd_keyfile**: Path to private key file used for TLS communication with etcd
|
||||
- **etcd_prefix**: Etcd prefix path to be used for flannel keys
|
||||
- **etcd_prefix**: etcd prefix path to be used for flannel keys
|
||||
- **ip_masq**: Install IP masquerade rules for traffic outside of flannel subnet
|
||||
- **subnet_file**: Path to flannel subnet file to write out
|
||||
- **interface**: Interface (name or IP) that should be used for inter-host communication
|
||||
@@ -241,14 +280,14 @@ coreos:
|
||||
Environment=DOCKER_OPTS='--insecure-registry="10.0.1.0/24"'
|
||||
```
|
||||
|
||||
Start the built-in `etcd` and `fleet` services:
|
||||
Start the built-in `etcd2` and `fleet` services:
|
||||
|
||||
```yaml
|
||||
#cloud-config
|
||||
|
||||
coreos:
|
||||
units:
|
||||
- name: etcd.service
|
||||
- name: etcd2.service
|
||||
command: start
|
||||
- name: fleet.service
|
||||
command: start
|
||||
@@ -298,6 +337,7 @@ All but the `passwd` and `ssh-authorized-keys` fields will be ignored if the use
|
||||
- **coreos-ssh-import-url**: Authorize SSH keys imported from a url endpoint.
|
||||
- **system**: Create the user as a system user. No home directory will be created.
|
||||
- **no-log-init**: Boolean. Skip initialization of lastlog and faillog databases.
|
||||
- **shell**: User's login shell.
|
||||
|
||||
The following fields are not yet implemented:
|
||||
|
||||
|
@@ -37,6 +37,7 @@ type CloudConfig struct {
|
||||
|
||||
type CoreOS struct {
|
||||
Etcd Etcd `yaml:"etcd"`
|
||||
Etcd2 Etcd2 `yaml:"etcd2"`
|
||||
Flannel Flannel `yaml:"flannel"`
|
||||
Fleet Fleet `yaml:"fleet"`
|
||||
Locksmith Locksmith `yaml:"locksmith"`
|
||||
|
@@ -374,6 +374,7 @@ users:
|
||||
no_user_group: true
|
||||
system: y
|
||||
no_log_init: True
|
||||
shell: /bin/sh
|
||||
`
|
||||
cfg, err := NewCloudConfig(contents)
|
||||
if err != nil {
|
||||
@@ -441,6 +442,10 @@ users:
|
||||
if !user.NoLogInit {
|
||||
t.Errorf("Failed to parse no_log_init field")
|
||||
}
|
||||
|
||||
if user.Shell != "/bin/sh" {
|
||||
t.Errorf("Failed to parse shell field, got %q", user.Shell)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloudConfigUsersGithubUser(t *testing.T) {
|
||||
|
@@ -16,7 +16,7 @@ package config
|
||||
|
||||
type Etcd struct {
|
||||
Addr string `yaml:"addr" env:"ETCD_ADDR"`
|
||||
AdvertiseClientURLs string `yaml:"advertise_client_urls" env:"ETCD_ADVERTISE_CLIENT_URLS"`
|
||||
AdvertiseClientURLs string `yaml:"advertise_client_urls" env:"ETCD_ADVERTISE_CLIENT_URLS" deprecated:"etcd2 options no longer work for etcd"`
|
||||
BindAddr string `yaml:"bind_addr" env:"ETCD_BIND_ADDR"`
|
||||
CAFile string `yaml:"ca_file" env:"ETCD_CA_FILE"`
|
||||
CertFile string `yaml:"cert_file" env:"ETCD_CERT_FILE"`
|
||||
@@ -26,26 +26,26 @@ type Etcd struct {
|
||||
CorsOrigins string `yaml:"cors" env:"ETCD_CORS"`
|
||||
DataDir string `yaml:"data_dir" env:"ETCD_DATA_DIR"`
|
||||
Discovery string `yaml:"discovery" env:"ETCD_DISCOVERY"`
|
||||
DiscoveryFallback string `yaml:"discovery_fallback" env:"ETCD_DISCOVERY_FALLBACK"`
|
||||
DiscoverySRV string `yaml:"discovery_srv" env:"ETCD_DISCOVERY_SRV"`
|
||||
DiscoveryProxy string `yaml:"discovery_proxy" env:"ETCD_DISCOVERY_PROXY"`
|
||||
ElectionTimeout int `yaml:"election_timeout" env:"ETCD_ELECTION_TIMEOUT"`
|
||||
ForceNewCluster bool `yaml:"force_new_cluster" env:"ETCD_FORCE_NEW_CLUSTER"`
|
||||
DiscoveryFallback string `yaml:"discovery_fallback" env:"ETCD_DISCOVERY_FALLBACK" deprecated:"etcd2 options no longer work for etcd"`
|
||||
DiscoverySRV string `yaml:"discovery_srv" env:"ETCD_DISCOVERY_SRV" deprecated:"etcd2 options no longer work for etcd"`
|
||||
DiscoveryProxy string `yaml:"discovery_proxy" env:"ETCD_DISCOVERY_PROXY" deprecated:"etcd2 options no longer work for etcd"`
|
||||
ElectionTimeout int `yaml:"election_timeout" env:"ETCD_ELECTION_TIMEOUT" deprecated:"etcd2 options no longer work for etcd"`
|
||||
ForceNewCluster bool `yaml:"force_new_cluster" env:"ETCD_FORCE_NEW_CLUSTER" deprecated:"etcd2 options no longer work for etcd"`
|
||||
GraphiteHost string `yaml:"graphite_host" env:"ETCD_GRAPHITE_HOST"`
|
||||
HeartbeatInterval int `yaml:"heartbeat_interval" env:"ETCD_HEARTBEAT_INTERVAL"`
|
||||
HeartbeatInterval int `yaml:"heartbeat_interval" env:"ETCD_HEARTBEAT_INTERVAL" deprecated:"etcd2 options no longer work for etcd"`
|
||||
HTTPReadTimeout float64 `yaml:"http_read_timeout" env:"ETCD_HTTP_READ_TIMEOUT"`
|
||||
HTTPWriteTimeout float64 `yaml:"http_write_timeout" env:"ETCD_HTTP_WRITE_TIMEOUT"`
|
||||
InitialAdvertisePeerURLs string `yaml:"initial_advertise_peer_urls" env:"ETCD_INITIAL_ADVERTISE_PEER_URLS"`
|
||||
InitialCluster string `yaml:"initial_cluster" env:"ETCD_INITIAL_CLUSTER"`
|
||||
InitialClusterState string `yaml:"initial_cluster_state" env:"ETCD_INITIAL_CLUSTER_STATE"`
|
||||
InitialClusterToken string `yaml:"initial_cluster_token" env:"ETCD_INITIAL_CLUSTER_TOKEN"`
|
||||
InitialAdvertisePeerURLs string `yaml:"initial_advertise_peer_urls" env:"ETCD_INITIAL_ADVERTISE_PEER_URLS" deprecated:"etcd2 options no longer work for etcd"`
|
||||
InitialCluster string `yaml:"initial_cluster" env:"ETCD_INITIAL_CLUSTER" deprecated:"etcd2 options no longer work for etcd"`
|
||||
InitialClusterState string `yaml:"initial_cluster_state" env:"ETCD_INITIAL_CLUSTER_STATE" deprecated:"etcd2 options no longer work for etcd"`
|
||||
InitialClusterToken string `yaml:"initial_cluster_token" env:"ETCD_INITIAL_CLUSTER_TOKEN" deprecated:"etcd2 options no longer work for etcd"`
|
||||
KeyFile string `yaml:"key_file" env:"ETCD_KEY_FILE"`
|
||||
ListenClientURLs string `yaml:"listen_client_urls" env:"ETCD_LISTEN_CLIENT_URLS"`
|
||||
ListenPeerURLs string `yaml:"listen_peer_urls" env:"ETCD_LISTEN_PEER_URLS"`
|
||||
ListenClientURLs string `yaml:"listen_client_urls" env:"ETCD_LISTEN_CLIENT_URLS" deprecated:"etcd2 options no longer work for etcd"`
|
||||
ListenPeerURLs string `yaml:"listen_peer_urls" env:"ETCD_LISTEN_PEER_URLS" deprecated:"etcd2 options no longer work for etcd"`
|
||||
MaxResultBuffer int `yaml:"max_result_buffer" env:"ETCD_MAX_RESULT_BUFFER"`
|
||||
MaxRetryAttempts int `yaml:"max_retry_attempts" env:"ETCD_MAX_RETRY_ATTEMPTS"`
|
||||
MaxSnapshots int `yaml:"max_snapshots" env:"ETCD_MAX_SNAPSHOTS"`
|
||||
MaxWALs int `yaml:"max_wals" env:"ETCD_MAX_WALS"`
|
||||
MaxSnapshots int `yaml:"max_snapshots" env:"ETCD_MAX_SNAPSHOTS" deprecated:"etcd2 options no longer work for etcd"`
|
||||
MaxWALs int `yaml:"max_wals" env:"ETCD_MAX_WALS" deprecated:"etcd2 options no longer work for etcd"`
|
||||
Name string `yaml:"name" env:"ETCD_NAME"`
|
||||
PeerAddr string `yaml:"peer_addr" env:"ETCD_PEER_ADDR"`
|
||||
PeerBindAddr string `yaml:"peer_bind_addr" env:"ETCD_PEER_BIND_ADDR"`
|
||||
@@ -56,7 +56,7 @@ type Etcd struct {
|
||||
PeerKeyFile string `yaml:"peer_key_file" env:"ETCD_PEER_KEY_FILE"`
|
||||
Peers string `yaml:"peers" env:"ETCD_PEERS"`
|
||||
PeersFile string `yaml:"peers_file" env:"ETCD_PEERS_FILE"`
|
||||
Proxy string `yaml:"proxy" env:"ETCD_PROXY"`
|
||||
Proxy string `yaml:"proxy" env:"ETCD_PROXY" deprecated:"etcd2 options no longer work for etcd"`
|
||||
RetryInterval float64 `yaml:"retry_interval" env:"ETCD_RETRY_INTERVAL"`
|
||||
Snapshot bool `yaml:"snapshot" env:"ETCD_SNAPSHOT"`
|
||||
SnapshotCount int `yaml:"snapshot_count" env:"ETCD_SNAPSHOTCOUNT"`
|
||||
|
44
config/etcd2.go
Normal file
44
config/etcd2.go
Normal file
@@ -0,0 +1,44 @@
|
||||
// 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 config
|
||||
|
||||
type Etcd2 struct {
|
||||
AdvertiseClientURLs string `yaml:"advertise_client_urls" env:"ETCD_ADVERTISE_CLIENT_URLS"`
|
||||
CAFile string `yaml:"ca_file" env:"ETCD_CA_FILE"`
|
||||
CertFile string `yaml:"cert_file" env:"ETCD_CERT_FILE"`
|
||||
CorsOrigins string `yaml:"cors" env:"ETCD_CORS"`
|
||||
DataDir string `yaml:"data_dir" env:"ETCD_DATA_DIR"`
|
||||
Discovery string `yaml:"discovery" env:"ETCD_DISCOVERY"`
|
||||
DiscoveryFallback string `yaml:"discovery_fallback" env:"ETCD_DISCOVERY_FALLBACK"`
|
||||
DiscoverySRV string `yaml:"discovery_srv" env:"ETCD_DISCOVERY_SRV"`
|
||||
DiscoveryProxy string `yaml:"discovery_proxy" env:"ETCD_DISCOVERY_PROXY"`
|
||||
ElectionTimeout int `yaml:"election_timeout" env:"ETCD_ELECTION_TIMEOUT"`
|
||||
HeartbeatInterval int `yaml:"heartbeat_interval" env:"ETCD_HEARTBEAT_INTERVAL"`
|
||||
InitialAdvertisePeerURLs string `yaml:"initial_advertise_peer_urls" env:"ETCD_INITIAL_ADVERTISE_PEER_URLS"`
|
||||
InitialCluster string `yaml:"initial_cluster" env:"ETCD_INITIAL_CLUSTER"`
|
||||
InitialClusterState string `yaml:"initial_cluster_state" env:"ETCD_INITIAL_CLUSTER_STATE"`
|
||||
InitialClusterToken string `yaml:"initial_cluster_token" env:"ETCD_INITIAL_CLUSTER_TOKEN"`
|
||||
KeyFile string `yaml:"key_file" env:"ETCD_KEY_FILE"`
|
||||
ListenClientURLs string `yaml:"listen_client_urls" env:"ETCD_LISTEN_CLIENT_URLS"`
|
||||
ListenPeerURLs string `yaml:"listen_peer_urls" env:"ETCD_LISTEN_PEER_URLS"`
|
||||
MaxSnapshots int `yaml:"max_snapshots" env:"ETCD_MAX_SNAPSHOTS"`
|
||||
MaxWALs int `yaml:"max_wals" env:"ETCD_MAX_WALS"`
|
||||
Name string `yaml:"name" env:"ETCD_NAME"`
|
||||
PeerCAFile string `yaml:"peer_ca_file" env:"ETCD_PEER_CA_FILE"`
|
||||
PeerCertFile string `yaml:"peer_cert_file" env:"ETCD_PEER_CERT_FILE"`
|
||||
PeerKeyFile string `yaml:"peer_key_file" env:"ETCD_PEER_KEY_FILE"`
|
||||
Proxy string `yaml:"proxy" env:"ETCD_PROXY"`
|
||||
SnapshotCount int `yaml:"snapshot_count" env:"ETCD_SNAPSHOTCOUNT"`
|
||||
}
|
@@ -29,4 +29,5 @@ type User struct {
|
||||
NoUserGroup bool `yaml:"no_user_group"`
|
||||
System bool `yaml:"system"`
|
||||
NoLogInit bool `yaml:"no_log_init"`
|
||||
Shell string `yaml:"shell"`
|
||||
}
|
||||
|
@@ -57,9 +57,9 @@ func checkEncoding(cfg node, report *Report) {
|
||||
continue
|
||||
}
|
||||
|
||||
c := f.Child("contents")
|
||||
c := f.Child("content")
|
||||
if _, err := config.DecodeContent(c.String(), e.String()); err != nil {
|
||||
report.Error(c.line, fmt.Sprintf("contents cannot be decoded as %q", e.String()))
|
||||
report.Error(c.line, fmt.Sprintf("content cannot be decoded as %q", e.String()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,6 +82,9 @@ func checkNodeStructure(n, g node, r *Report) {
|
||||
case reflect.Struct:
|
||||
for _, cn := range n.children {
|
||||
if cg := g.Child(cn.name); cg.IsValid() {
|
||||
if msg := cg.field.Tag.Get("deprecated"); msg != "" {
|
||||
r.Warning(cn.line, fmt.Sprintf("deprecated key %q (%s)", cn.name, msg))
|
||||
}
|
||||
checkNodeStructure(cn, cg, r)
|
||||
} else {
|
||||
r.Warning(cn.line, fmt.Sprintf("unrecognized key %q", cn.name))
|
||||
|
@@ -60,27 +60,27 @@ func TestCheckEncoding(t *testing.T) {
|
||||
}{
|
||||
{},
|
||||
{
|
||||
config: "write_files:\n - encoding: base64\n contents: aGVsbG8K",
|
||||
config: "write_files:\n - encoding: base64\n content: aGVsbG8K",
|
||||
},
|
||||
{
|
||||
config: "write_files:\n - contents: !!binary aGVsbG8K",
|
||||
config: "write_files:\n - content: !!binary aGVsbG8K",
|
||||
},
|
||||
{
|
||||
config: "write_files:\n - encoding: base64\n contents: !!binary aGVsbG8K",
|
||||
entries: []Entry{{entryError, `contents cannot be decoded as "base64"`, 3}},
|
||||
config: "write_files:\n - encoding: base64\n content: !!binary aGVsbG8K",
|
||||
entries: []Entry{{entryError, `content cannot be decoded as "base64"`, 3}},
|
||||
},
|
||||
{
|
||||
config: "write_files:\n - encoding: base64\n contents: !!binary YUdWc2JHOEsK",
|
||||
config: "write_files:\n - encoding: base64\n content: !!binary YUdWc2JHOEsK",
|
||||
},
|
||||
{
|
||||
config: "write_files:\n - encoding: gzip\n contents: !!binary H4sIAOC3tVQAA8tIzcnJ5wIAIDA6NgYAAAA=",
|
||||
config: "write_files:\n - encoding: gzip\n content: !!binary H4sIAOC3tVQAA8tIzcnJ5wIAIDA6NgYAAAA=",
|
||||
},
|
||||
{
|
||||
config: "write_files:\n - encoding: gzip+base64\n contents: H4sIAOC3tVQAA8tIzcnJ5wIAIDA6NgYAAAA=",
|
||||
config: "write_files:\n - encoding: gzip+base64\n content: H4sIAOC3tVQAA8tIzcnJ5wIAIDA6NgYAAAA=",
|
||||
},
|
||||
{
|
||||
config: "write_files:\n - encoding: custom\n contents: hello",
|
||||
entries: []Entry{{entryError, `contents cannot be decoded as "custom"`, 3}},
|
||||
config: "write_files:\n - encoding: custom\n content: hello",
|
||||
entries: []Entry{{entryError, `content cannot be decoded as "custom"`, 3}},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -119,6 +119,15 @@ func TestCheckStructure(t *testing.T) {
|
||||
config: "coreos:\n etcd:\n discovery: good",
|
||||
},
|
||||
|
||||
// Test for deprecated keys
|
||||
{
|
||||
config: "coreos:\n etcd:\n addr: hi",
|
||||
},
|
||||
{
|
||||
config: "coreos:\n etcd:\n proxy: hi",
|
||||
entries: []Entry{{entryWarning, "deprecated key \"proxy\" (etcd2 options no longer work for etcd)", 3}},
|
||||
},
|
||||
|
||||
// Test for error on list of nodes
|
||||
{
|
||||
config: "coreos:\n units:\n - hello\n - goodbye",
|
||||
|
@@ -39,7 +39,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
version = "1.3.1"
|
||||
version = "1.4.0"
|
||||
datasourceInterval = 100 * time.Millisecond
|
||||
datasourceMaxInterval = 30 * time.Second
|
||||
datasourceTimeout = 5 * time.Minute
|
||||
|
@@ -69,7 +69,9 @@ func (cd *configDrive) FetchMetadata() (metadata datasource.Metadata, err error)
|
||||
|
||||
metadata.SSHPublicKeys = m.SSHAuthorizedKeyMap
|
||||
metadata.Hostname = m.Hostname
|
||||
metadata.NetworkConfig, err = cd.tryReadFile(path.Join(cd.openstackRoot(), m.NetworkConfig.ContentPath))
|
||||
if m.NetworkConfig.ContentPath != "" {
|
||||
metadata.NetworkConfig, err = cd.tryReadFile(path.Join(cd.openstackRoot(), m.NetworkConfig.ContentPath))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
@@ -31,23 +31,22 @@ func TestFetchMetadata(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
root: "/",
|
||||
files: test.MockFilesystem{"/openstack/latest/meta_data.json": ""},
|
||||
files: test.NewMockFilesystem(test.File{Path: "/openstack/latest/meta_data.json", Contents: ""}),
|
||||
},
|
||||
{
|
||||
root: "/",
|
||||
files: test.MockFilesystem{"/openstack/latest/meta_data.json": `{"ignore": "me"}`},
|
||||
files: test.NewMockFilesystem(test.File{Path: "/openstack/latest/meta_data.json", Contents: `{"ignore": "me"}`}),
|
||||
},
|
||||
{
|
||||
root: "/",
|
||||
files: test.MockFilesystem{"/openstack/latest/meta_data.json": `{"hostname": "host"}`},
|
||||
files: test.NewMockFilesystem(test.File{Path: "/openstack/latest/meta_data.json", Contents: `{"hostname": "host"}`}),
|
||||
metadata: datasource.Metadata{Hostname: "host"},
|
||||
},
|
||||
{
|
||||
root: "/media/configdrive",
|
||||
files: test.MockFilesystem{
|
||||
"/media/configdrive/openstack/latest/meta_data.json": `{"hostname": "host", "network_config": {"content_path": "config_file.json"}, "public_keys":{"1": "key1", "2": "key2"}}`,
|
||||
"/media/configdrive/openstack/config_file.json": "make it work",
|
||||
},
|
||||
files: test.NewMockFilesystem(test.File{Path: "/media/configdrive/openstack/latest/meta_data.json", Contents: `{"hostname": "host", "network_config": {"content_path": "config_file.json"}, "public_keys":{"1": "key1", "2": "key2"}}`},
|
||||
test.File{Path: "/media/configdrive/openstack/config_file.json", Contents: "make it work"},
|
||||
),
|
||||
metadata: datasource.Metadata{
|
||||
Hostname: "host",
|
||||
NetworkConfig: []byte("make it work"),
|
||||
@@ -61,10 +60,10 @@ func TestFetchMetadata(t *testing.T) {
|
||||
cd := configDrive{tt.root, tt.files.ReadFile}
|
||||
metadata, err := cd.FetchMetadata()
|
||||
if err != nil {
|
||||
t.Fatalf("bad error for %q: want %v, got %q", tt, nil, err)
|
||||
t.Fatalf("bad error for %+v: want %v, got %q", tt, nil, err)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.metadata, metadata) {
|
||||
t.Fatalf("bad metadata for %q: want %#v, got %#v", tt, tt.metadata, metadata)
|
||||
t.Fatalf("bad metadata for %+v: want %#v, got %#v", tt, tt.metadata, metadata)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,27 +77,27 @@ func TestFetchUserdata(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
"/",
|
||||
test.MockFilesystem{},
|
||||
test.NewMockFilesystem(),
|
||||
"",
|
||||
},
|
||||
{
|
||||
"/",
|
||||
test.MockFilesystem{"/openstack/latest/user_data": "userdata"},
|
||||
test.NewMockFilesystem(test.File{Path: "/openstack/latest/user_data", Contents: "userdata"}),
|
||||
"userdata",
|
||||
},
|
||||
{
|
||||
"/media/configdrive",
|
||||
test.MockFilesystem{"/media/configdrive/openstack/latest/user_data": "userdata"},
|
||||
test.NewMockFilesystem(test.File{Path: "/media/configdrive/openstack/latest/user_data", Contents: "userdata"}),
|
||||
"userdata",
|
||||
},
|
||||
} {
|
||||
cd := configDrive{tt.root, tt.files.ReadFile}
|
||||
userdata, err := cd.FetchUserdata()
|
||||
if err != nil {
|
||||
t.Fatalf("bad error for %q: want %v, got %q", tt, nil, err)
|
||||
t.Fatalf("bad error for %+v: want %v, got %q", tt, nil, err)
|
||||
}
|
||||
if string(userdata) != tt.userdata {
|
||||
t.Fatalf("bad userdata for %q: want %q, got %q", tt, tt.userdata, userdata)
|
||||
t.Fatalf("bad userdata for %+v: want %q, got %q", tt, tt.userdata, userdata)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -108,7 +108,9 @@ func (scs *serverContextService) FetchMetadata() (metadata datasource.Metadata,
|
||||
}
|
||||
|
||||
metadata.SSHPublicKeys = map[string]string{}
|
||||
if key, ok := inputMetadata.Meta["ssh_public_key"]; ok {
|
||||
// CloudSigma uses an empty string, rather than no string,
|
||||
// to represent the lack of a SSH key
|
||||
if key, _ := inputMetadata.Meta["ssh_public_key"]; len(key) > 0 {
|
||||
splitted := strings.Split(key, " ")
|
||||
metadata.SSHPublicKeys[splitted[len(splitted)-1]] = key
|
||||
}
|
||||
|
@@ -43,6 +43,27 @@ func (f *fakeCepgoClient) FetchRaw(key string) ([]byte, error) {
|
||||
return f.raw, f.err
|
||||
}
|
||||
|
||||
func TestServerContextWithEmptyPublicSSHKey(t *testing.T) {
|
||||
client := new(fakeCepgoClient)
|
||||
scs := NewServerContextService()
|
||||
scs.client = client
|
||||
client.raw = []byte(`{
|
||||
"meta": {
|
||||
"base64_fields": "cloudinit-user-data",
|
||||
"cloudinit-user-data": "I2Nsb3VkLWNvbmZpZwoKaG9zdG5hbWU6IGNvcmVvczE=",
|
||||
"ssh_public_key": ""
|
||||
}
|
||||
}`)
|
||||
metadata, err := scs.FetchMetadata()
|
||||
if err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
if len(metadata.SSHPublicKeys) != 0 {
|
||||
t.Error("There should be no Public SSH Keys provided")
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerContextFetchMetadata(t *testing.T) {
|
||||
client := new(fakeCepgoClient)
|
||||
scs := NewServerContextService()
|
||||
|
57
datasource/test/filesystem.go
Normal file
57
datasource/test/filesystem.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// 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 test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
|
||||
type MockFilesystem map[string]File
|
||||
|
||||
type File struct {
|
||||
Path string
|
||||
Contents string
|
||||
Directory bool
|
||||
}
|
||||
|
||||
func (m MockFilesystem) ReadFile(filename string) ([]byte, error) {
|
||||
if f, ok := m[path.Clean(filename)]; ok {
|
||||
if f.Directory {
|
||||
return nil, fmt.Errorf("read %s: is a directory", filename)
|
||||
}
|
||||
return []byte(f.Contents), nil
|
||||
}
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
|
||||
func NewMockFilesystem(files ...File) MockFilesystem {
|
||||
fs := MockFilesystem{}
|
||||
for _, file := range files {
|
||||
fs[file.Path] = file
|
||||
|
||||
// Create the directories leading up to the file
|
||||
p := path.Dir(file.Path)
|
||||
for p != "/" && p != "." {
|
||||
if f, ok := fs[p]; ok && !f.Directory {
|
||||
panic(fmt.Sprintf("%q already exists and is not a directory (%#v)", p, f))
|
||||
}
|
||||
fs[p] = File{Path: p, Directory: true}
|
||||
p = path.Dir(p)
|
||||
}
|
||||
}
|
||||
return fs
|
||||
}
|
115
datasource/test/filesystem_test.go
Normal file
115
datasource/test/filesystem_test.go
Normal file
@@ -0,0 +1,115 @@
|
||||
// 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 test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReadFile(t *testing.T) {
|
||||
tests := []struct {
|
||||
filesystem MockFilesystem
|
||||
|
||||
filename string
|
||||
contents string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
filename: "dne",
|
||||
err: os.ErrNotExist,
|
||||
},
|
||||
{
|
||||
filesystem: MockFilesystem{
|
||||
"exists": File{Contents: "hi"},
|
||||
},
|
||||
filename: "exists",
|
||||
contents: "hi",
|
||||
},
|
||||
{
|
||||
filesystem: MockFilesystem{
|
||||
"dir": File{Directory: true},
|
||||
},
|
||||
filename: "dir",
|
||||
err: errors.New("read dir: is a directory"),
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
contents, err := tt.filesystem.ReadFile(tt.filename)
|
||||
if tt.contents != string(contents) {
|
||||
t.Errorf("bad contents (test %d): want %q, got %q", i, tt.contents, string(contents))
|
||||
}
|
||||
if !reflect.DeepEqual(tt.err, err) {
|
||||
t.Errorf("bad error (test %d): want %v, got %v", i, tt.err, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewMockFilesystem(t *testing.T) {
|
||||
tests := []struct {
|
||||
files []File
|
||||
|
||||
filesystem MockFilesystem
|
||||
}{
|
||||
{
|
||||
filesystem: MockFilesystem{},
|
||||
},
|
||||
{
|
||||
files: []File{File{Path: "file"}},
|
||||
filesystem: MockFilesystem{
|
||||
"file": File{Path: "file"},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: []File{File{Path: "/file"}},
|
||||
filesystem: MockFilesystem{
|
||||
"/file": File{Path: "/file"},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: []File{File{Path: "/dir/file"}},
|
||||
filesystem: MockFilesystem{
|
||||
"/dir": File{Path: "/dir", Directory: true},
|
||||
"/dir/file": File{Path: "/dir/file"},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: []File{File{Path: "/dir/dir/file"}},
|
||||
filesystem: MockFilesystem{
|
||||
"/dir": File{Path: "/dir", Directory: true},
|
||||
"/dir/dir": File{Path: "/dir/dir", Directory: true},
|
||||
"/dir/dir/file": File{Path: "/dir/dir/file"},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: []File{File{Path: "/dir/dir/dir", Directory: true}},
|
||||
filesystem: MockFilesystem{
|
||||
"/dir": File{Path: "/dir", Directory: true},
|
||||
"/dir/dir": File{Path: "/dir/dir", Directory: true},
|
||||
"/dir/dir/dir": File{Path: "/dir/dir/dir", Directory: true},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
filesystem := NewMockFilesystem(tt.files...)
|
||||
if !reflect.DeepEqual(tt.filesystem, filesystem) {
|
||||
t.Errorf("bad filesystem (test %d): want %#v, got %#v", i, tt.filesystem, filesystem)
|
||||
}
|
||||
}
|
||||
}
|
@@ -31,19 +31,19 @@ func TestFetchMetadata(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
root: "/",
|
||||
files: test.MockFilesystem{},
|
||||
files: test.NewMockFilesystem(),
|
||||
},
|
||||
{
|
||||
root: "/",
|
||||
files: test.MockFilesystem{"/SharedConfig.xml": ""},
|
||||
files: test.NewMockFilesystem(test.File{Path: "/SharedConfig.xml", Contents: ""}),
|
||||
},
|
||||
{
|
||||
root: "/var/lib/waagent",
|
||||
files: test.MockFilesystem{"/var/lib/waagent/SharedConfig.xml": ""},
|
||||
files: test.NewMockFilesystem(test.File{Path: "/var/lib/waagent/SharedConfig.xml", Contents: ""}),
|
||||
},
|
||||
{
|
||||
root: "/var/lib/waagent",
|
||||
files: test.MockFilesystem{"/var/lib/waagent/SharedConfig.xml": `<?xml version="1.0" encoding="utf-8"?>
|
||||
files: test.NewMockFilesystem(test.File{Path: "/var/lib/waagent/SharedConfig.xml", Contents: `<?xml version="1.0" encoding="utf-8"?>
|
||||
<SharedConfig version="1.0.0.0" goalStateIncarnation="1">
|
||||
<Deployment name="c8f9e4c9c18948e1bebf57c5685da756" guid="{1d10394f-c741-4a1a-a6bb-278f213c5a5e}" incarnation="0" isNonCancellableTopologyChangeEnabled="false">
|
||||
<Service name="core-test-1" guid="{00000000-0000-0000-0000-000000000000}" />
|
||||
@@ -79,7 +79,7 @@ func TestFetchMetadata(t *testing.T) {
|
||||
</InputEndpoints>
|
||||
</Instance>
|
||||
</Instances>
|
||||
</SharedConfig>`},
|
||||
</SharedConfig>`}),
|
||||
metadata: datasource.Metadata{
|
||||
PrivateIPv4: net.ParseIP("100.73.202.64"),
|
||||
PublicIPv4: net.ParseIP("191.239.39.77"),
|
||||
@@ -89,10 +89,10 @@ func TestFetchMetadata(t *testing.T) {
|
||||
a := waagent{tt.root, tt.files.ReadFile}
|
||||
metadata, err := a.FetchMetadata()
|
||||
if err != nil {
|
||||
t.Fatalf("bad error for %q: want %v, got %q", tt, nil, err)
|
||||
t.Fatalf("bad error for %+v: want %v, got %q", tt, nil, err)
|
||||
}
|
||||
if !reflect.DeepEqual(tt.metadata, metadata) {
|
||||
t.Fatalf("bad metadata for %q: want %#v, got %#v", tt, tt.metadata, metadata)
|
||||
t.Fatalf("bad metadata for %+v: want %#v, got %#v", tt, tt.metadata, metadata)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,21 +104,21 @@ func TestFetchUserdata(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
"/",
|
||||
test.MockFilesystem{},
|
||||
test.NewMockFilesystem(),
|
||||
},
|
||||
{
|
||||
"/",
|
||||
test.MockFilesystem{"/CustomData": ""},
|
||||
test.NewMockFilesystem(test.File{Path: "/CustomData", Contents: ""}),
|
||||
},
|
||||
{
|
||||
"/var/lib/waagent/",
|
||||
test.MockFilesystem{"/var/lib/waagent/CustomData": ""},
|
||||
test.NewMockFilesystem(test.File{Path: "/var/lib/waagent/CustomData", Contents: ""}),
|
||||
},
|
||||
} {
|
||||
a := waagent{tt.root, tt.files.ReadFile}
|
||||
_, err := a.FetchUserdata()
|
||||
if err != nil {
|
||||
t.Fatalf("bad error for %q: want %v, got %q", tt, nil, err)
|
||||
t.Fatalf("bad error for %+v: want %v, got %q", tt, nil, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -135,6 +135,7 @@ func Apply(cfg config.CloudConfig, ifaces []network.InterfaceGenerator, env *Env
|
||||
|
||||
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},
|
||||
|
@@ -12,17 +12,26 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package test
|
||||
package system
|
||||
|
||||
import (
|
||||
"os"
|
||||
"github.com/coreos/coreos-cloudinit/config"
|
||||
)
|
||||
|
||||
type MockFilesystem map[string]string
|
||||
|
||||
func (m MockFilesystem) ReadFile(filename string) ([]byte, error) {
|
||||
if contents, ok := m[filename]; ok {
|
||||
return []byte(contents), nil
|
||||
}
|
||||
return nil, os.ErrNotExist
|
||||
// Etcd2 is a top-level structure which embeds its underlying configuration,
|
||||
// config.Etcd2, and provides the system-specific Unit().
|
||||
type Etcd2 struct {
|
||||
config.Etcd2
|
||||
}
|
||||
|
||||
// Units creates a Unit file drop-in for etcd, using any configured options.
|
||||
func (ee Etcd2) Units() []Unit {
|
||||
return []Unit{{config.Unit{
|
||||
Name: "etcd2.service",
|
||||
Runtime: true,
|
||||
DropIns: []config.UnitDropIn{{
|
||||
Name: "20-cloudinit.conf",
|
||||
Content: serviceContents(ee.Etcd2),
|
||||
}},
|
||||
}}}
|
||||
}
|
@@ -72,6 +72,10 @@ func CreateUser(u *config.User) error {
|
||||
args = append(args, "--no-log-init")
|
||||
}
|
||||
|
||||
if u.Shell != "" {
|
||||
args = append(args, "--shell", u.Shell)
|
||||
}
|
||||
|
||||
args = append(args, u.Name)
|
||||
|
||||
output, err := exec.Command("useradd", args...).CombinedOutput()
|
||||
|
Reference in New Issue
Block a user