1
0
mirror of https://github.com/rancher/os.git synced 2025-07-10 21:33:04 +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
- package: github.com/coreos/coreos-cloudinit
version: 405c2600b19ae77516c967f8ee8ebde5624d3663
version: 65031e1ab2d3574544d26f5b5d7ddddd0032fd00
repo: https://github.com/rancher/coreos-cloudinit.git
- package: github.com/coreos/go-systemd

View File

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

View File

@ -77,3 +77,10 @@ coreos:
addr: 203.0.113.29:4001
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
NAME="coreos-cloudinit"
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
mkdir -p gopath/src/${ORG_PATH}
@ -11,4 +14,4 @@ fi
export GOBIN=${PWD}/bin
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"
"regexp"
"strings"
"unicode"
"github.com/coreos/yaml"
)
@ -37,6 +38,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"`
@ -48,10 +50,8 @@ type CoreOS struct {
func IsCloudConfig(userdata string) bool {
header := strings.SplitN(userdata, "\n", 2)[0]
// Explicitly trim the header so we can handle user-data from
// non-unix operating systems. The rest of the file is parsed
// by yaml, which correctly handles CRLF.
header = strings.TrimSuffix(header, "\r")
// Trim trailing whitespaces
header = strings.TrimRightFunc(header, unicode.IsSpace)
return (header == "#cloud-config")
}

View File

@ -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) {

View File

@ -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"`

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

View File

@ -23,4 +23,5 @@ type Flannel struct {
IPMasq string `yaml:"ip_masq" env:"FLANNELD_IP_MASQ"`
SubnetFile string `yaml:"subnet_file" env:"FLANNELD_SUBNET_FILE"`
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 {
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"`
EtcdCAFile string `yaml:"etcd_cafile" env:"FLEET_ETCD_CAFILE"`
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"`
Metadata string `yaml:"metadata" env:"FLEET_METADATA"`
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"`
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"`
EtcdCertFile string `yaml:"etcd_certfile" env:"LOCKSMITHD_ETCD_CERTFILE"`
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"`
PasswordHash string `yaml:"passwd"`
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
SSHImportGithubUser string `yaml:"coreos_ssh_import_github"`
SSHImportGithubUsers []string `yaml:"coreos_ssh_import_github_users"`
SSHImportURL string `yaml:"coreos_ssh_import_url"`
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" deprecated:"trying to fetch from a remote endpoint introduces too many intermittent errors"`
SSHImportURL string `yaml:"coreos_ssh_import_url" deprecated:"trying to fetch from a remote endpoint introduces too many intermittent errors"`
GECOS string `yaml:"gecos"`
Homedir string `yaml:"homedir"`
NoCreateHome bool `yaml:"no_create_home"`
@ -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"`
}

View File

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

View File

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

View File

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

View File

@ -90,34 +90,27 @@ func TestFetchMetadata(t *testing.T) {
"0": "publickey1",
"1": "publickey2",
},
NetworkConfig: []byte(`{
"droplet_id": 1,
"user_data": "hello",
"vendor_data": "hello",
"public_keys": [
"publickey1",
"publickey2"
],
"region": "nyc2",
"interfaces": {
"public": [
{
"ipv4": {
"ip_address": "192.168.1.2",
"netmask": "255.255.255.0",
"gateway": "192.168.1.1"
NetworkConfig: Metadata{
Interfaces: Interfaces{
Public: []Interface{
Interface{
IPv4: &Address{
IPAddress: "192.168.1.2",
Netmask: "255.255.255.0",
Gateway: "192.168.1.1",
},
"ipv6": {
"ip_address": "fe00::",
"cidr": 126,
"gateway": "fe00::"
IPv6: &Address{
IPAddress: "fe00::",
Cidr: 126,
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"
"bytes"
"fmt"
"log"
"net"
"strings"
@ -61,7 +62,7 @@ func (ms metadataService) FetchMetadata() (datasource.Metadata, error) {
return metadata, err
}
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 {
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{
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},

View File

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

View File

@ -47,7 +47,7 @@ func TestParseHeaderCRLF(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)
if err != nil {
t.Fatalf("Failed parsing config: %v", err)

View File

@ -15,7 +15,6 @@
package network
import (
"encoding/json"
"fmt"
"log"
"net"
@ -23,26 +22,18 @@ import (
"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")
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")
nameservers, err := parseNameservers(cfg.DNS)
nameservers, err := parseNameservers(config.DNS)
if err != nil {
return nil, err
}
log.Printf("Parsed %d nameservers\n", len(nameservers))
log.Println("Parsing interfaces")
generators, err := parseInterfaces(cfg.Interfaces, nameservers)
generators, err := parseInterfaces(config.Interfaces, nameservers)
if err != nil {
return nil, err
}
@ -52,9 +43,9 @@ func ProcessDigitalOceanNetconf(config []byte) ([]InterfaceGenerator, error) {
return generators, nil
}
func parseNameservers(cfg digitalocean.DNS) ([]net.IP, error) {
nameservers := make([]net.IP, 0, len(cfg.Nameservers))
for _, ns := range cfg.Nameservers {
func parseNameservers(config digitalocean.DNS) ([]net.IP, error) {
nameservers := make([]net.IP, 0, len(config.Nameservers))
for _, ns := range config.Nameservers {
if ip := net.ParseIP(ns); ip == nil {
return nil, fmt.Errorf("could not parse %q as nameserver IP address", ns)
} else {
@ -64,16 +55,16 @@ func parseNameservers(cfg digitalocean.DNS) ([]net.IP, error) {
return nameservers, nil
}
func parseInterfaces(cfg digitalocean.Interfaces, nameservers []net.IP) ([]InterfaceGenerator, error) {
generators := make([]InterfaceGenerator, 0, len(cfg.Public)+len(cfg.Private))
for _, iface := range cfg.Public {
func parseInterfaces(config digitalocean.Interfaces, nameservers []net.IP) ([]InterfaceGenerator, error) {
generators := make([]InterfaceGenerator, 0, len(config.Public)+len(config.Private))
for _, iface := range config.Public {
if generator, err := parseInterface(iface, nameservers, true); err == nil {
generators = append(generators, &physicalInterface{*generator})
} else {
return nil, err
}
}
for _, iface := range cfg.Private {
for _, iface := range config.Private {
if generator, err := parseInterface(iface, []net.IP{}, false); err == nil {
generators = append(generators, &physicalInterface{*generator})
} 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)
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) {
for _, tt := range []struct {
cfg digitalocean.Interface
@ -64,7 +72,7 @@ func TestParseInterface(t *testing.T) {
cfg: digitalocean.Interface{
MAC: "bad",
},
err: errors.New("invalid MAC address: bad"),
err: mkInvalidMAC(),
},
{
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)
if !errorsEqual(tt.err, err) {
@ -337,13 +409,13 @@ func TestParseInterfaces(t *testing.T) {
cfg: digitalocean.Interfaces{
Public: []digitalocean.Interface{{MAC: "bad"}},
},
err: errors.New("invalid MAC address: bad"),
err: mkInvalidMAC(),
},
{
cfg: digitalocean.Interfaces{
Private: []digitalocean.Interface{{MAC: "bad"}},
},
err: errors.New("invalid MAC address: bad"),
err: mkInvalidMAC(),
},
} {
ifaces, err := parseInterfaces(tt.cfg, tt.nss)
@ -358,27 +430,37 @@ func TestParseInterfaces(t *testing.T) {
func TestProcessDigitalOceanNetconf(t *testing.T) {
for _, tt := range []struct {
cfg string
cfg digitalocean.Metadata
ifaces []InterfaceGenerator
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"),
},
{
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"),
},
{
cfg: `{}`,
ifaces: []InterfaceGenerator{},
},
} {
ifaces, err := ProcessDigitalOceanNetconf([]byte(tt.cfg))
ifaces, err := ProcessDigitalOceanNetconf(tt.cfg)
if !errorsEqual(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 {
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 {

View File

@ -52,7 +52,7 @@ func TestInterfaceGenerators(t *testing.T) {
},
{
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",
kind: "bond",
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
import (
"crypto/tls"
"errors"
"fmt"
"io/ioutil"
"log"
"net"
"net/http"
neturl "net/url"
"strings"
@ -55,16 +53,15 @@ type ErrNetwork struct {
}
type HttpClient struct {
// Initial backoff duration. Defaults to 50 milliseconds
InitialBackoff time.Duration
// Maximum exp backoff duration. Defaults to 5 seconds
MaxBackoff time.Duration
// Maximum number of connection retries. Defaults to 15
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
SkipTLS bool
@ -78,29 +75,12 @@ type Getter interface {
func NewHttpClient() *HttpClient {
hc := &HttpClient{
InitialBackoff: 50 * time.Millisecond,
MaxBackoff: time.Second * 5,
MaxRetries: 15,
Timeout: time.Duration(2) * time.Second,
SkipTLS: false,
}
// 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
},
client: &http.Client{
Timeout: 10 * time.Second,
},
}
@ -134,7 +114,7 @@ func (h *HttpClient) GetRetry(rawurl string) ([]byte, error) {
dataURL := url.String()
duration := 50 * time.Millisecond
duration := h.InitialBackoff
for retry := 1; retry <= h.MaxRetries; 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
import (
"fmt"
"log"
"net"
"os/exec"
@ -59,7 +58,7 @@ func downNetworkInterfaces(interfaces []network.InterfaceGenerator) error {
if systemInterface, ok := sysInterfaceMap[iface.Name()]; ok {
log.Printf("Taking down interface %q\n", systemInterface.Name)
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")
}
if u.Shell != "" {
args = append(args, "--shell", u.Shell)
}
args = append(args, u.Name)
output, err := exec.Command("useradd", args...).CombinedOutput()

View File

@ -1,19 +1,8 @@
#!/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
declare -a TESTPKGS=(
SRC="
config
config/validate
datasource
@ -26,41 +15,29 @@ declare -a TESTPKGS=(
datasource/proc_cmdline
datasource/test
datasource/url
datasource/vmware
datasource/waagent
initialize
network
pkg
system
)
.
"
if [ -z "$PKG" ]; then
GOFMTPATH="${TESTPKGS[*]} *.go"
# 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 gofix..."
go tool fix -diff $SRC
echo "Checking gofmt..."
fmtRes=$(gofmt -l $GOFMTPATH)
if [ -n "$fmtRes" ]; then
echo "$fmtRes"
exit 1
fi
gofmt -d -e $SRC
# split SRC into an array and prepend REPO_PATH to each local package for go vet
split_vet=(${SRC// / })
VET_TEST="${REPO_PATH} ${split_vet[@]/#/${REPO_PATH}/}"
echo "Checking govet..."
vetRes=$(go vet $TESTPKGS)
if [ -n "${vetRes}" ]; then
echo -e "govet checking failed:\n${vetRes}"
exit 255
fi
go vet $VET_TEST
echo "Running tests..."
go test -timeout 60s -cover $@ ${VET_TEST} --race
echo "Success"