1
0
mirror of https://github.com/rancher/os.git synced 2025-07-11 05:43:03 +00:00

Merge pull request #692 from ibuildthecloud/godep

Newer version of coreos-cloudinit
This commit is contained in:
Ivan Mikushin 2015-12-18 15:11:02 +05:00
commit d18ec0d34b
40 changed files with 1383 additions and 220 deletions

View File

@ -11,7 +11,7 @@ import:
version: 0302d3914d2a6ad61404584cdae6e6dbc9c03599 version: 0302d3914d2a6ad61404584cdae6e6dbc9c03599
- package: github.com/coreos/coreos-cloudinit - package: github.com/coreos/coreos-cloudinit
version: 405c2600b19ae77516c967f8ee8ebde5624d3663 version: 65031e1ab2d3574544d26f5b5d7ddddd0032fd00
repo: https://github.com/rancher/coreos-cloudinit.git repo: https://github.com/rancher/coreos-cloudinit.git
- package: github.com/coreos/go-systemd - package: github.com/coreos/go-systemd

View File

@ -3,15 +3,10 @@ sudo: false
matrix: matrix:
include: include:
- go: 1.4 - go: 1.4
env: TOOLS_CMD=golang.org/x/tools/cmd install:
- go: 1.3 - go get golang.org/x/tools/cmd/cover
env: TOOLS_CMD=code.google.com/p/go.tools/cmd - go get golang.org/x/tools/cmd/vet
- go: 1.2 - go: 1.5
env: TOOLS_CMD=code.google.com/p/go.tools/cmd
install:
- go get ${TOOLS_CMD}/cover
- go get ${TOOLS_CMD}/vet
script: script:
- ./test - ./test

View File

@ -77,3 +77,10 @@ coreos:
addr: 203.0.113.29:4001 addr: 203.0.113.29:4001
peer-addr: 192.0.2.13:7001 peer-addr: 192.0.2.13:7001
``` ```
## Bugs
Please use the [CoreOS issue tracker][bugs] to report all bugs, issues, and feature requests.
[bugs]: https://github.com/coreos/bugs/issues/new?labels=component/cloud-init

View File

@ -1,7 +1,10 @@
#!/bin/bash -e #!/bin/bash -e
NAME="coreos-cloudinit"
ORG_PATH="github.com/coreos" ORG_PATH="github.com/coreos"
REPO_PATH="${ORG_PATH}/coreos-cloudinit" REPO_PATH="${ORG_PATH}/${NAME}"
VERSION=$(git describe --dirty --tags)
GLDFLAGS="-X main.version \"${VERSION}\""
if [ ! -h gopath/src/${REPO_PATH} ]; then if [ ! -h gopath/src/${REPO_PATH} ]; then
mkdir -p gopath/src/${ORG_PATH} mkdir -p gopath/src/${ORG_PATH}
@ -11,4 +14,4 @@ fi
export GOBIN=${PWD}/bin export GOBIN=${PWD}/bin
export GOPATH=${PWD}/gopath export GOPATH=${PWD}/gopath
go build -o bin/coreos-cloudinit ${REPO_PATH} go build -ldflags "${GLDFLAGS}" -o ${GOBIN}/${NAME} ${REPO_PATH}

View File

@ -19,6 +19,7 @@ import (
"reflect" "reflect"
"regexp" "regexp"
"strings" "strings"
"unicode"
"github.com/coreos/yaml" "github.com/coreos/yaml"
) )
@ -37,6 +38,7 @@ type CloudConfig struct {
type CoreOS struct { type CoreOS struct {
Etcd Etcd `yaml:"etcd"` Etcd Etcd `yaml:"etcd"`
Etcd2 Etcd2 `yaml:"etcd2"`
Flannel Flannel `yaml:"flannel"` Flannel Flannel `yaml:"flannel"`
Fleet Fleet `yaml:"fleet"` Fleet Fleet `yaml:"fleet"`
Locksmith Locksmith `yaml:"locksmith"` Locksmith Locksmith `yaml:"locksmith"`
@ -48,10 +50,8 @@ type CoreOS struct {
func IsCloudConfig(userdata string) bool { func IsCloudConfig(userdata string) bool {
header := strings.SplitN(userdata, "\n", 2)[0] header := strings.SplitN(userdata, "\n", 2)[0]
// Explicitly trim the header so we can handle user-data from // Trim trailing whitespaces
// non-unix operating systems. The rest of the file is parsed header = strings.TrimRightFunc(header, unicode.IsSpace)
// by yaml, which correctly handles CRLF.
header = strings.TrimSuffix(header, "\r")
return (header == "#cloud-config") return (header == "#cloud-config")
} }

View File

@ -374,6 +374,7 @@ users:
no_user_group: true no_user_group: true
system: y system: y
no_log_init: True no_log_init: True
shell: /bin/sh
` `
cfg, err := NewCloudConfig(contents) cfg, err := NewCloudConfig(contents)
if err != nil { if err != nil {
@ -441,6 +442,10 @@ users:
if !user.NoLogInit { if !user.NoLogInit {
t.Errorf("Failed to parse no_log_init field") 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) { func TestCloudConfigUsersGithubUser(t *testing.T) {

View File

@ -16,7 +16,7 @@ package config
type Etcd struct { type Etcd struct {
Addr string `yaml:"addr" env:"ETCD_ADDR"` 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"` 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"`
@ -26,26 +26,26 @@ type Etcd struct {
CorsOrigins string `yaml:"cors" env:"ETCD_CORS"` CorsOrigins string `yaml:"cors" env:"ETCD_CORS"`
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"`
DiscoveryFallback string `yaml:"discovery_fallback" env:"ETCD_DISCOVERY_FALLBACK"` 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"` 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"` 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"` 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"` 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"` 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"` HTTPReadTimeout float64 `yaml:"http_read_timeout" env:"ETCD_HTTP_READ_TIMEOUT"`
HTTPWriteTimeout float64 `yaml:"http_write_timeout" env:"ETCD_HTTP_WRITE_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"` 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"` 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"` 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"` 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"` KeyFile string `yaml:"key_file" env:"ETCD_KEY_FILE"`
ListenClientURLs string `yaml:"listen_client_urls" env:"ETCD_LISTEN_CLIENT_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"` 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"` MaxResultBuffer int `yaml:"max_result_buffer" env:"ETCD_MAX_RESULT_BUFFER"`
MaxRetryAttempts int `yaml:"max_retry_attempts" env:"ETCD_MAX_RETRY_ATTEMPTS"` MaxRetryAttempts int `yaml:"max_retry_attempts" env:"ETCD_MAX_RETRY_ATTEMPTS"`
MaxSnapshots int `yaml:"max_snapshots" env:"ETCD_MAX_SNAPSHOTS"` 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"` MaxWALs int `yaml:"max_wals" env:"ETCD_MAX_WALS" deprecated:"etcd2 options no longer work for etcd"`
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"`
@ -56,7 +56,7 @@ type Etcd struct {
PeerKeyFile string `yaml:"peer_key_file" env:"ETCD_PEER_KEY_FILE"` PeerKeyFile string `yaml:"peer_key_file" env:"ETCD_PEER_KEY_FILE"`
Peers string `yaml:"peers" env:"ETCD_PEERS"` Peers string `yaml:"peers" env:"ETCD_PEERS"`
PeersFile string `yaml:"peers_file" env:"ETCD_PEERS_FILE"` 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"` RetryInterval float64 `yaml:"retry_interval" env:"ETCD_RETRY_INTERVAL"`
Snapshot bool `yaml:"snapshot" env:"ETCD_SNAPSHOT"` Snapshot bool `yaml:"snapshot" env:"ETCD_SNAPSHOT"`
SnapshotCount int `yaml:"snapshot_count" env:"ETCD_SNAPSHOTCOUNT"` SnapshotCount int `yaml:"snapshot_count" env:"ETCD_SNAPSHOTCOUNT"`

View 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 config
type Etcd2 struct {
AdvertiseClientURLs string `yaml:"advertise_client_urls" env:"ETCD_ADVERTISE_CLIENT_URLS"`
CAFile string `yaml:"ca_file" env:"ETCD_CA_FILE" deprecated:"ca_file obsoleted by trusted_ca_file and client_cert_auth"`
CertFile string `yaml:"cert_file" env:"ETCD_CERT_FILE"`
ClientCertAuth bool `yaml:"client_cert_auth" env:"ETCD_CLIENT_CERT_AUTH"`
CorsOrigins string `yaml:"cors" env:"ETCD_CORS"`
DataDir string `yaml:"data_dir" env:"ETCD_DATA_DIR"`
Debug bool `yaml:"debug" env:"ETCD_DEBUG"`
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"`
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"`
LogPackageLevels string `yaml:"log_package_levels" env:"ETCD_LOG_PACKAGE_LEVELS"`
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" deprecated:"peer_ca_file obsoleted peer_trusted_ca_file and peer_client_cert_auth"`
PeerCertFile string `yaml:"peer_cert_file" env:"ETCD_PEER_CERT_FILE"`
PeerKeyFile string `yaml:"peer_key_file" env:"ETCD_PEER_KEY_FILE"`
PeerClientCertAuth bool `yaml:"peer_client_cert_auth" env:"ETCD_PEER_CLIENT_CERT_AUTH"`
PeerTrustedCAFile string `yaml:"peer_trusted_ca_file" env:"ETCD_PEER_TRUSTED_CA_FILE"`
Proxy string `yaml:"proxy" env:"ETCD_PROXY" valid:"^(on|off|readonly)$"`
ProxyDialTimeout int `yaml:"proxy_dial_timeout" env:"ETCD_PROXY_DIAL_TIMEOUT"`
ProxyFailureWait int `yaml:"proxy_failure_wait" env:"ETCD_PROXY_FAILURE_WAIT"`
ProxyReadTimeout int `yaml:"proxy_read_timeout" env:"ETCD_PROXY_READ_TIMEOUT"`
ProxyRefreshInterval int `yaml:"proxy_refresh_interval" env:"ETCD_PROXY_REFRESH_INTERVAL"`
ProxyWriteTimeout int `yaml:"proxy_write_timeout" env:"ETCD_PROXY_WRITE_TIMEOUT"`
SnapshotCount int `yaml:"snapshot_count" env:"ETCD_SNAPSHOT_COUNT"`
TrustedCAFile string `yaml:"trusted_ca_file" env:"ETCD_TRUSTED_CA_FILE"`
WalDir string `yaml:"wal_dir" env:"ETCD_WAL_DIR"`
}

View File

@ -1,18 +1,16 @@
/* // Copyright 2015 CoreOS, Inc.
Copyright 2014 CoreOS, Inc. //
// Licensed under the Apache License, Version 2.0 (the "License");
Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License.
you may not use this file except in compliance with the License. // You may obtain a copy of the License at
You may obtain a copy of the License at //
// http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0 //
// Unless required by applicable law or agreed to in writing, software
Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS,
distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and
See the License for the specific language governing permissions and // limitations under the License.
limitations under the License.
*/
package config package config

View File

@ -23,4 +23,5 @@ type Flannel struct {
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"`
PublicIP string `yaml:"public_ip" env:"FLANNELD_PUBLIC_IP"`
} }

View File

@ -16,6 +16,8 @@ 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"`
AuthorizedKeysFile string `yaml:"authorized_keys_file" env:"FLEET_AUTHORIZED_KEYS_FILE"`
DisableEngine bool `yaml:"disable_engine" env:"FLEET_DISABLE_ENGINE"`
EngineReconcileInterval float64 `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"`
@ -25,5 +27,7 @@ type Fleet struct {
EtcdServers string `yaml:"etcd_servers" env:"FLEET_ETCD_SERVERS"` EtcdServers string `yaml:"etcd_servers" env:"FLEET_ETCD_SERVERS"`
Metadata string `yaml:"metadata" env:"FLEET_METADATA"` Metadata string `yaml:"metadata" env:"FLEET_METADATA"`
PublicIP string `yaml:"public_ip" env:"FLEET_PUBLIC_IP"` PublicIP string `yaml:"public_ip" env:"FLEET_PUBLIC_IP"`
TokenLimit int `yaml:"token_limit" env:"FLEET_TOKEN_LIMIT"`
Verbosity int `yaml:"verbosity" env:"FLEET_VERBOSITY"` Verbosity int `yaml:"verbosity" env:"FLEET_VERBOSITY"`
VerifyUnits bool `yaml:"verify_units" env:"FLEET_VERIFY_UNITS"`
} }

View File

@ -0,0 +1,26 @@
// 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
import (
"encoding/json"
)
func IsIgnitionConfig(userdata string) bool {
var cfg struct {
Version *int `json:"ignitionVersion" yaml:"ignition_version"`
}
return (json.Unmarshal([]byte(userdata), &cfg) == nil && cfg.Version != nil)
}

View File

@ -19,4 +19,7 @@ type Locksmith struct {
EtcdCAFile string `yaml:"etcd_cafile" env:"LOCKSMITHD_ETCD_CAFILE"` EtcdCAFile string `yaml:"etcd_cafile" env:"LOCKSMITHD_ETCD_CAFILE"`
EtcdCertFile string `yaml:"etcd_certfile" env:"LOCKSMITHD_ETCD_CERTFILE"` EtcdCertFile string `yaml:"etcd_certfile" env:"LOCKSMITHD_ETCD_CERTFILE"`
EtcdKeyFile string `yaml:"etcd_keyfile" env:"LOCKSMITHD_ETCD_KEYFILE"` EtcdKeyFile string `yaml:"etcd_keyfile" env:"LOCKSMITHD_ETCD_KEYFILE"`
Group string `yaml:"group" env:"LOCKSMITHD_GROUP"`
RebootWindowStart string `yaml:"window_start" env:"REBOOT_WINDOW_START" valid:"^((?i:sun|mon|tue|wed|thu|fri|sat|sun) )?0*([0-9]|1[0-9]|2[0-3]):0*([0-9]|[1-5][0-9])$"`
RebootWindowLength string `yaml:"window_length" env:"REBOOT_WINDOW_LENGTH" valid:"^[-+]?([0-9]*(\\.[0-9]*)?[a-z]+)+$"`
} }

View File

@ -0,0 +1,76 @@
// 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
import (
"testing"
)
func TestRebootWindowStart(t *testing.T) {
tests := []struct {
value string
isValid bool
}{
{value: "Sun 0:0", isValid: true},
{value: "Sun 00:00", isValid: true},
{value: "sUn 23:59", isValid: true},
{value: "mon 0:0", isValid: true},
{value: "tue 0:0", isValid: true},
{value: "tues 0:0", isValid: false},
{value: "wed 0:0", isValid: true},
{value: "thu 0:0", isValid: true},
{value: "thur 0:0", isValid: false},
{value: "fri 0:0", isValid: true},
{value: "sat 0:0", isValid: true},
{value: "sat00:00", isValid: false},
{value: "00:00", isValid: true},
{value: "10:10", isValid: true},
{value: "20:20", isValid: true},
{value: "20:30", isValid: true},
{value: "20:40", isValid: true},
{value: "20:50", isValid: true},
{value: "20:60", isValid: false},
{value: "24:00", isValid: false},
}
for _, tt := range tests {
isValid := (nil == AssertStructValid(Locksmith{RebootWindowStart: tt.value}))
if tt.isValid != isValid {
t.Errorf("bad assert (%s): want %t, got %t", tt.value, tt.isValid, isValid)
}
}
}
func TestRebootWindowLength(t *testing.T) {
tests := []struct {
value string
isValid bool
}{
{value: "1h", isValid: true},
{value: "1d", isValid: true},
{value: "0d", isValid: true},
{value: "0.5h", isValid: true},
{value: "0.5.0h", isValid: false},
}
for _, tt := range tests {
isValid := (nil == AssertStructValid(Locksmith{RebootWindowLength: tt.value}))
if tt.isValid != isValid {
t.Errorf("bad assert (%s): want %t, got %t", tt.value, tt.isValid, isValid)
}
}
}

View File

@ -18,9 +18,9 @@ type User struct {
Name string `yaml:"name"` Name string `yaml:"name"`
PasswordHash string `yaml:"passwd"` PasswordHash string `yaml:"passwd"`
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"` SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
SSHImportGithubUser string `yaml:"coreos_ssh_import_github"` SSHImportGithubUser string `yaml:"coreos_ssh_import_github" deprecated:"trying to fetch from a remote endpoint introduces too many intermittent errors"`
SSHImportGithubUsers []string `yaml:"coreos_ssh_import_github_users"` SSHImportGithubUsers []string `yaml:"coreos_ssh_import_github_users" deprecated:"trying to fetch from a remote endpoint introduces too many intermittent errors"`
SSHImportURL string `yaml:"coreos_ssh_import_url"` SSHImportURL string `yaml:"coreos_ssh_import_url" deprecated:"trying to fetch from a remote endpoint introduces too many intermittent errors"`
GECOS string `yaml:"gecos"` GECOS string `yaml:"gecos"`
Homedir string `yaml:"homedir"` Homedir string `yaml:"homedir"`
NoCreateHome bool `yaml:"no_create_home"` NoCreateHome bool `yaml:"no_create_home"`
@ -29,4 +29,5 @@ type User struct {
NoUserGroup bool `yaml:"no_user_group"` NoUserGroup bool `yaml:"no_user_group"`
System bool `yaml:"system"` System bool `yaml:"system"`
NoLogInit bool `yaml:"no_log_init"` NoLogInit bool `yaml:"no_log_init"`
Shell string `yaml:"shell"`
} }

View File

@ -15,9 +15,14 @@
package main package main
import ( import (
"bytes"
"compress/gzip"
"flag" "flag"
"fmt" "fmt"
"io/ioutil"
"log"
"os" "os"
"runtime"
"sync" "sync"
"time" "time"
@ -29,8 +34,10 @@ import (
"github.com/coreos/coreos-cloudinit/datasource/metadata/cloudsigma" "github.com/coreos/coreos-cloudinit/datasource/metadata/cloudsigma"
"github.com/coreos/coreos-cloudinit/datasource/metadata/digitalocean" "github.com/coreos/coreos-cloudinit/datasource/metadata/digitalocean"
"github.com/coreos/coreos-cloudinit/datasource/metadata/ec2" "github.com/coreos/coreos-cloudinit/datasource/metadata/ec2"
"github.com/coreos/coreos-cloudinit/datasource/metadata/packet"
"github.com/coreos/coreos-cloudinit/datasource/proc_cmdline" "github.com/coreos/coreos-cloudinit/datasource/proc_cmdline"
"github.com/coreos/coreos-cloudinit/datasource/url" "github.com/coreos/coreos-cloudinit/datasource/url"
"github.com/coreos/coreos-cloudinit/datasource/vmware"
"github.com/coreos/coreos-cloudinit/datasource/waagent" "github.com/coreos/coreos-cloudinit/datasource/waagent"
"github.com/coreos/coreos-cloudinit/initialize" "github.com/coreos/coreos-cloudinit/initialize"
"github.com/coreos/coreos-cloudinit/network" "github.com/coreos/coreos-cloudinit/network"
@ -39,7 +46,6 @@ import (
) )
const ( const (
version = "1.3.2+git"
datasourceInterval = 100 * time.Millisecond datasourceInterval = 100 * time.Millisecond
datasourceMaxInterval = 30 * time.Second datasourceMaxInterval = 30 * time.Second
datasourceTimeout = 5 * time.Minute datasourceTimeout = 5 * time.Minute
@ -57,8 +63,10 @@ var (
ec2MetadataService string ec2MetadataService string
cloudSigmaMetadataService bool cloudSigmaMetadataService bool
digitalOceanMetadataService string digitalOceanMetadataService string
packetMetadataService string
url string url string
procCmdLine bool procCmdLine bool
vmware bool
} }
convertNetconf string convertNetconf string
workspace string workspace string
@ -66,6 +74,7 @@ var (
oem string oem string
validate bool validate bool
}{} }{}
version = "was not built properly"
) )
func init() { func init() {
@ -78,8 +87,10 @@ func init() {
flag.StringVar(&flags.sources.ec2MetadataService, "from-ec2-metadata", "", "Download EC2 data from the provided url") flag.StringVar(&flags.sources.ec2MetadataService, "from-ec2-metadata", "", "Download EC2 data from the provided url")
flag.BoolVar(&flags.sources.cloudSigmaMetadataService, "from-cloudsigma-metadata", false, "Download data from CloudSigma server context") flag.BoolVar(&flags.sources.cloudSigmaMetadataService, "from-cloudsigma-metadata", false, "Download data from CloudSigma server context")
flag.StringVar(&flags.sources.digitalOceanMetadataService, "from-digitalocean-metadata", "", "Download DigitalOcean data from the provided url") flag.StringVar(&flags.sources.digitalOceanMetadataService, "from-digitalocean-metadata", "", "Download DigitalOcean data from the provided url")
flag.StringVar(&flags.sources.packetMetadataService, "from-packet-metadata", "", "Download Packet data from metadata service")
flag.StringVar(&flags.sources.url, "from-url", "", "Download user-data from provided url") flag.StringVar(&flags.sources.url, "from-url", "", "Download user-data from provided url")
flag.BoolVar(&flags.sources.procCmdLine, "from-proc-cmdline", false, fmt.Sprintf("Parse %s for '%s=<url>', using the cloud-config served by an HTTP GET to <url>", proc_cmdline.ProcCmdlineLocation, proc_cmdline.ProcCmdlineCloudConfigFlag)) flag.BoolVar(&flags.sources.procCmdLine, "from-proc-cmdline", false, fmt.Sprintf("Parse %s for '%s=<url>', using the cloud-config served by an HTTP GET to <url>", proc_cmdline.ProcCmdlineLocation, proc_cmdline.ProcCmdlineCloudConfigFlag))
flag.BoolVar(&flags.sources.vmware, "from-vmware-guestinfo", false, "Read data from VMware guestinfo")
flag.StringVar(&flags.oem, "oem", "", "Use the settings specific to the provided OEM") flag.StringVar(&flags.oem, "oem", "", "Use the settings specific to the provided OEM")
flag.StringVar(&flags.convertNetconf, "convert-netconf", "", "Read the network config provided in cloud-drive and translate it from the specified format into networkd unit files") flag.StringVar(&flags.convertNetconf, "convert-netconf", "", "Read the network config provided in cloud-drive and translate it from the specified format into networkd unit files")
flag.StringVar(&flags.workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data") flag.StringVar(&flags.workspace, "workspace", "/var/lib/coreos-cloudinit", "Base directory coreos-cloudinit should use to store data")
@ -109,12 +120,25 @@ var (
"cloudsigma": oemConfig{ "cloudsigma": oemConfig{
"from-cloudsigma-metadata": "true", "from-cloudsigma-metadata": "true",
}, },
"packet": oemConfig{
"from-packet-metadata": "https://metadata.packet.net/",
},
"vmware": oemConfig{
"from-vmware-guestinfo": "true",
"convert-netconf": "vmware",
},
} }
) )
func main() { func main() {
failure := false failure := false
// Conservative Go 1.5 upgrade strategy:
// keep GOMAXPROCS' default at 1 for now.
if os.Getenv("GOMAXPROCS") == "" {
runtime.GOMAXPROCS(1)
}
flag.Parse() flag.Parse()
if c, ok := oemConfigs[flags.oem]; ok { if c, ok := oemConfigs[flags.oem]; ok {
@ -126,12 +150,12 @@ func main() {
for k := range oemConfigs { for k := range oemConfigs {
oems = append(oems, k) oems = append(oems, k)
} }
fmt.Printf("Invalid option to --oem: %q. Supported options: %q\n", flags.oem, oems) fmt.Printf("Invalid option to -oem: %q. Supported options: %q\n", flags.oem, oems)
os.Exit(2) os.Exit(2)
} }
if flags.printVersion == true { if flags.printVersion == true {
fmt.Printf("coreos-cloudinit version %s\n", version) fmt.Printf("coreos-cloudinit %s\n", version)
os.Exit(0) os.Exit(0)
} }
@ -139,50 +163,57 @@ func main() {
case "": case "":
case "debian": case "debian":
case "digitalocean": case "digitalocean":
case "packet":
case "vmware":
default: default:
fmt.Printf("Invalid option to -convert-netconf: '%s'. Supported options: 'debian, digitalocean'\n", flags.convertNetconf) fmt.Printf("Invalid option to -convert-netconf: '%s'. Supported options: 'debian, digitalocean, packet, vmware'\n", flags.convertNetconf)
os.Exit(2) os.Exit(2)
} }
dss := getDatasources() dss := getDatasources()
if len(dss) == 0 { if len(dss) == 0 {
fmt.Println("Provide at least one of --from-file, --from-configdrive, --from-ec2-metadata, --from-cloudsigma-metadata, --from-url or --from-proc-cmdline") fmt.Println("Provide at least one of --from-file, --from-configdrive, --from-ec2-metadata, --from-cloudsigma-metadata, --from-packet-metadata, --from-digitalocean-metadata, --from-vmware-guestinfo, --from-waagent, --from-url or --from-proc-cmdline")
os.Exit(2) os.Exit(2)
} }
ds := selectDatasource(dss) ds := selectDatasource(dss)
if ds == nil { if ds == nil {
fmt.Println("No datasources available in time") log.Println("No datasources available in time")
os.Exit(1) os.Exit(1)
} }
fmt.Printf("Fetching user-data from datasource of type %q\n", ds.Type()) log.Printf("Fetching user-data from datasource of type %q\n", ds.Type())
userdataBytes, err := ds.FetchUserdata() userdataBytes, err := ds.FetchUserdata()
if err != nil { if err != nil {
fmt.Printf("Failed fetching user-data from datasource: %v\nContinuing...\n", err) log.Printf("Failed fetching user-data from datasource: %v. Continuing...\n", err)
failure = true
}
userdataBytes, err = decompressIfGzip(userdataBytes)
if err != nil {
log.Printf("Failed decompressing user-data from datasource: %v. Continuing...\n", err)
failure = true failure = true
} }
if report, err := validate.Validate(userdataBytes); err == nil { if report, err := validate.Validate(userdataBytes); err == nil {
ret := 0 ret := 0
for _, e := range report.Entries() { for _, e := range report.Entries() {
fmt.Println(e) log.Println(e)
ret = 1 ret = 1
} }
if flags.validate { if flags.validate {
os.Exit(ret) os.Exit(ret)
} }
} else { } else {
fmt.Printf("Failed while validating user_data (%q)\n", err) log.Printf("Failed while validating user_data (%q)\n", err)
if flags.validate { if flags.validate {
os.Exit(1) os.Exit(1)
} }
} }
fmt.Printf("Fetching meta-data from datasource of type %q\n", ds.Type()) log.Printf("Fetching meta-data from datasource of type %q\n", ds.Type())
metadata, err := ds.FetchMetadata() metadata, err := ds.FetchMetadata()
if err != nil { if err != nil {
fmt.Printf("Failed fetching meta-data from datasource: %v\n", err) log.Printf("Failed fetching meta-data from datasource: %v\n", err)
os.Exit(1) os.Exit(1)
} }
@ -192,19 +223,23 @@ func main() {
var ccu *config.CloudConfig var ccu *config.CloudConfig
var script *config.Script var script *config.Script
if ud, err := initialize.ParseUserData(userdata); err != nil { switch ud, err := initialize.ParseUserData(userdata); err {
fmt.Printf("Failed to parse user-data: %v\nContinuing...\n", err) case initialize.ErrIgnitionConfig:
failure = true fmt.Printf("Detected an Ignition config. Exiting...")
} else { os.Exit(0)
case nil:
switch t := ud.(type) { switch t := ud.(type) {
case *config.CloudConfig: case *config.CloudConfig:
ccu = t ccu = t
case *config.Script: case *config.Script:
script = t script = t
} }
default:
fmt.Printf("Failed to parse user-data: %v\nContinuing...\n", err)
failure = true
} }
fmt.Println("Merging cloud-config from meta-data and user-data") log.Println("Merging cloud-config from meta-data and user-data")
cc := mergeConfigs(ccu, metadata) cc := mergeConfigs(ccu, metadata)
var ifaces []network.InterfaceGenerator var ifaces []network.InterfaceGenerator
@ -212,26 +247,30 @@ func main() {
var err error var err error
switch flags.convertNetconf { switch flags.convertNetconf {
case "debian": case "debian":
ifaces, err = network.ProcessDebianNetconf(metadata.NetworkConfig) ifaces, err = network.ProcessDebianNetconf(metadata.NetworkConfig.([]byte))
case "digitalocean": case "digitalocean":
ifaces, err = network.ProcessDigitalOceanNetconf(metadata.NetworkConfig) ifaces, err = network.ProcessDigitalOceanNetconf(metadata.NetworkConfig.(digitalocean.Metadata))
case "packet":
ifaces, err = network.ProcessPacketNetconf(metadata.NetworkConfig.(packet.NetworkData))
case "vmware":
ifaces, err = network.ProcessVMwareNetconf(metadata.NetworkConfig.(map[string]string))
default: default:
err = fmt.Errorf("Unsupported network config format %q", flags.convertNetconf) err = fmt.Errorf("Unsupported network config format %q", flags.convertNetconf)
} }
if err != nil { if err != nil {
fmt.Printf("Failed to generate interfaces: %v\n", err) log.Printf("Failed to generate interfaces: %v\n", err)
os.Exit(1) os.Exit(1)
} }
} }
if err = initialize.Apply(cc, ifaces, env); err != nil { if err = initialize.Apply(cc, ifaces, env); err != nil {
fmt.Printf("Failed to apply cloud-config: %v\n", err) log.Printf("Failed to apply cloud-config: %v\n", err)
os.Exit(1) os.Exit(1)
} }
if script != nil { if script != nil {
if err = runScript(*script, env); err != nil { if err = runScript(*script, env); err != nil {
fmt.Printf("Failed to run script: %v\n", err) log.Printf("Failed to run script: %v\n", err)
os.Exit(1) os.Exit(1)
} }
} }
@ -251,7 +290,7 @@ func mergeConfigs(cc *config.CloudConfig, md datasource.Metadata) (out config.Cl
if md.Hostname != "" { if md.Hostname != "" {
if out.Hostname != "" { if out.Hostname != "" {
fmt.Printf("Warning: user-data hostname (%s) overrides metadata hostname (%s)\n", out.Hostname, md.Hostname) log.Printf("Warning: user-data hostname (%s) overrides metadata hostname (%s)\n", out.Hostname, md.Hostname)
} else { } else {
out.Hostname = md.Hostname out.Hostname = md.Hostname
} }
@ -290,9 +329,15 @@ func getDatasources() []datasource.Datasource {
if flags.sources.waagent != "" { if flags.sources.waagent != "" {
dss = append(dss, waagent.NewDatasource(flags.sources.waagent)) dss = append(dss, waagent.NewDatasource(flags.sources.waagent))
} }
if flags.sources.packetMetadataService != "" {
dss = append(dss, packet.NewDatasource(flags.sources.packetMetadataService))
}
if flags.sources.procCmdLine { if flags.sources.procCmdLine {
dss = append(dss, proc_cmdline.NewDatasource()) dss = append(dss, proc_cmdline.NewDatasource())
} }
if flags.sources.vmware {
dss = append(dss, vmware.NewDatasource())
}
return dss return dss
} }
@ -313,7 +358,7 @@ func selectDatasource(sources []datasource.Datasource) datasource.Datasource {
duration := datasourceInterval duration := datasourceInterval
for { for {
fmt.Printf("Checking availability of %q\n", s.Type()) log.Printf("Checking availability of %q\n", s.Type())
if s.IsAvailable() { if s.IsAvailable() {
ds <- s ds <- s
return return
@ -351,7 +396,7 @@ func selectDatasource(sources []datasource.Datasource) datasource.Datasource {
func runScript(script config.Script, env *initialize.Environment) error { func runScript(script config.Script, env *initialize.Environment) error {
err := initialize.PrepWorkspace(env.Workspace()) err := initialize.PrepWorkspace(env.Workspace())
if err != nil { if err != nil {
fmt.Printf("Failed preparing workspace: %v\n", err) log.Printf("Failed preparing workspace: %v\n", err)
return err return err
} }
path, err := initialize.PersistScriptInWorkspace(script, env.Workspace()) path, err := initialize.PersistScriptInWorkspace(script, env.Workspace())
@ -362,3 +407,17 @@ func runScript(script config.Script, env *initialize.Environment) error {
} }
return err return err
} }
const gzipMagicBytes = "\x1f\x8b"
func decompressIfGzip(userdataBytes []byte) ([]byte, error) {
if !bytes.HasPrefix(userdataBytes, []byte(gzipMagicBytes)) {
return userdataBytes, nil
}
gzr, err := gzip.NewReader(bytes.NewReader(userdataBytes))
if err != nil {
return nil, err
}
defer gzr.Close()
return ioutil.ReadAll(gzr)
}

View File

@ -15,6 +15,9 @@
package main package main
import ( import (
"bytes"
"encoding/base64"
"errors"
"reflect" "reflect"
"testing" "testing"
@ -87,3 +90,58 @@ func TestMergeConfigs(t *testing.T) {
} }
} }
} }
func mustDecode(in string) []byte {
out, err := base64.StdEncoding.DecodeString(in)
if err != nil {
panic(err)
}
return out
}
func TestDecompressIfGzip(t *testing.T) {
tests := []struct {
in []byte
out []byte
err error
}{
{
in: nil,
out: nil,
err: nil,
},
{
in: []byte{},
out: []byte{},
err: nil,
},
{
in: mustDecode("H4sIAJWV/VUAA1NOzskvTdFNzs9Ly0wHABt6mQENAAAA"),
out: []byte("#cloud-config"),
err: nil,
},
{
in: []byte("#cloud-config"),
out: []byte("#cloud-config"),
err: nil,
},
{
in: mustDecode("H4sCORRUPT=="),
out: nil,
err: errors.New("any error"),
},
}
for i, tt := range tests {
out, err := decompressIfGzip(tt.in)
if !bytes.Equal(out, tt.out) || (tt.err != nil && err == nil) {
t.Errorf("bad gzip (%d): want (%s, %#v), got (%s, %#v)", i, string(tt.out), tt.err, string(out), err)
}
}
}

View File

@ -16,8 +16,8 @@ package configdrive
import ( import (
"encoding/json" "encoding/json"
"fmt"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"path" "path"
@ -93,7 +93,7 @@ func (cd *configDrive) openstackVersionRoot() string {
} }
func (cd *configDrive) tryReadFile(filename string) ([]byte, error) { func (cd *configDrive) tryReadFile(filename string) ([]byte, error) {
fmt.Printf("Attempting to read from %q\n", filename) log.Printf("Attempting to read from %q\n", filename)
data, err := cd.readFile(filename) data, err := cd.readFile(filename)
if os.IsNotExist(err) { if os.IsNotExist(err) {
err = nil err = nil

View File

@ -34,5 +34,5 @@ type Metadata struct {
PrivateIPv6 net.IP PrivateIPv6 net.IP
Hostname string Hostname string
SSHPublicKeys map[string]string SSHPublicKeys map[string]string
NetworkConfig []byte NetworkConfig interface{}
} }

View File

@ -40,6 +40,7 @@ type Address struct {
type Interface struct { type Interface struct {
IPv4 *Address `json:"ipv4"` IPv4 *Address `json:"ipv4"`
IPv6 *Address `json:"ipv6"` IPv6 *Address `json:"ipv6"`
AnchorIPv4 *Address `json:"anchor_ipv4"`
MAC string `json:"mac"` MAC string `json:"mac"`
Type string `json:"type"` Type string `json:"type"`
} }
@ -100,7 +101,7 @@ func (ms *metadataService) FetchMetadata() (metadata datasource.Metadata, err er
for i, key := range m.PublicKeys { for i, key := range m.PublicKeys {
metadata.SSHPublicKeys[strconv.Itoa(i)] = key metadata.SSHPublicKeys[strconv.Itoa(i)] = key
} }
metadata.NetworkConfig = data metadata.NetworkConfig = m
return return
} }

View File

@ -90,34 +90,27 @@ func TestFetchMetadata(t *testing.T) {
"0": "publickey1", "0": "publickey1",
"1": "publickey2", "1": "publickey2",
}, },
NetworkConfig: []byte(`{ NetworkConfig: Metadata{
"droplet_id": 1, Interfaces: Interfaces{
"user_data": "hello", Public: []Interface{
"vendor_data": "hello", Interface{
"public_keys": [ IPv4: &Address{
"publickey1", IPAddress: "192.168.1.2",
"publickey2" Netmask: "255.255.255.0",
], Gateway: "192.168.1.1",
"region": "nyc2",
"interfaces": {
"public": [
{
"ipv4": {
"ip_address": "192.168.1.2",
"netmask": "255.255.255.0",
"gateway": "192.168.1.1"
}, },
"ipv6": { IPv6: &Address{
"ip_address": "fe00::", IPAddress: "fe00::",
"cidr": 126, Cidr: 126,
"gateway": "fe00::" Gateway: "fe00::",
},
MAC: "ab:cd:ef:gh:ij",
Type: "public",
},
},
},
PublicKeys: []string{"publickey1", "publickey2"},
}, },
"mac": "ab:cd:ef:gh:ij",
"type": "public"
}
]
}
}`),
}, },
}, },
{ {

View File

@ -18,6 +18,7 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"fmt" "fmt"
"log"
"net" "net"
"strings" "strings"
@ -61,7 +62,7 @@ func (ms metadataService) FetchMetadata() (datasource.Metadata, error) {
return metadata, err return metadata, err
} }
metadata.SSHPublicKeys[name] = sshkey metadata.SSHPublicKeys[name] = sshkey
fmt.Printf("Found SSH key for %q\n", name) log.Printf("Found SSH key for %q\n", name)
} }
} else if _, ok := err.(pkg.ErrNotFound); !ok { } else if _, ok := err.(pkg.ErrNotFound); !ok {
return metadata, err return metadata, err

View File

@ -0,0 +1,106 @@
// 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 packet
import (
"encoding/json"
"net"
"strconv"
"github.com/coreos/coreos-cloudinit/datasource"
"github.com/coreos/coreos-cloudinit/datasource/metadata"
)
const (
DefaultAddress = "https://metadata.packet.net/"
apiVersion = ""
userdataUrl = "userdata"
metadataPath = "metadata"
)
type Netblock struct {
Address net.IP `json:"address"`
Cidr int `json:"cidr"`
Netmask net.IP `json:"netmask"`
Gateway net.IP `json:"gateway"`
AddressFamily int `json:"address_family"`
Public bool `json:"public"`
}
type Nic struct {
Name string `json:"name"`
Mac string `json:"mac"`
}
type NetworkData struct {
Interfaces []Nic `json:"interfaces"`
Netblocks []Netblock `json:"addresses"`
DNS []net.IP `json:"dns"`
}
// Metadata that will be pulled from the https://metadata.packet.net/metadata only. We have the opportunity to add more later.
type Metadata struct {
Hostname string `json:"hostname"`
SSHKeys []string `json:"ssh_keys"`
NetworkData NetworkData `json:"network"`
}
type metadataService struct {
metadata.MetadataService
}
func NewDatasource(root string) *metadataService {
return &metadataService{MetadataService: metadata.NewDatasource(root, apiVersion, userdataUrl, metadataPath)}
}
func (ms *metadataService) FetchMetadata() (metadata datasource.Metadata, err error) {
var data []byte
var m Metadata
if data, err = ms.FetchData(ms.MetadataUrl()); err != nil || len(data) == 0 {
return
}
if err = json.Unmarshal(data, &m); err != nil {
return
}
if len(m.NetworkData.Netblocks) > 0 {
for _, Netblock := range m.NetworkData.Netblocks {
if Netblock.AddressFamily == 4 {
if Netblock.Public == true {
metadata.PublicIPv4 = Netblock.Address
} else {
metadata.PrivateIPv4 = Netblock.Address
}
} else {
metadata.PublicIPv6 = Netblock.Address
}
}
}
metadata.Hostname = m.Hostname
metadata.SSHPublicKeys = map[string]string{}
for i, key := range m.SSHKeys {
metadata.SSHPublicKeys[strconv.Itoa(i)] = key
}
metadata.NetworkConfig = m.NetworkData
return
}
func (ms metadataService) Type() string {
return "packet-metadata-service"
}

View File

@ -135,6 +135,7 @@ func Apply(cfg config.CloudConfig, ifaces []network.InterfaceGenerator, env *Env
for _, ccu := range []CloudConfigUnit{ for _, ccu := range []CloudConfigUnit{
system.Etcd{Etcd: cfg.CoreOS.Etcd}, system.Etcd{Etcd: cfg.CoreOS.Etcd},
system.Etcd2{Etcd2: cfg.CoreOS.Etcd2},
system.Fleet{Fleet: cfg.CoreOS.Fleet}, system.Fleet{Fleet: cfg.CoreOS.Fleet},
system.Locksmith{Locksmith: cfg.CoreOS.Locksmith}, system.Locksmith{Locksmith: cfg.CoreOS.Locksmith},
system.Update{Update: cfg.CoreOS.Update, ReadConfig: system.DefaultReadConfig}, system.Update{Update: cfg.CoreOS.Update, ReadConfig: system.DefaultReadConfig},

View File

@ -21,6 +21,10 @@ import (
"github.com/coreos/coreos-cloudinit/config" "github.com/coreos/coreos-cloudinit/config"
) )
var (
ErrIgnitionConfig = errors.New("not a config (found Ignition)")
)
func ParseUserData(contents string) (interface{}, error) { func ParseUserData(contents string) (interface{}, error) {
if len(contents) == 0 { if len(contents) == 0 {
return nil, nil return nil, nil
@ -33,6 +37,8 @@ func ParseUserData(contents string) (interface{}, error) {
case config.IsCloudConfig(contents): case config.IsCloudConfig(contents):
log.Printf("Parsing user-data as cloud-config") log.Printf("Parsing user-data as cloud-config")
return config.NewCloudConfig(contents) return config.NewCloudConfig(contents)
case config.IsIgnitionConfig(contents):
return nil, ErrIgnitionConfig
default: default:
return nil, errors.New("Unrecognized user-data format") return nil, errors.New("Unrecognized user-data format")
} }

View File

@ -47,7 +47,7 @@ func TestParseHeaderCRLF(t *testing.T) {
} }
func TestParseConfigCRLF(t *testing.T) { func TestParseConfigCRLF(t *testing.T) {
contents := "#cloud-config\r\nhostname: foo\r\nssh_authorized_keys:\r\n - foobar\r\n" contents := "#cloud-config \r\nhostname: foo\r\nssh_authorized_keys:\r\n - foobar\r\n"
ud, err := ParseUserData(contents) ud, err := ParseUserData(contents)
if err != nil { if err != nil {
t.Fatalf("Failed parsing config: %v", err) t.Fatalf("Failed parsing config: %v", err)

View File

@ -15,7 +15,6 @@
package network package network
import ( import (
"encoding/json"
"fmt" "fmt"
"log" "log"
"net" "net"
@ -23,26 +22,18 @@ import (
"github.com/coreos/coreos-cloudinit/datasource/metadata/digitalocean" "github.com/coreos/coreos-cloudinit/datasource/metadata/digitalocean"
) )
func ProcessDigitalOceanNetconf(config []byte) ([]InterfaceGenerator, error) { func ProcessDigitalOceanNetconf(config digitalocean.Metadata) ([]InterfaceGenerator, error) {
log.Println("Processing DigitalOcean network config") log.Println("Processing DigitalOcean network config")
if len(config) == 0 {
return nil, nil
}
var cfg digitalocean.Metadata
if err := json.Unmarshal(config, &cfg); err != nil {
return nil, err
}
log.Println("Parsing nameservers") log.Println("Parsing nameservers")
nameservers, err := parseNameservers(cfg.DNS) nameservers, err := parseNameservers(config.DNS)
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Printf("Parsed %d nameservers\n", len(nameservers)) log.Printf("Parsed %d nameservers\n", len(nameservers))
log.Println("Parsing interfaces") log.Println("Parsing interfaces")
generators, err := parseInterfaces(cfg.Interfaces, nameservers) generators, err := parseInterfaces(config.Interfaces, nameservers)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -52,9 +43,9 @@ func ProcessDigitalOceanNetconf(config []byte) ([]InterfaceGenerator, error) {
return generators, nil return generators, nil
} }
func parseNameservers(cfg digitalocean.DNS) ([]net.IP, error) { func parseNameservers(config digitalocean.DNS) ([]net.IP, error) {
nameservers := make([]net.IP, 0, len(cfg.Nameservers)) nameservers := make([]net.IP, 0, len(config.Nameservers))
for _, ns := range cfg.Nameservers { for _, ns := range config.Nameservers {
if ip := net.ParseIP(ns); ip == nil { if ip := net.ParseIP(ns); ip == nil {
return nil, fmt.Errorf("could not parse %q as nameserver IP address", ns) return nil, fmt.Errorf("could not parse %q as nameserver IP address", ns)
} else { } else {
@ -64,16 +55,16 @@ func parseNameservers(cfg digitalocean.DNS) ([]net.IP, error) {
return nameservers, nil return nameservers, nil
} }
func parseInterfaces(cfg digitalocean.Interfaces, nameservers []net.IP) ([]InterfaceGenerator, error) { func parseInterfaces(config digitalocean.Interfaces, nameservers []net.IP) ([]InterfaceGenerator, error) {
generators := make([]InterfaceGenerator, 0, len(cfg.Public)+len(cfg.Private)) generators := make([]InterfaceGenerator, 0, len(config.Public)+len(config.Private))
for _, iface := range cfg.Public { for _, iface := range config.Public {
if generator, err := parseInterface(iface, nameservers, true); err == nil { if generator, err := parseInterface(iface, nameservers, true); err == nil {
generators = append(generators, &physicalInterface{*generator}) generators = append(generators, &physicalInterface{*generator})
} else { } else {
return nil, err return nil, err
} }
} }
for _, iface := range cfg.Private { for _, iface := range config.Private {
if generator, err := parseInterface(iface, []net.IP{}, false); err == nil { if generator, err := parseInterface(iface, []net.IP{}, false); err == nil {
generators = append(generators, &physicalInterface{*generator}) generators = append(generators, &physicalInterface{*generator})
} else { } else {
@ -135,6 +126,28 @@ func parseInterface(iface digitalocean.Interface, nameservers []net.IP, useRoute
}) })
} }
} }
if iface.AnchorIPv4 != nil {
var ip, mask net.IP
if ip = net.ParseIP(iface.AnchorIPv4.IPAddress); ip == nil {
return nil, fmt.Errorf("could not parse %q as anchor IPv4 address", iface.AnchorIPv4.IPAddress)
}
if mask = net.ParseIP(iface.AnchorIPv4.Netmask); mask == nil {
return nil, fmt.Errorf("could not parse %q as anchor IPv4 mask", iface.AnchorIPv4.Netmask)
}
addresses = append(addresses, net.IPNet{
IP: ip,
Mask: net.IPMask(mask),
})
if useRoute {
routes = append(routes, route{
destination: net.IPNet{
IP: net.IPv4zero,
Mask: net.IPMask(net.IPv4zero),
},
})
}
}
hwaddr, err := net.ParseMAC(iface.MAC) hwaddr, err := net.ParseMAC(iface.MAC)
if err != nil { if err != nil {

View File

@ -52,6 +52,14 @@ func TestParseNameservers(t *testing.T) {
} }
} }
func mkInvalidMAC() error {
if isGo15 {
return &net.AddrError{Err: "invalid MAC address", Addr: "bad"}
} else {
return errors.New("invalid MAC address: bad")
}
}
func TestParseInterface(t *testing.T) { func TestParseInterface(t *testing.T) {
for _, tt := range []struct { for _, tt := range []struct {
cfg digitalocean.Interface cfg digitalocean.Interface
@ -64,7 +72,7 @@ func TestParseInterface(t *testing.T) {
cfg: digitalocean.Interface{ cfg: digitalocean.Interface{
MAC: "bad", MAC: "bad",
}, },
err: errors.New("invalid MAC address: bad"), err: mkInvalidMAC(),
}, },
{ {
cfg: digitalocean.Interface{ cfg: digitalocean.Interface{
@ -250,6 +258,70 @@ func TestParseInterface(t *testing.T) {
}, },
}, },
}, },
{
cfg: digitalocean.Interface{
MAC: "01:23:45:67:89:AB",
AnchorIPv4: &digitalocean.Address{
IPAddress: "bad",
Netmask: "255.255.0.0",
},
},
nss: []net.IP{},
err: errors.New("could not parse \"bad\" as anchor IPv4 address"),
},
{
cfg: digitalocean.Interface{
MAC: "01:23:45:67:89:AB",
AnchorIPv4: &digitalocean.Address{
IPAddress: "1.2.3.4",
Netmask: "bad",
},
},
nss: []net.IP{},
err: errors.New("could not parse \"bad\" as anchor IPv4 mask"),
},
{
cfg: digitalocean.Interface{
MAC: "01:23:45:67:89:AB",
IPv4: &digitalocean.Address{
IPAddress: "1.2.3.4",
Netmask: "255.255.0.0",
Gateway: "5.6.7.8",
},
AnchorIPv4: &digitalocean.Address{
IPAddress: "7.8.9.10",
Netmask: "255.255.0.0",
},
},
useRoute: true,
nss: []net.IP{},
iface: &logicalInterface{
hwaddr: net.HardwareAddr([]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab}),
config: configMethodStatic{
addresses: []net.IPNet{
{
IP: net.ParseIP("1.2.3.4"),
Mask: net.IPMask(net.ParseIP("255.255.0.0")),
},
{
IP: net.ParseIP("7.8.9.10"),
Mask: net.IPMask(net.ParseIP("255.255.0.0")),
},
},
nameservers: []net.IP{},
routes: []route{
{
destination: net.IPNet{IP: net.IPv4zero, Mask: net.IPMask(net.IPv4zero)},
gateway: net.ParseIP("5.6.7.8"),
},
{
destination: net.IPNet{IP: net.IPv4zero, Mask: net.IPMask(net.IPv4zero)},
},
},
},
},
},
} { } {
iface, err := parseInterface(tt.cfg, tt.nss, tt.useRoute) iface, err := parseInterface(tt.cfg, tt.nss, tt.useRoute)
if !errorsEqual(tt.err, err) { if !errorsEqual(tt.err, err) {
@ -337,13 +409,13 @@ func TestParseInterfaces(t *testing.T) {
cfg: digitalocean.Interfaces{ cfg: digitalocean.Interfaces{
Public: []digitalocean.Interface{{MAC: "bad"}}, Public: []digitalocean.Interface{{MAC: "bad"}},
}, },
err: errors.New("invalid MAC address: bad"), err: mkInvalidMAC(),
}, },
{ {
cfg: digitalocean.Interfaces{ cfg: digitalocean.Interfaces{
Private: []digitalocean.Interface{{MAC: "bad"}}, Private: []digitalocean.Interface{{MAC: "bad"}},
}, },
err: errors.New("invalid MAC address: bad"), err: mkInvalidMAC(),
}, },
} { } {
ifaces, err := parseInterfaces(tt.cfg, tt.nss) ifaces, err := parseInterfaces(tt.cfg, tt.nss)
@ -358,27 +430,37 @@ func TestParseInterfaces(t *testing.T) {
func TestProcessDigitalOceanNetconf(t *testing.T) { func TestProcessDigitalOceanNetconf(t *testing.T) {
for _, tt := range []struct { for _, tt := range []struct {
cfg string cfg digitalocean.Metadata
ifaces []InterfaceGenerator ifaces []InterfaceGenerator
err error err error
}{ }{
{ {
cfg: ``, cfg: digitalocean.Metadata{
DNS: digitalocean.DNS{
Nameservers: []string{"bad"},
},
}, },
{
cfg: `{"dns":{"nameservers":["bad"]}}`,
err: errors.New("could not parse \"bad\" as nameserver IP address"), err: errors.New("could not parse \"bad\" as nameserver IP address"),
}, },
{ {
cfg: `{"interfaces":{"public":[{"ipv4":{"ip_address":"bad"}}]}}`, cfg: digitalocean.Metadata{
Interfaces: digitalocean.Interfaces{
Public: []digitalocean.Interface{
digitalocean.Interface{
IPv4: &digitalocean.Address{
IPAddress: "bad",
},
},
},
},
},
err: errors.New("could not parse \"bad\" as IPv4 address"), err: errors.New("could not parse \"bad\" as IPv4 address"),
}, },
{ {
cfg: `{}`,
ifaces: []InterfaceGenerator{}, ifaces: []InterfaceGenerator{},
}, },
} { } {
ifaces, err := ProcessDigitalOceanNetconf([]byte(tt.cfg)) ifaces, err := ProcessDigitalOceanNetconf(tt.cfg)
if !errorsEqual(tt.err, err) { if !errorsEqual(tt.err, err) {
t.Fatalf("bad error (%q): want %q, got %q", tt.cfg, tt.err, err) t.Fatalf("bad error (%q): want %q, got %q", tt.cfg, tt.err, err)
} }

View File

@ -130,7 +130,17 @@ type bondInterface struct {
} }
func (b *bondInterface) Netdev() string { func (b *bondInterface) Netdev() string {
return fmt.Sprintf("[NetDev]\nKind=bond\nName=%s\n", b.name) config := fmt.Sprintf("[NetDev]\nKind=bond\nName=%s\n", b.name)
if b.hwaddr != nil {
config += fmt.Sprintf("MACAddress=%s\n", b.hwaddr.String())
}
config += fmt.Sprintf("\n[Bond]\n")
for _, name := range sortedKeys(b.options) {
config += fmt.Sprintf("%s=%s\n", name, b.options[name])
}
return config
} }
func (b *bondInterface) Type() string { func (b *bondInterface) Type() string {

View File

@ -52,7 +52,7 @@ func TestInterfaceGenerators(t *testing.T) {
}, },
{ {
name: "testname", name: "testname",
netdev: "[NetDev]\nKind=bond\nName=testname\n", netdev: "[NetDev]\nKind=bond\nName=testname\n\n[Bond]\n",
network: "[Match]\nName=testname\n\n[Network]\nBond=testbond1\nVLAN=testvlan1\nVLAN=testvlan2\nDHCP=true\n", network: "[Match]\nName=testname\n\n[Network]\nBond=testbond1\nVLAN=testvlan1\nVLAN=testvlan2\nDHCP=true\n",
kind: "bond", kind: "bond",
iface: &bondInterface{logicalInterface: logicalInterface{ iface: &bondInterface{logicalInterface: logicalInterface{

View File

@ -0,0 +1,5 @@
// +build !go1.5
package network
const isGo15 = false

View File

@ -0,0 +1,5 @@
// +build go1.5
package network
const isGo15 = true

View 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 network
import (
"net"
"github.com/coreos/coreos-cloudinit/datasource/metadata/packet"
)
func ProcessPacketNetconf(netdata packet.NetworkData) ([]InterfaceGenerator, error) {
var nameservers []net.IP
if netdata.DNS != nil {
nameservers = netdata.DNS
} else {
nameservers = append(nameservers, net.ParseIP("8.8.8.8"), net.ParseIP("8.8.4.4"))
}
generators, err := parseNetwork(netdata, nameservers)
if err != nil {
return nil, err
}
return generators, nil
}
func parseNetwork(netdata packet.NetworkData, nameservers []net.IP) ([]InterfaceGenerator, error) {
var interfaces []InterfaceGenerator
var addresses []net.IPNet
var routes []route
for _, netblock := range netdata.Netblocks {
addresses = append(addresses, net.IPNet{
IP: netblock.Address,
Mask: net.IPMask(netblock.Netmask),
})
if netblock.Public == false {
routes = append(routes, route{
destination: net.IPNet{
IP: net.IPv4(10, 0, 0, 0),
Mask: net.IPv4Mask(255, 0, 0, 0),
},
gateway: netblock.Gateway,
})
} else {
if netblock.AddressFamily == 4 {
routes = append(routes, route{
destination: net.IPNet{
IP: net.IPv4zero,
Mask: net.IPMask(net.IPv4zero),
},
gateway: netblock.Gateway,
})
} else {
routes = append(routes, route{
destination: net.IPNet{
IP: net.IPv6zero,
Mask: net.IPMask(net.IPv6zero),
},
gateway: netblock.Gateway,
})
}
}
}
bond := bondInterface{
logicalInterface: logicalInterface{
name: "bond0",
config: configMethodStatic{
addresses: addresses,
nameservers: nameservers,
routes: routes,
},
},
options: map[string]string{
"Mode": "802.3ad",
"LACPTransmitRate": "fast",
"MIIMonitorSec": ".2",
"UpDelaySec": ".2",
"DownDelaySec": ".2",
},
}
bond.hwaddr, _ = net.ParseMAC(netdata.Interfaces[0].Mac)
for index, iface := range netdata.Interfaces {
bond.slaves = append(bond.slaves, iface.Name)
interfaces = append(interfaces, &physicalInterface{
logicalInterface: logicalInterface{
name: iface.Name,
config: configMethodStatic{
nameservers: nameservers,
},
children: []networkInterface{&bond},
configDepth: index,
},
})
}
interfaces = append(interfaces, &bond)
return interfaces, nil
}

View File

@ -0,0 +1,174 @@
// 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 network
import (
"fmt"
"log"
"net"
)
func ProcessVMwareNetconf(config map[string]string) ([]InterfaceGenerator, error) {
log.Println("Processing VMware network config")
log.Println("Parsing nameservers")
var nameservers []net.IP
for i := 0; ; i++ {
if ipStr, ok := config[fmt.Sprintf("dns.server.%d", i)]; ok {
if ip := net.ParseIP(ipStr); ip != nil {
nameservers = append(nameservers, ip)
} else {
return nil, fmt.Errorf("invalid nameserver: %q", ipStr)
}
} else {
break
}
}
log.Printf("Parsed %d nameservers", len(nameservers))
var interfaces []InterfaceGenerator
for i := 0; ; i++ {
var addresses []net.IPNet
var routes []route
var err error
var dhcp bool
iface := &physicalInterface{}
log.Printf("Proccessing interface %d", i)
log.Println("Processing DHCP")
if dhcp, err = processDHCPConfig(config, fmt.Sprintf("interface.%d.", i)); err != nil {
return nil, err
}
log.Println("Processing addresses")
if as, err := processAddressConfig(config, fmt.Sprintf("interface.%d.", i)); err == nil {
addresses = append(addresses, as...)
} else {
return nil, err
}
log.Println("Processing routes")
if rs, err := processRouteConfig(config, fmt.Sprintf("interface.%d.", i)); err == nil {
routes = append(routes, rs...)
} else {
return nil, err
}
if mac, ok := config[fmt.Sprintf("interface.%d.mac", i)]; ok {
log.Printf("Parsing interface %d MAC address: %q", i, mac)
if hwaddr, err := net.ParseMAC(mac); err == nil {
iface.hwaddr = hwaddr
} else {
return nil, fmt.Errorf("error while parsing MAC address: %v", err)
}
}
if name, ok := config[fmt.Sprintf("interface.%d.name", i)]; ok {
log.Printf("Parsing interface %d name: %q", i, name)
iface.name = name
}
if len(addresses) > 0 || len(routes) > 0 {
iface.config = configMethodStatic{
hwaddress: iface.hwaddr,
addresses: addresses,
nameservers: nameservers,
routes: routes,
}
} else if dhcp {
iface.config = configMethodDHCP{
hwaddress: iface.hwaddr,
}
} else {
break
}
interfaces = append(interfaces, iface)
}
return interfaces, nil
}
func processAddressConfig(config map[string]string, prefix string) (addresses []net.IPNet, err error) {
for a := 0; ; a++ {
prefix := fmt.Sprintf("%sip.%d.", prefix, a)
addressStr, ok := config[prefix+"address"]
if !ok {
break
}
ip, network, err := net.ParseCIDR(addressStr)
if err != nil {
return nil, fmt.Errorf("invalid address: %q", addressStr)
}
addresses = append(addresses, net.IPNet{
IP: ip,
Mask: network.Mask,
})
}
return
}
func processRouteConfig(config map[string]string, prefix string) (routes []route, err error) {
for r := 0; ; r++ {
prefix := fmt.Sprintf("%sroute.%d.", prefix, r)
gatewayStr, gok := config[prefix+"gateway"]
destinationStr, dok := config[prefix+"destination"]
if gok && !dok {
return nil, fmt.Errorf("missing destination key")
} else if !gok && dok {
return nil, fmt.Errorf("missing gateway key")
} else if !gok && !dok {
break
}
gateway := net.ParseIP(gatewayStr)
if gateway == nil {
return nil, fmt.Errorf("invalid gateway: %q", gatewayStr)
}
_, destination, err := net.ParseCIDR(destinationStr)
if err != nil {
return nil, err
}
routes = append(routes, route{
destination: *destination,
gateway: gateway,
})
}
return
}
func processDHCPConfig(config map[string]string, prefix string) (dhcp bool, err error) {
dhcpStr, ok := config[prefix+"dhcp"]
if !ok {
return false, nil
}
switch dhcpStr {
case "yes":
return true, nil
case "no":
return false, nil
default:
return false, fmt.Errorf("invalid DHCP option: %q", dhcpStr)
}
}

View File

@ -0,0 +1,361 @@
// 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 network
import (
"errors"
"net"
"reflect"
"testing"
)
func mustParseMac(mac net.HardwareAddr, err error) net.HardwareAddr {
if err != nil {
panic(err)
}
return mac
}
func TestProcessVMwareNetconf(t *testing.T) {
tests := []struct {
config map[string]string
interfaces []InterfaceGenerator
err error
}{
{},
{
config: map[string]string{
"interface.0.dhcp": "yes",
},
interfaces: []InterfaceGenerator{
&physicalInterface{logicalInterface{
config: configMethodDHCP{},
}},
},
},
{
config: map[string]string{
"interface.0.mac": "00:11:22:33:44:55",
"interface.0.dhcp": "yes",
},
interfaces: []InterfaceGenerator{
&physicalInterface{logicalInterface{
hwaddr: mustParseMac(net.ParseMAC("00:11:22:33:44:55")),
config: configMethodDHCP{hwaddress: mustParseMac(net.ParseMAC("00:11:22:33:44:55"))},
}},
},
},
{
config: map[string]string{
"interface.0.name": "eth0",
"interface.0.dhcp": "yes",
},
interfaces: []InterfaceGenerator{
&physicalInterface{logicalInterface{
name: "eth0",
config: configMethodDHCP{},
}},
},
},
{
config: map[string]string{
"interface.0.mac": "00:11:22:33:44:55",
"interface.0.ip.0.address": "10.0.0.100/24",
"interface.0.route.0.gateway": "10.0.0.1",
"interface.0.route.0.destination": "0.0.0.0/0",
},
interfaces: []InterfaceGenerator{
&physicalInterface{logicalInterface{
hwaddr: mustParseMac(net.ParseMAC("00:11:22:33:44:55")),
config: configMethodStatic{
hwaddress: mustParseMac(net.ParseMAC("00:11:22:33:44:55")),
addresses: []net.IPNet{net.IPNet{IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)}},
// I realize how upset you must be that I am shoving an IPMask into an IP. This is because net.IPv4zero is
// actually a magic IPv6 address which ruins our equality check. What's that? Just use IP::Equal()? I'd rather
// DeepEqual just handle that for me, but until Go gets operator overloading, we are stuck with this.
routes: []route{route{
destination: net.IPNet{IP: net.IP(net.CIDRMask(0, net.IPv4len*8)), Mask: net.CIDRMask(0, net.IPv4len*8)},
gateway: net.ParseIP("10.0.0.1")},
},
},
}},
},
},
{
config: map[string]string{
"dns.server.0": "1.2.3.4",
"dns.server.1": "5.6.7.8",
"interface.0.mac": "00:11:22:33:44:55",
"interface.0.ip.0.address": "10.0.0.100/24",
"interface.0.ip.1.address": "10.0.0.101/24",
"interface.0.route.0.gateway": "10.0.0.1",
"interface.0.route.0.destination": "0.0.0.0/0",
"interface.1.name": "eth0",
"interface.1.ip.0.address": "10.0.1.100/24",
"interface.1.route.0.gateway": "10.0.1.1",
"interface.1.route.0.destination": "0.0.0.0/0",
"interface.2.dhcp": "yes",
"interface.2.mac": "00:11:22:33:44:77",
},
interfaces: []InterfaceGenerator{
&physicalInterface{logicalInterface{
hwaddr: mustParseMac(net.ParseMAC("00:11:22:33:44:55")),
config: configMethodStatic{
hwaddress: mustParseMac(net.ParseMAC("00:11:22:33:44:55")),
addresses: []net.IPNet{
net.IPNet{IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)},
net.IPNet{IP: net.ParseIP("10.0.0.101"), Mask: net.CIDRMask(24, net.IPv4len*8)},
},
routes: []route{route{
destination: net.IPNet{IP: net.IP(net.CIDRMask(0, net.IPv4len*8)), Mask: net.CIDRMask(0, net.IPv4len*8)},
gateway: net.ParseIP("10.0.0.1")},
},
nameservers: []net.IP{net.ParseIP("1.2.3.4"), net.ParseIP("5.6.7.8")},
},
}},
&physicalInterface{logicalInterface{
name: "eth0",
config: configMethodStatic{
addresses: []net.IPNet{net.IPNet{IP: net.ParseIP("10.0.1.100"), Mask: net.CIDRMask(24, net.IPv4len*8)}},
routes: []route{route{
destination: net.IPNet{IP: net.IP(net.CIDRMask(0, net.IPv4len*8)), Mask: net.CIDRMask(0, net.IPv4len*8)},
gateway: net.ParseIP("10.0.1.1")},
},
nameservers: []net.IP{net.ParseIP("1.2.3.4"), net.ParseIP("5.6.7.8")},
},
}},
&physicalInterface{logicalInterface{
hwaddr: mustParseMac(net.ParseMAC("00:11:22:33:44:77")),
config: configMethodDHCP{hwaddress: mustParseMac(net.ParseMAC("00:11:22:33:44:77"))},
}},
},
},
{
config: map[string]string{"dns.server.0": "test dns"},
err: errors.New(`invalid nameserver: "test dns"`),
},
}
for i, tt := range tests {
interfaces, err := ProcessVMwareNetconf(tt.config)
if !reflect.DeepEqual(tt.err, err) {
t.Errorf("bad error (#%d): want %v, got %v", i, tt.err, err)
}
if !reflect.DeepEqual(tt.interfaces, interfaces) {
t.Errorf("bad interfaces (#%d): want %#v, got %#v", i, tt.interfaces, interfaces)
for _, iface := range tt.interfaces {
t.Logf(" want: %#v", iface)
}
for _, iface := range interfaces {
t.Logf(" got: %#v", iface)
}
}
}
}
func TestProcessAddressConfig(t *testing.T) {
tests := []struct {
config map[string]string
prefix string
addresses []net.IPNet
err error
}{
{},
// static - ipv4
{
config: map[string]string{
"ip.0.address": "10.0.0.100/24",
},
addresses: []net.IPNet{{IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)}},
},
{
config: map[string]string{
"this.is.a.prefix.ip.0.address": "10.0.0.100/24",
},
prefix: "this.is.a.prefix.",
addresses: []net.IPNet{{IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)}},
},
{
config: map[string]string{
"ip.0.address": "10.0.0.100/24",
"ip.1.address": "10.0.0.101/24",
"ip.2.address": "10.0.0.102/24",
},
addresses: []net.IPNet{
{IP: net.ParseIP("10.0.0.100"), Mask: net.CIDRMask(24, net.IPv4len*8)},
{IP: net.ParseIP("10.0.0.101"), Mask: net.CIDRMask(24, net.IPv4len*8)},
{IP: net.ParseIP("10.0.0.102"), Mask: net.CIDRMask(24, net.IPv4len*8)},
},
},
// static - ipv6
{
config: map[string]string{
"ip.0.address": "fe00::100/64",
},
addresses: []net.IPNet{{IP: net.ParseIP("fe00::100"), Mask: net.IPMask(net.CIDRMask(64, net.IPv6len*8))}},
},
{
config: map[string]string{
"ip.0.address": "fe00::100/64",
"ip.1.address": "fe00::101/64",
"ip.2.address": "fe00::102/64",
},
addresses: []net.IPNet{
{IP: net.ParseIP("fe00::100"), Mask: net.CIDRMask(64, net.IPv6len*8)},
{IP: net.ParseIP("fe00::101"), Mask: net.CIDRMask(64, net.IPv6len*8)},
{IP: net.ParseIP("fe00::102"), Mask: net.CIDRMask(64, net.IPv6len*8)},
},
},
// invalid
{
config: map[string]string{
"ip.0.address": "test address",
},
err: errors.New(`invalid address: "test address"`),
},
}
for i, tt := range tests {
addresses, err := processAddressConfig(tt.config, tt.prefix)
if !reflect.DeepEqual(tt.err, err) {
t.Errorf("bad error (#%d): want %v, got %v", i, tt.err, err)
}
if err != nil {
continue
}
if !reflect.DeepEqual(tt.addresses, addresses) {
t.Errorf("bad addresses (#%d): want %#v, got %#v", i, tt.addresses, addresses)
}
}
}
func TestProcessRouteConfig(t *testing.T) {
tests := []struct {
config map[string]string
prefix string
routes []route
err error
}{
{},
{
config: map[string]string{
"route.0.gateway": "10.0.0.1",
"route.0.destination": "0.0.0.0/0",
},
routes: []route{{destination: net.IPNet{IP: net.IP(net.CIDRMask(0, net.IPv4len*8)), Mask: net.CIDRMask(0, net.IPv4len*8)}, gateway: net.ParseIP("10.0.0.1")}},
},
{
config: map[string]string{
"this.is.a.prefix.route.0.gateway": "10.0.0.1",
"this.is.a.prefix.route.0.destination": "0.0.0.0/0",
},
prefix: "this.is.a.prefix.",
routes: []route{{destination: net.IPNet{IP: net.IP(net.CIDRMask(0, net.IPv4len*8)), Mask: net.CIDRMask(0, net.IPv4len*8)}, gateway: net.ParseIP("10.0.0.1")}},
},
{
config: map[string]string{
"route.0.gateway": "fe00::1",
"route.0.destination": "::/0",
},
routes: []route{{destination: net.IPNet{IP: net.IPv6zero, Mask: net.IPMask(net.IPv6zero)}, gateway: net.ParseIP("fe00::1")}},
},
// invalid
{
config: map[string]string{
"route.0.gateway": "test gateway",
"route.0.destination": "0.0.0.0/0",
},
err: errors.New(`invalid gateway: "test gateway"`),
},
{
config: map[string]string{
"route.0.gateway": "10.0.0.1",
"route.0.destination": "test destination",
},
err: &net.ParseError{Type: "CIDR address", Text: "test destination"},
},
}
for i, tt := range tests {
routes, err := processRouteConfig(tt.config, tt.prefix)
if !reflect.DeepEqual(tt.err, err) {
t.Errorf("bad error (#%d): want %v, got %v", i, tt.err, err)
}
if err != nil {
continue
}
if !reflect.DeepEqual(tt.routes, routes) {
t.Errorf("bad routes (#%d): want %#v, got %#v", i, tt.routes, routes)
}
}
}
func TestProcessDHCPConfig(t *testing.T) {
tests := []struct {
config map[string]string
prefix string
dhcp bool
err error
}{
{},
// prefix
{config: map[string]string{"this.is.a.prefix.mac": ""}, prefix: "this.is.a.prefix.", dhcp: false},
{config: map[string]string{"this.is.a.prefix.dhcp": "yes"}, prefix: "this.is.a.prefix.", dhcp: true},
// dhcp
{config: map[string]string{"dhcp": "yes"}, dhcp: true},
{config: map[string]string{"dhcp": "no"}, dhcp: false},
// invalid
{config: map[string]string{"dhcp": "blah"}, err: errors.New(`invalid DHCP option: "blah"`)},
}
for i, tt := range tests {
dhcp, err := processDHCPConfig(tt.config, tt.prefix)
if !reflect.DeepEqual(tt.err, err) {
t.Errorf("bad error (#%d): want %v, got %v", i, tt.err, err)
}
if err != nil {
continue
}
if tt.dhcp != dhcp {
t.Errorf("bad dhcp (#%d): want %v, got %v", i, tt.dhcp, dhcp)
}
}
}

View File

@ -15,12 +15,10 @@
package pkg package pkg
import ( import (
"crypto/tls"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"net"
"net/http" "net/http"
neturl "net/url" neturl "net/url"
"strings" "strings"
@ -55,16 +53,15 @@ type ErrNetwork struct {
} }
type HttpClient struct { type HttpClient struct {
// Initial backoff duration. Defaults to 50 milliseconds
InitialBackoff time.Duration
// Maximum exp backoff duration. Defaults to 5 seconds // Maximum exp backoff duration. Defaults to 5 seconds
MaxBackoff time.Duration MaxBackoff time.Duration
// Maximum number of connection retries. Defaults to 15 // Maximum number of connection retries. Defaults to 15
MaxRetries int MaxRetries int
// HTTP client timeout, this is suggested to be low since exponential
// backoff will kick off too. Defaults to 2 seconds
Timeout time.Duration
// Whether or not to skip TLS verification. Defaults to false // Whether or not to skip TLS verification. Defaults to false
SkipTLS bool SkipTLS bool
@ -78,29 +75,12 @@ type Getter interface {
func NewHttpClient() *HttpClient { func NewHttpClient() *HttpClient {
hc := &HttpClient{ hc := &HttpClient{
InitialBackoff: 50 * time.Millisecond,
MaxBackoff: time.Second * 5, MaxBackoff: time.Second * 5,
MaxRetries: 15, MaxRetries: 15,
Timeout: time.Duration(2) * time.Second,
SkipTLS: false, SkipTLS: false,
} client: &http.Client{
Timeout: 10 * time.Second,
// We need to create our own client in order to add timeout support.
// TODO(c4milo) Replace it once Go 1.3 is officially used by CoreOS
// More info: https://code.google.com/p/go/source/detail?r=ada6f2d5f99f
hc.client = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: hc.SkipTLS,
},
Dial: func(network, addr string) (net.Conn, error) {
deadline := time.Now().Add(hc.Timeout)
c, err := net.DialTimeout(network, addr, hc.Timeout)
if err != nil {
return nil, err
}
c.SetDeadline(deadline)
return c, nil
},
}, },
} }
@ -134,7 +114,7 @@ func (h *HttpClient) GetRetry(rawurl string) ([]byte, error) {
dataURL := url.String() dataURL := url.String()
duration := 50 * time.Millisecond duration := h.InitialBackoff
for retry := 1; retry <= h.MaxRetries; retry++ { for retry := 1; retry <= h.MaxRetries; retry++ {
log.Printf("Fetching data from %s. Attempt #%d", dataURL, retry) log.Printf("Fetching data from %s. Attempt #%d", dataURL, retry)

View File

@ -0,0 +1,37 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package system
import (
"github.com/coreos/coreos-cloudinit/config"
)
// 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),
}},
}}}
}

View File

@ -15,7 +15,6 @@
package system package system
import ( import (
"fmt"
"log" "log"
"net" "net"
"os/exec" "os/exec"
@ -59,7 +58,7 @@ func downNetworkInterfaces(interfaces []network.InterfaceGenerator) error {
if systemInterface, ok := sysInterfaceMap[iface.Name()]; ok { if systemInterface, ok := sysInterfaceMap[iface.Name()]; ok {
log.Printf("Taking down interface %q\n", systemInterface.Name) log.Printf("Taking down interface %q\n", systemInterface.Name)
if err := netlink.NetworkLinkDown(systemInterface); err != nil { if err := netlink.NetworkLinkDown(systemInterface); err != nil {
fmt.Printf("Error while downing interface %q (%s). Continuing...\n", systemInterface.Name, err) log.Printf("Error while downing interface %q (%s). Continuing...\n", systemInterface.Name, err)
} }
} }
} }

View File

@ -72,6 +72,10 @@ func CreateUser(u *config.User) error {
args = append(args, "--no-log-init") args = append(args, "--no-log-init")
} }
if u.Shell != "" {
args = append(args, "--shell", u.Shell)
}
args = append(args, u.Name) args = append(args, u.Name)
output, err := exec.Command("useradd", args...).CombinedOutput() output, err := exec.Command("useradd", args...).CombinedOutput()

View File

@ -1,19 +1,8 @@
#!/bin/bash -e #!/bin/bash -e
#
# Run all coreos-cloudinit tests
# ./test
# ./test -v
#
# Run tests for one package
# PKG=initialize ./test
#
# Invoke ./cover for HTML output
COVER=${COVER:-"-cover"}
source ./build source ./build
declare -a TESTPKGS=( SRC="
config config
config/validate config/validate
datasource datasource
@ -26,41 +15,29 @@ declare -a TESTPKGS=(
datasource/proc_cmdline datasource/proc_cmdline
datasource/test datasource/test
datasource/url datasource/url
datasource/vmware
datasource/waagent datasource/waagent
initialize initialize
network network
pkg pkg
system system
) .
"
if [ -z "$PKG" ]; then echo "Checking gofix..."
GOFMTPATH="${TESTPKGS[*]} *.go" go tool fix -diff $SRC
# prepend repo path to each package
TESTPKGS="${TESTPKGS[*]/#/${REPO_PATH}/} ./"
else
GOFMTPATH="$TESTPKGS"
# strip out slashes and dots from PKG=./foo/
TESTPKGS=${PKG//\//}
TESTPKGS=${TESTPKGS//./}
TESTPKGS=${TESTPKGS/#/${REPO_PATH}/}
fi
echo "Running tests..."
go test -i ${TESTPKGS}
go test ${COVER} $@ ${TESTPKGS}
echo "Checking gofmt..." echo "Checking gofmt..."
fmtRes=$(gofmt -l $GOFMTPATH) gofmt -d -e $SRC
if [ -n "$fmtRes" ]; then
echo "$fmtRes" # split SRC into an array and prepend REPO_PATH to each local package for go vet
exit 1 split_vet=(${SRC// / })
fi VET_TEST="${REPO_PATH} ${split_vet[@]/#/${REPO_PATH}/}"
echo "Checking govet..." echo "Checking govet..."
vetRes=$(go vet $TESTPKGS) go vet $VET_TEST
if [ -n "${vetRes}" ]; then
echo -e "govet checking failed:\n${vetRes}" echo "Running tests..."
exit 255 go test -timeout 60s -cover $@ ${VET_TEST} --race
fi
echo "Success" echo "Success"