diff --git a/.github/tailor.yaml b/.github/tailor.yaml new file mode 100644 index 00000000..019ec260 --- /dev/null +++ b/.github/tailor.yaml @@ -0,0 +1,15 @@ +rules: + - name: commit title scope + description: all commit titles must have a lowercase scope + expression: |- + .commits all((.title test "^[a-z1-9/*,:_-]+:") or (.title test "^Revert")) + + - name: commit title length + description: all commit titles must be no more than 50 characters + expression: |- + .commits all((.title length < 51) or (.title test "^Revert")) + + - name: commit description + description: all commits must have a description with each line having no more than 72 characters + expression: |- + .commits all((.description lines all(. length < 73) and (.description lines length > 0)) or (.title test "^Revert")) diff --git a/.gitignore b/.gitignore index d098fb6b..324c57f7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -bin/ -gopath/ +target/ +**/*.rs.bk diff --git a/.travis.yml b/.travis.yml index 9c11341c..8c91a741 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,8 @@ -language: go +language: rust +rust: + - stable + - beta + - nightly matrix: - include: - - go: 1.5 - env: GO15VENDOREXPERIMENT=1 - - go: 1.6 - -install: - - - -script: - - ./test + allow_failures: + - rust: nightly diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7ad3b312..f15d76a9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,26 +5,33 @@ GitHub pull requests. This document outlines some of the conventions on development workflow, commit message formatting, contact points and other resources to make it easier to get your contribution accepted. -# Certificate of Origin +## Certificate of Origin By contributing to this project you agree to the Developer Certificate of Origin (DCO). This document was created by the Linux Kernel community and is a simple statement that you, as a contributor, have the legal right to make the contribution. See the [DCO](DCO) file for details. -# Email and Chat +## Email and Chat The project currently uses the general CoreOS email list and IRC channel: - Email: [coreos-dev](https://groups.google.com/forum/#!forum/coreos-dev) - IRC: #[coreos](irc://irc.freenode.org:6667/#coreos) IRC channel on freenode.org +Please avoid emailing maintainers found in the MAINTAINERS file directly. They +are very busy and read the mailing lists. + +## Reporting a security vulnerability + +Due to their public nature, GitHub and mailing lists are not appropriate places for reporting vulnerabilities. Please refer to CoreOS's [security disclosure][disclosure] process when reporting issues that may be security related. + ## Getting Started - Fork the repository on GitHub - Read the [README](README.md) for build and test instructions - Play with the project, submit bugs, submit patches! -## Contribution Flow +### Contribution Flow This is a rough outline of what a contributor's workflow looks like: @@ -37,6 +44,10 @@ This is a rough outline of what a contributor's workflow looks like: Thanks for your contributions! +### Code Style + +Code should be formatted according to the output of [rustfmt](https://github.com/rust-lang-nursery/rustfmt) + ### Format of the Commit Message We follow a rough convention for commit messages that is designed to answer two @@ -44,10 +55,10 @@ questions: what changed and why. The subject line should feature the what and the body of the commit should describe the why. ``` -environment: write new keys in consistent order +scripts: add the test-cluster command -Go 1.3 randomizes the ordering of keys when iterating over a map. -Sort the keys to make this ordering consistent. +this uses tmux to setup a test cluster that you can easily kill and +start for debugging. Fixes #38 ``` @@ -66,3 +77,6 @@ The first line is the subject and should be no longer than 70 characters, the second line is always blank, and other lines should be wrapped at 80 characters. This allows the message to be easier to read on GitHub as well as in various git tools. + + +[disclosure]: https://coreos.com/security/disclosure/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..daa65035 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1571 @@ +[root] +name = "coreos-metadata" +version = "1.0.0" +dependencies = [ + "base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.26.2 (registry+https://github.com/rust-lang/crates.io-index)", + "clippy 0.0.165 (registry+https://github.com/rust-lang/crates.io-index)", + "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "hostname 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.11.6 (registry+https://github.com/rust-lang/crates.io-index)", + "ipnetwork 0.12.7 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "mockito 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "openssh-keys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.9.20 (registry+https://github.com/rust-lang/crates.io-index)", + "pnet 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)", + "reqwest 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "serde-xml-rs 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "slog 2.0.12 (registry+https://github.com/rust-lang/crates.io-index)", + "slog-async 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slog-scope 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slog-term 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "update-ssh-keys 0.1.0 (git+https://github.com/coreos/update-ssh-keys?tag=v0.1.0)", + "users 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "adler32" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "advapi32-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "advapi32-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "aho-corasick" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ansi_term" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "atty" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "base64" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bitflags" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bitflags" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "block-buffer" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "build_const" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byte-tools" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byteorder" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "bytes" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cargo_metadata" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cc" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "chrono" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "clap" +version = "2.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "clippy" +version = "0.0.165" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cargo_metadata 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "clippy_lints 0.0.165 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "clippy_lints" +version = "0.0.165" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "itertools 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "pulldown-cmark 0.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "quine-mc_cluskey 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "conv" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "custom_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-foundation" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-foundation-sys" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crc" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "build_const 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crc-core 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crc-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "crossbeam" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "crypt32-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "custom_derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "digest" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "dtoa" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "either" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "error-chain" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "foreign-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "fs2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-zircon-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "futures" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures-cpupool" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "gcc" +version = "0.3.54" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "generic-array" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nodrop 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "typenum 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "getopts" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "glob" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "hostname" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", + "winutil 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "http-muncher" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "httparse" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "hyper" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-cpupool 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "relay 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-proto 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hyper-tls" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.11.6 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-tls 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "idna" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "iovec" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ipnetwork" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "isatty" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itertools" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "itoa" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "kernel32-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "language-tags" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazy_static" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazycell" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libflate" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crc 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "log" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "magenta" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "conv 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "magenta-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "magenta-sys" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "matches" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "memchr" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mime" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicase 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mio" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "iovec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazycell 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "magenta 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "magenta-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mockito" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "http-muncher 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "native-tls" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "openssl 0.9.20 (registry+https://github.com/rust-lang/crates.io-index)", + "schannel 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "net2" +version = "0.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nix" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nodrop" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "odds 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-integer" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-iter" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "num-traits" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "num_cpus" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "odds" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "openssh-keys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "openssl" +version = "0.9.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.20 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "openssl-sys" +version = "0.9.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "percent-encoding" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pkg-config" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pnet" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "ipnetwork 0.12.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", + "pnet_macros 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pnet_macros_support 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "syntex 0.42.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pnet_macros" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syntex 0.42.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syntex_syntax 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "pnet_macros_support" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "pulldown-cmark" +version = "0.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quine-mc_cluskey" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "quote" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-zircon 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "regex-syntax" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "relay" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "reqwest" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.11.6 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libflate 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_urlencoded 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-tls 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rustc-serialize" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc_version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "safemem" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "schannel" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crypt32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "secur32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "scoped-tls" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "secur32-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "security-framework" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "security-framework-sys" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "semver" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "serde-xml-rs" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "xml-rs 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive_internals 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_derive_internals" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", + "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_json" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "serde_urlencoded" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sha2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "slab" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "slab" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "slog" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "slog-async" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "slog 2.0.12 (registry+https://github.com/rust-lang/crates.io-index)", + "take_mut 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "slog-scope" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "slog 2.0.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "slog-term" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "isatty 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "slog 2.0.12 (registry+https://github.com/rust-lang/crates.io-index)", + "term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "smallvec" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "strsim" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "syn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "synom" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syntex" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "syntex_errors 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)", + "syntex_syntax 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syntex_errors" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", + "syntex_pos 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)", + "term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syntex_pos" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syntex_syntax" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", + "syntex_errors 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)", + "syntex_pos 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)", + "term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "take" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "take_mut" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "tempdir" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "term" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "term_size" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "termion" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "textwrap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thread_local" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)", + "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "time" +version = "0.1.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-io" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-proto" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-service" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-tls" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "toml" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "typenum" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicase" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-width" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-xid" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-xid" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "update-ssh-keys" +version = "0.1.0" +source = "git+https://github.com/coreos/update-ssh-keys?tag=v0.1.0#fcbbf62deb7bbe1df527d71c8b6c0231cb1090cb" +dependencies = [ + "clap 2.26.2 (registry+https://github.com/rust-lang/crates.io-index)", + "error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fs2 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "openssh-keys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "users 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "url" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "users" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "utf8-ranges" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vcpkg" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vec_map" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winutil" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "advapi32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "xml-rs" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6cbd0b9af8587c72beadc9f72d35b9fbb070982c9e6203e46e93f10df25f8f45" +"checksum advapi32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "307c92332867e586720c0222ee9d890bbe8431711efed8a1b06bc5b40fc66bd7" +"checksum advapi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e06588080cb19d0acb6739808aafa5f26bfb2ca015b2b6370028b44cf7cb8a9a" +"checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699" +"checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" +"checksum atty 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "21e50800ec991574876040fff8ee46b136a53e985286fbe6a3bdfe6421b78860" +"checksum base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96434f987501f0ed4eb336a411e0631ecd1afa11574fe148587adc4ff96143c9" +"checksum bitflags 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4f67931368edf3a9a51d29886d245f1c3db2f1ef0dcc9e35ff70341b78c10d23" +"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" +"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" +"checksum block-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1339a1042f5d9f295737ad4d9a6ab6bf81c84a933dba110b9200cd6d1448b814" +"checksum build_const 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e90dc84f5e62d2ebe7676b83c22d33b6db8bd27340fb6ffbff0a364efa0cb9c9" +"checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" +"checksum byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff81738b726f5d099632ceaffe7fb65b90212e8dce59d518729e7e8634032d3d" +"checksum bytes 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d828f97b58cc5de3e40c421d0cf2132d6b2da4ee0e11b8632fa838f0f9333ad6" +"checksum cargo_metadata 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "be1057b8462184f634c3a208ee35b0f935cfd94b694b26deadccd98732088d7b" +"checksum cc 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2c674f0870e3dbd4105184ea035acb1c32c8ae69939c9e228d2b11bbfe29efad" +"checksum cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" +"checksum chrono 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7c20ebe0b2b08b0aeddba49c609fe7957ba2e33449882cb186a180bc60682fa9" +"checksum clap 2.26.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3451e409013178663435d6f15fdb212f14ee4424a3d74f979d081d0a66b6f1f2" +"checksum clippy 0.0.165 (registry+https://github.com/rust-lang/crates.io-index)" = "88f67cdb6697a5d357f8f70cffdf65f4dcb6ec8bb5069f2bc347589b3869cbe8" +"checksum clippy_lints 0.0.165 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f37194e4a7cb36daeeb5727eac96e32c82965e4ae8b119817885377ddb6715" +"checksum conv 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "78ff10625fd0ac447827aa30ea8b861fead473bb60aeb73af6c1c58caf0d1299" +"checksum core-foundation 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25bfd746d203017f7d5cbd31ee5d8e17f94b6521c7af77ece6c9e4b2d4b16c67" +"checksum core-foundation-sys 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "065a5d7ffdcbc8fa145d6f0746f3555025b9097a9e9cda59f7467abae670c78d" +"checksum crc 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fba69ea0e15e720f7e1cfe1cf3bc55007fbd41e32b8ae11cfa343e7e5961e79a" +"checksum crc-core 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "003d1170779d405378223470f5864b41b79a91969be1260e4de7b4ec069af69c" +"checksum crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0c5ea215664ca264da8a9d9c3be80d2eaf30923c259d03e870388eb927508f97" +"checksum crypt32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e34988f7e069e0b2f3bfc064295161e489b2d4e04a2e4248fb94360cdf00b4ec" +"checksum custom_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "ef8ae57c4978a2acd8b869ce6b9ca1dfe817bff704c220209fdef2c0b75a01b9" +"checksum digest 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e5b29bf156f3f4b3c4f610a25ff69370616ae6e0657d416de22645483e72af0a" +"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab" +"checksum either 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e311a7479512fbdf858fb54d91ec59f3b9f85bc0113659f46bba12b199d273ce" +"checksum error-chain 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3" +"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +"checksum foreign-types 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e4056b9bd47f8ac5ba12be771f77a0dae796d1bbaaf5fd0b9c2d38b69b8a29d" +"checksum fs2 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9ab76cfd2aaa59b7bf6688ad9ba15bbae64bff97f04ea02144cfd3443e5c2866" +"checksum fuchsia-zircon 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f6c0581a4e363262e52b87f59ee2afe3415361c6ec35e665924eb08afe8ff159" +"checksum fuchsia-zircon-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "43f3795b4bae048dc6123a6b972cadde2e676f9ded08aef6bb77f5f157684a82" +"checksum futures 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "05a23db7bd162d4e8265968602930c476f688f0c180b44bdaf55e0cb2c687558" +"checksum futures-cpupool 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "e86f49cc0d92fe1b97a5980ec32d56208272cbb00f15044ea9e2799dde766fdf" +"checksum gcc 0.3.54 (registry+https://github.com/rust-lang/crates.io-index)" = "5e33ec290da0d127825013597dbdfc28bee4964690c7ce1166cbc2a7bd08b1bb" +"checksum generic-array 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fceb69994e330afed50c93524be68c42fa898c2d9fd4ee8da03bd7363acd26f2" +"checksum getopts 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)" = "65922871abd2f101a2eb0eaebadc66668e54a87ad9c3dd82520b5f86ede5eff9" +"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" +"checksum hostname 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d865f01509e69c001c31b8c341607d93caef4ecf6836ffee39a1702430147943" +"checksum http-muncher 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f0d8b98946459faf4fdfdf6d90e9aac08967b9b9ffe67005d2b1f99115da741" +"checksum httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "af2f2dd97457e8fb1ae7c5a420db346af389926e36f43768b96f101546b04a07" +"checksum hyper 0.11.6 (registry+https://github.com/rust-lang/crates.io-index)" = "1b45eac8b696d59491b079bd04fcb0f3488c0f6ed62dcb36bcfea8a543e9cdc3" +"checksum hyper-tls 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c81fa95203e2a6087242c38691a0210f23e9f3f8f944350bd676522132e2985" +"checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d" +"checksum iovec 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b6e8b9c2247fcf6c6a1151f1156932be5606c9fd6f55a2d7f9fc1cb29386b2f7" +"checksum ipnetwork 0.12.7 (registry+https://github.com/rust-lang/crates.io-index)" = "2134e210e2a024b5684f90e1556d5f71a1ce7f8b12e9ac9924c67fb36f63b336" +"checksum isatty 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "00c9301a947a2eaee7ce2556b80285dcc89558d07088962e6e8b9c25730f9dc6" +"checksum itertools 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d3f2be4da1690a039e9ae5fd575f706a63ad5a2120f161b1d653c9da3930dd21" +"checksum itoa 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8324a32baf01e2ae060e9de58ed0bc2320c9a2833491ee36cd3b4c414de4db8c" +"checksum kernel32-sys 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "e014dab1082fd9d80ea1fa6fcb261b47ed3eb511612a14198bb507701add083e" +"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" +"checksum lazy_static 0.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c9e5e58fa1a4c3b915a561a78a22ee0cac6ab97dca2504428bc1cb074375f8d5" +"checksum lazycell 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3b585b7a6811fb03aa10e74b278a0f00f8dd9b45dc681f148bb29fa5cb61859b" +"checksum libc 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)" = "56cce3130fd040c28df6f495c8492e5ec5808fb4c9093c310df02b0c8f030148" +"checksum libflate 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ae46bcdafa496981e996e57c5be82c0a7f130a071323764c6faa4803619f1e67" +"checksum log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "880f77541efa6e5cc74e76910c9884d9859683118839d6a1dc3b11e63512565b" +"checksum magenta 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf0336886480e671965f794bc9b6fce88503563013d1bfb7a502c81fe3ac527" +"checksum magenta-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40d014c7011ac470ae28e2f76a02bfea4a8480f73e701353b49ad7a8d75f4699" +"checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376" +"checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4" +"checksum mime 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e2e00e17be181010a91dbfefb01660b17311059dc8c7f48b9017677721e732bd" +"checksum mio 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "dbd91d3bfbceb13897065e97b2ef177a09a438cb33612b2d371bf568819a9313" +"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +"checksum mockito 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c1b79c374f7fb89af61c1d49086c824a1c2abe9cfcfe6b182980959b70fb4b4a" +"checksum native-tls 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04b781c9134a954c84f0594b9ab3f5606abc516030388e8511887ef4c204a1e5" +"checksum net2 0.2.31 (registry+https://github.com/rust-lang/crates.io-index)" = "3a80f842784ef6c9a958b68b7516bc7e35883c614004dd94959a4dca1b716c09" +"checksum nix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2c5afeb0198ec7be8569d666644b574345aad2e95a53baf3a532da3e0f3fb32" +"checksum nodrop 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "c20f62cbc112bb5beabe96e420b34b17cb627edb03039930a37351520efc69ee" +"checksum num 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "a311b77ebdc5dd4cf6449d81e4135d9f0e3b153839ac90e648a8ef538f923525" +"checksum num-integer 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "d1452e8b06e448a07f0e6ebb0bb1d92b8890eea63288c0b627331d53514d0fba" +"checksum num-iter 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "7485fcc84f85b4ecd0ea527b14189281cf27d60e583ae65ebc9c088b13dffe01" +"checksum num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "99843c856d68d8b4313b03a17e33c4bb42ae8f6610ea81b28abe076ac721b9b0" +"checksum num_cpus 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "514f0d73e64be53ff320680ca671b64fe3fb91da01e1ae2ddc99eb51d453b20d" +"checksum odds 0.2.25 (registry+https://github.com/rust-lang/crates.io-index)" = "c3df9b730298cea3a1c3faa90b7e2f9df3a9c400d0936d6015e6165734eefcba" +"checksum openssh-keys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a1af844e3f3f7742b5a8edbc0e5af0fe4b3a0b4172f6ec0c261fed74765dde8c" +"checksum openssl 0.9.20 (registry+https://github.com/rust-lang/crates.io-index)" = "8bf434ff6117485dc16478d77a4f5c84eccc9c3645c4da8323b287ad6a15a638" +"checksum openssl-sys 0.9.20 (registry+https://github.com/rust-lang/crates.io-index)" = "0ad395f1cee51b64a8d07cc8063498dc7554db62d5f3ca87a67f4eed2791d0c8" +"checksum percent-encoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de154f638187706bde41d9b4738748933d64e6b37bdbffc0b47a97d16a6ae356" +"checksum pkg-config 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3a8b4c6b8165cd1a1cd4b9b120978131389f64bdaf456435caa41e630edba903" +"checksum pnet 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d0364bc9319e77afb74fa65f6c8b0d73b1d3208f289cd227678f10562730791" +"checksum pnet_macros 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e77669156bd4cb7732fc11261a33cc56705100e3bed019ee1cbd84d97e2d9909" +"checksum pnet_macros_support 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8c79f254137dcce415bdf2c10b40cf74009a7a8d4a17985cc7773b41f7ccfaf1" +"checksum pulldown-cmark 0.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "378e941dbd392c101f2cb88097fa4d7167bc421d4b88de3ff7dbee503bc3233b" +"checksum quine-mc_cluskey 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "07589615d719a60c8dd8a4622e7946465dfef20d1a428f969e3443e7386d5f45" +"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" +"checksum rand 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "61efcbcd9fa8d8fbb07c84e34a8af18a1ff177b449689ad38a6e9457ecc7b2ae" +"checksum redox_syscall 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "8dde11f18c108289bef24469638a04dce49da56084f2d50618b226e47eb04509" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" +"checksum regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1731164734096285ec2a5ec7fea5248ae2f5485b3feeb0115af4fda2183b2d1b" +"checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db" +"checksum relay 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f301bafeb60867c85170031bdb2fcf24c8041f33aee09e7b116a58d4e9f781c5" +"checksum reqwest 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5866613d84e2a39c0479a960bf2d0eff1fbfc934f02cd42b5c08c1e1efc5b1fd" +"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" +"checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" +"checksum safemem 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e27a8b19b835f7aea908818e871f5cc3a5a186550c30773be987e155e8163d8f" +"checksum schannel 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7554288337c1110e34d7a2433518d889374c1de1a45f856b7bcddb03702131fc" +"checksum scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f417c22df063e9450888a7561788e9bd46d3bb3c1466435b4eccb903807f147d" +"checksum secur32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f412dfa83308d893101dd59c10d6fda8283465976c28c287c5c855bf8d216bc" +"checksum security-framework 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "dfa44ee9c54ce5eecc9de7d5acbad112ee58755239381f687e564004ba4a2332" +"checksum security-framework-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "5421621e836278a0b139268f36eee0dc7e389b784dc3f79d8f11aabadf41bead" +"checksum semver 0.1.20 (registry+https://github.com/rust-lang/crates.io-index)" = "d4f410fedcf71af0345d7607d246e7ad15faaadd49d240ee3b24e5dc21a820ac" +"checksum semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537" +"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +"checksum serde 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "6a7046c9d4c6c522d10b2d098f9bebe2bef227e0e74044d8c1bfcf6b476af799" +"checksum serde-xml-rs 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0c06881f4313eec67d4ecfcd8e14339f6042cfc0de4b1bd3ceae74c29d597f68" +"checksum serde_derive 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "1afcaae083fd1c46952a315062326bc9957f182358eb7da03b57ef1c688f7aa9" +"checksum serde_derive_internals 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd381f6d01a6616cdba8530492d453b7761b456ba974e98768a18cad2cd76f58" +"checksum serde_json 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ee28c1d94a7745259b767ca9e5b95d55bafbd3205ca3acb978cad84a6ed6bc62" +"checksum serde_urlencoded 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce0fd303af908732989354c6f02e05e2e6d597152870f2c6990efb0577137480" +"checksum sha2 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d963c78ce367df26d7ea8b8cc655c651b42e8a1e584e869c1e17dae3ccb116a" +"checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" +"checksum slab 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fdeff4cd9ecff59ec7e3744cbca73dfe5ac35c2aedb2cfba8a1c715a18912e9d" +"checksum slog 2.0.12 (registry+https://github.com/rust-lang/crates.io-index)" = "717c886b5e7855cf2e434a2d73ff494d33aaa584fb48e3074affc7b66dcd290d" +"checksum slog-async 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ad0c2eeab552a206f95303868338aa67041146da8f8978dba8343c2b29b8ec2" +"checksum slog-scope 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dd3236e1818c147ed3003930289dc6457425aaca3a7dbb6b7f3032f075d865c5" +"checksum slog-term 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b4dc32ad4eb5c7406c9aa333731803977fc962906af7e6f30ee9ccccc02076f" +"checksum smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c8cbcd6df1e117c2210e13ab5109635ad68a929fcbb8964dc965b76cb5ee013" +"checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" +"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" +"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" +"checksum syntex 0.42.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0a30b08a6b383a22e5f6edc127d169670d48f905bb00ca79a00ea3e442ebe317" +"checksum syntex_errors 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)" = "04c48f32867b6114449155b2a82114b86d4b09e1bddb21c47ff104ab9172b646" +"checksum syntex_pos 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd49988e52451813c61fecbe9abb5cfd4e1b7bb6cdbb980a6fbcbab859171a6" +"checksum syntex_syntax 0.42.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7628a0506e8f9666fdabb5f265d0059b059edac9a3f810bda077abb5d826bd8d" +"checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5" +"checksum take_mut 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7986ceb18a0d75e1fcb8b27c0119389bbe05f016e5a6e54d003251acc1122108" +"checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6" +"checksum term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1" +"checksum term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b6b55df3198cc93372e85dd2ed817f0e38ce8cc0f22eb32391bfad9c4bf209" +"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" +"checksum textwrap 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df8e08afc40ae3459e4838f303e465aa50d823df8d7f83ca88108f6d3afe7edd" +"checksum thread_local 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1697c4b57aeeb7a536b647165a2825faddffb1d3bad386d507709bd51a90bb14" +"checksum time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d788d3aa77bc0ef3e9621256885555368b47bd495c13dd2e7413c89f845520" +"checksum tokio-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "c843a027f7c1df5f81e7734a0df3f67bf329411781ebf36393ce67beef6071e3" +"checksum tokio-io 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b4ab83e7adb5677e42e405fa4ceff75659d93c4d7d7dd22f52fcec59ee9f02af" +"checksum tokio-proto 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fbb47ae81353c63c487030659494b295f6cb6576242f907f203473b191b0389" +"checksum tokio-service 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "24da22d077e0f15f55162bdbdc661228c1581892f52074fb242678d015b45162" +"checksum tokio-tls 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d88e411cac1c87e405e4090be004493c5d8072a370661033b1a64ea205ec2e13" +"checksum toml 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a7540f4ffc193e0d3c94121edb19b055670d369f77d5804db11ae053a45b6e7e" +"checksum typenum 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "13a99dc6780ef33c78780b826cf9d2a78840b72cae9474de4bcaf9051e60ebbd" +"checksum unicase 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2e01da42520092d0cd2d6ac3ae69eb21a22ad43ff195676b86f8c37f487d6b80" +"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +"checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f" +"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" +"checksum unicode-xid 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "36dff09cafb4ec7c8cf0023eb0b686cb6ce65499116a12201c9e11840ca01beb" +"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +"checksum update-ssh-keys 0.1.0 (git+https://github.com/coreos/update-ssh-keys?tag=v0.1.0)" = "" +"checksum url 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eeb819346883532a271eb626deb43c4a1bb4c4dd47c519bd78137c3e72a4fe27" +"checksum users 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "99ab1b53affc9f75f57da4a8b051a188e84d20d43bea0dd9bd8db71eebbca6da" +"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" +"checksum vcpkg 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9e0a7d8bed3178a8fb112199d466eeca9ed09a14ba8ad67718179b4fd5487d0b" +"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum winutil 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9486f81d058e5faff87ed37e6f476505f2c0fae341d46d53fddc73a88a26fec5" +"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +"checksum xml-rs 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7ec6c39eaa68382c8e31e35239402c0a9489d4141a8ceb0c716099a0b515b562" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..cc7907f1 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,45 @@ +[package] +name = "coreos-metadata" +version = "1.0.0" +authors = ["Stephen Demos "] + +[[bin]] +name = "coreos-metadata" +path = "src/bin/coreos-metadata.rs" + +[dependencies] +clap = "2.26" +users = "0.6" +slog-term = "2.2" +slog-async = "2.1" +slog-scope = "4.0" +mime = "0.3" +serde = "1.0" +serde_derive = "1.0" +serde-xml-rs = "0.2" +serde_json = "1.0" +openssl = "0.9.17" +base64 = "0.6" +byteorder = "1.1" +pnet = "0.19" +reqwest = "0.7" +hyper = "0.11" +error-chain = { version = "0.11", default-features = false } +openssh-keys = "0.1" +update-ssh-keys = { git = "https://github.com/coreos/update-ssh-keys", tag = "v0.1.0" } +ipnetwork = "0.12" +clippy = { version = "*", optional = true } +hostname = "0.1" +tempdir = "0.3" +nix = "0.9" + +[dependencies.slog] +version = "2.0" +features = ["max_level_trace"] + +[dev-dependencies] +mockito = "0.9" + +[features] +default = [] +lints = ["clippy"] diff --git a/MAINTAINERS b/MAINTAINERS deleted file mode 100644 index a7ebc5d9..00000000 --- a/MAINTAINERS +++ /dev/null @@ -1 +0,0 @@ -Alex Crawford (@crawford) diff --git a/NEWS b/NEWS deleted file mode 100644 index b62a57d5..00000000 --- a/NEWS +++ /dev/null @@ -1,110 +0,0 @@ -26-Sep-2017 COREOS-METADATA v0.14.0 - - Features - - Add CloudStack network provider - -08-Sep-2017 COREOS-METADATA v0.13.0 - - Features - - - Add CloudStack provider - - Add Oracle OCI provider - -30-Jun-2017 COREOS-METADATA v0.12.0 - - Features - - - Support writing network units on Packet - - Changes - - - Change network unit priority from 00 to 10 - -30-Jun-2017 COREOS-METADATA v0.11.0 - - Features - - - Add COREOS_DIGITALOCEAN_REGION - - Add vagrant_virtualbox provider - - Bug Fixes - - - Properly fetch SSH keys on EC2 - - Add trailing newlines to hostname and SSH key files - -23-May-2017 COREOS-METADATA v0.10.0 - - Features - - - Add COREOS_EC2_REGION - -10-May-2017 COREOS-METADATA v0.9.0 - - Features - - - Added COREOS_EC2_AVAILABILITY_ZONE to the ec2 provider - -17-Mar-2017 COREOS-METADATA v0.8.0 - - Features - - - Expose Packet's phone-home URL - -10-Mar-2017 COREOS-METADATA v0.7.0 - - Features - - - Add support for OpenStack-Metadata - -09-Nov-2016 COREOS-METADATA v0.6.2 - - Bug Fixes - - - Properly handle missing IP addresses on GCE - -01-Nov-2016 COREOS-METADATA v0.6.1 - - Bug Fixes - - - Wait for DHCP leases on Azure - -07-Sep-2016 COREOS-METADATA v0.6.0 - - Features - - - Add support for DigitalOcean - -26-Jul-2016 COREOS-METADATA v0.5.0 - - Features - - - Add support for Packet - -15-Jun-2016 COREOS-METADATA v0.4.1 - - Bug Fixes - - - Properly handle missing metadata fields - - Do not output attribute when its value is nil - - Features - - - Include the EC2 instance ID in the list of attributes - -29-Apr-2016 COREOS-METADATA v0.4.0 - - Features - - - Add support for writing SSH keys - - Add support for GCE - - Changes - - - Move to Go 1.5 and 1.6 and the new vendor/internal packages - - Renamed `--output` to `--attributes` - -24-Nov-2015 COREOS-METADATA v0.3.0 - - Changes - - - Renamed all output variables to be provider specific diff --git a/NOTICE b/NOTICE index e520005c..571695f5 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ CoreOS Project -Copyright 2015 CoreOS, Inc +Copyright 2017 CoreOS, Inc This product includes software developed at CoreOS, Inc. (http://www.coreos.com/). diff --git a/README.md b/README.md index a726e00e..6da94498 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ This is a small utility, typically used in conjunction with [Ignition][ignition] The supported cloud providers and their respective metadata are as follows: - azure + - SSH Keys - Attributes - COREOS_AZURE_IPV4_DYNAMIC - COREOS_AZURE_IPV4_VIRTUAL @@ -38,6 +39,7 @@ The supported cloud providers and their respective metadata are as follows: - COREOS_GCE_IP_LOCAL_0 - packet - SSH Keys + - Network Configs - Attributes - COREOS_PACKET_HOSTNAME - COREOS_PACKET_IPV4_PUBLIC_0 diff --git a/build b/build deleted file mode 100755 index b12d15d8..00000000 --- a/build +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash - -set -eu - -NAME="coreos-metadata" -ORG_PATH="github.com/coreos" -REPO_PATH="${ORG_PATH}/${NAME}" -VERSION=$(git describe --dirty) -GLDFLAGS="-X main.version=\"${VERSION}\"" - -if [ ! -h gopath/src/${REPO_PATH} ]; then - mkdir -p gopath/src/${ORG_PATH} - ln -s ../../../.. gopath/src/${REPO_PATH} || exit 255 -fi - -export GOBIN=${PWD}/bin -export GOPATH=${PWD}/gopath - -eval $(go env) - -echo "Building ${NAME}..." -go build -ldflags "${GLDFLAGS}" -o ${GOBIN}/${NAME} ${REPO_PATH}/internal diff --git a/code-of-conduct.md b/code-of-conduct.md new file mode 100644 index 00000000..c0c20dd8 --- /dev/null +++ b/code-of-conduct.md @@ -0,0 +1,63 @@ +## CoreOS Community Code of Conduct + +### Contributor Code of Conduct + +As contributors and maintainers of this project, and in the interest of +fostering an open and welcoming community, we pledge to respect all people who +contribute through reporting issues, posting feature requests, updating +documentation, submitting pull requests or patches, and other activities. + +We are committed to making participation in this project a harassment-free +experience for everyone, regardless of level of experience, gender, gender +identity and expression, sexual orientation, disability, personal appearance, +body size, race, ethnicity, age, religion, or nationality. + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery +* Personal attacks +* Trolling or insulting/derogatory comments +* Public or private harassment +* Publishing others' private information, such as physical or electronic addresses, without explicit permission +* Other unethical or unprofessional conduct. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct. By adopting this Code of Conduct, +project maintainers commit themselves to fairly and consistently applying these +principles to every aspect of managing this project. Project maintainers who do +not follow or enforce the Code of Conduct may be permanently removed from the +project team. + +This code of conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting a project maintainer, Brandon Philips +, and/or Meghan Schofield +. + +This Code of Conduct is adapted from the Contributor Covenant +(http://contributor-covenant.org), version 1.2.0, available at +http://contributor-covenant.org/version/1/2/0/ + +### CoreOS Events Code of Conduct + +CoreOS events are working conferences intended for professional networking and +collaboration in the CoreOS community. Attendees are expected to behave +according to professional standards and in accordance with their employer’s +policies on appropriate workplace behavior. + +While at CoreOS events or related social networking opportunities, attendees +should not engage in discriminatory or offensive speech or actions including +but not limited to gender, sexuality, race, age, disability, or religion. +Speakers should be especially aware of these concerns. + +CoreOS does not condone any statements by speakers contrary to these standards. +CoreOS reserves the right to deny entrance and/or eject from an event (without +refund) any individual found to be engaging in discriminatory or offensive +speech or actions. + +Please bring any concerns to the immediate attention of designated on-site +staff, Brandon Philips , and/or Meghan Schofield +. diff --git a/internal/main.go b/internal/main.go deleted file mode 100644 index c54ec372..00000000 --- a/internal/main.go +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright 2015 CoreOS, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "errors" - "flag" - "fmt" - "io/ioutil" - "os" - "os/user" - "path/filepath" - "strings" - - "github.com/coreos/coreos-metadata/internal/providers" - "github.com/coreos/coreos-metadata/internal/providers/azure" - "github.com/coreos/coreos-metadata/internal/providers/cloudStackConfigDrive" - "github.com/coreos/coreos-metadata/internal/providers/cloudStackMetadata" - "github.com/coreos/coreos-metadata/internal/providers/digitalocean" - "github.com/coreos/coreos-metadata/internal/providers/ec2" - "github.com/coreos/coreos-metadata/internal/providers/gce" - "github.com/coreos/coreos-metadata/internal/providers/openstackMetadata" - "github.com/coreos/coreos-metadata/internal/providers/oracleoci" - "github.com/coreos/coreos-metadata/internal/providers/packet" - "github.com/coreos/coreos-metadata/internal/providers/vagrant_virtualbox" - - "github.com/coreos/update-ssh-keys/authorized_keys_d" -) - -var ( - version = "was not built properly" - versionString = fmt.Sprintf("coreos-metadata %s", version) - - ErrUnknownProvider = errors.New("unknown provider") -) - -const ( - cmdlinePath = "/proc/cmdline" - cmdlineOEMFlag = "coreos.oem.id" -) - -func main() { - flags := struct { - attributes string - cmdline bool - hostname string - networkUnits string - provider string - sshKeys string - version bool - }{} - - flag.StringVar(&flags.attributes, "attributes", "", "The file into which the metadata attributes are written") - flag.BoolVar(&flags.cmdline, "cmdline", false, "Read the cloud provider from the kernel cmdline") - flag.StringVar(&flags.hostname, "hostname", "", "The file into which the hostname should be written") - flag.StringVar(&flags.networkUnits, "network-units", "", "The directory into which network units are written") - flag.StringVar(&flags.provider, "provider", "", "The name of the cloud provider") - flag.StringVar(&flags.sshKeys, "ssh-keys", "", "Update SSH keys for the given user") - flag.BoolVar(&flags.version, "version", false, "Print the version and exit") - - flag.Parse() - - if flags.version { - fmt.Println(versionString) - return - } - - if flags.cmdline && flags.provider == "" { - args, err := ioutil.ReadFile(cmdlinePath) - if err != nil { - fmt.Fprintf(os.Stderr, "could not read cmdline: %v\n", err) - os.Exit(2) - } - - flags.provider = parseCmdline(args) - } - - metadataFn, err := getMetadataProvider(flags.provider) - if err != nil { - fmt.Fprintf(os.Stderr, "invalid provider %q\n", flags.provider) - os.Exit(2) - } - - metadata, err := metadataFn() - if err != nil { - fmt.Fprintf(os.Stderr, "failed to fetch metadata: %v\n", err) - os.Exit(1) - } - - if err := writeMetadataAttributes(flags.attributes, metadata); err != nil { - fmt.Fprintf(os.Stderr, "failed to write metadata attributes: %v\n", err) - os.Exit(1) - } - - if err := writeMetadataKeys(flags.sshKeys, metadata); err != nil { - fmt.Fprintf(os.Stderr, "failed to write metadata keys: %v\n", err) - os.Exit(1) - } - - if err := writeHostname(flags.hostname, metadata); err != nil { - fmt.Fprintf(os.Stderr, "failed to write hostname: %v\n", err) - os.Exit(1) - } - - if err := writeNetworkUnits(flags.networkUnits, metadata); err != nil { - fmt.Fprintf(os.Stderr, "failed to write network units: %v\n", err) - os.Exit(1) - } -} - -func parseCmdline(cmdline []byte) (oem string) { - for _, arg := range strings.Split(string(cmdline), " ") { - parts := strings.SplitN(strings.TrimSpace(arg), "=", 2) - key := parts[0] - - if key != cmdlineOEMFlag { - continue - } - - if len(parts) == 2 { - oem = parts[1] - } - } - - return -} - -func getMetadataProvider(providerName string) (func() (providers.Metadata, error), error) { - switch providerName { - case "azure": - return azure.FetchMetadata, nil - case "digitalocean": - return digitalocean.FetchMetadata, nil - case "ec2": - return ec2.FetchMetadata, nil - case "gce": - return gce.FetchMetadata, nil - case "packet": - return packet.FetchMetadata, nil - case "openstack-metadata": - return openstackMetadata.FetchMetadata, nil - case "vagrant-virtualbox": - return vagrant_virtualbox.FetchMetadata, nil - case "cloudstack-configdrive": - return cloudStackConfigDrive.FetchMetadata, nil - case "cloudstack-metadata": - return cloudStackMetadata.FetchMetadata, nil - case "oracle-oci": - return oracleoci.FetchMetadata, nil - default: - return nil, ErrUnknownProvider - } -} - -func writeVariable(out *os.File, key string, value string) (err error) { - if len(value) > 0 { - _, err = fmt.Fprintf(out, "COREOS_%s=%s\n", key, value) - } - return -} - -func writeMetadataAttributes(attributes string, metadata providers.Metadata) error { - if attributes == "" { - return nil - } - - if err := os.MkdirAll(filepath.Dir(attributes), 0755); err != nil { - fmt.Fprintf(os.Stderr, "failed to create directory: %v\n", err) - os.Exit(1) - } - - out, err := os.Create(attributes) - if err != nil { - fmt.Fprintf(os.Stderr, "failed to create file: %v\n", err) - os.Exit(1) - } - defer out.Close() - - for key, value := range metadata.Attributes { - if err := writeVariable(out, key, value); err != nil { - return err - } - } - return nil -} - -func writeMetadataKeys(username string, metadata providers.Metadata) error { - if username == "" || metadata.SshKeys == nil { - return nil - } - - usr, err := user.Lookup(username) - if err != nil { - return fmt.Errorf("unable to lookup user %q: %v", username, err) - } - - akd, err := authorized_keys_d.Open(usr, true) - if err != nil { - return err - } - defer akd.Close() - - ks := strings.Join(metadata.SshKeys, "\n") + "\n" - if err := akd.Add("coreos-metadata", []byte(ks), true, true); err != nil { - return err - } - - return akd.Sync() -} - -func writeHostname(path string, metadata providers.Metadata) error { - if path == "" || metadata.Hostname == "" { - return nil - } - - err := os.MkdirAll(filepath.Dir(path), 0755) - if err != nil { - return err - } - - return ioutil.WriteFile(path, []byte(metadata.Hostname+"\n"), 0644) -} - -func writeNetworkUnits(root string, metadata providers.Metadata) error { - if root == "" || metadata.Network == nil { - return nil - } - - err := os.MkdirAll(root, 0755) - if err != nil { - return err - } - - for _, iface := range metadata.Network { - name := filepath.Join(root, iface.UnitName()) - err := ioutil.WriteFile(name, []byte(iface.NetworkConfig()), 0644) - if err != nil { - return err - } - } - - for _, device := range metadata.NetDev { - name := filepath.Join(root, device.UnitName()) - err := ioutil.WriteFile(name, []byte(device.NetdevConfig()), 0644) - if err != nil { - return err - } - } - - return nil -} diff --git a/internal/main_test.go b/internal/main_test.go deleted file mode 100644 index 7a665ad6..00000000 --- a/internal/main_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package main - -import ( - "reflect" - "testing" -) - -func TestGetMetadataProvider(t *testing.T) { - tests := []struct { - desc string - name string - err error - }{ - { - desc: "supported provider", - name: "digitalocean", - err: nil, - }, - { - desc: "unknown provider", - name: "not-supported", - err: ErrUnknownProvider, - }, - { - desc: "empty provider", - name: "", - err: ErrUnknownProvider, - }, - } - - for _, tt := range tests { - _, err := getMetadataProvider(tt.name) - if !reflect.DeepEqual(err, tt.err) { - t.Errorf("%s:\nwant: %v\n got: %v", tt.desc, tt.err, err) - } - } -} diff --git a/internal/providers/azure/azure.go b/internal/providers/azure/azure.go deleted file mode 100644 index dbf53d8e..00000000 --- a/internal/providers/azure/azure.go +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright 2015 CoreOS, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package azure - -import ( - "bufio" - "encoding/xml" - "fmt" - "net" - "os" - "strconv" - "strings" - "time" - - "github.com/coreos/coreos-metadata/internal/providers" - "github.com/coreos/coreos-metadata/internal/retry" -) - -const ( - AgentName = "com.coreos.metadata" - FabricProtocolVersion = "2012-11-30" - LeaseRetryInterval = 500 * time.Millisecond -) - -type metadata struct { - virtualIPv4 net.IP - dynamicIPv4 net.IP -} - -func FetchMetadata() (providers.Metadata, error) { - addr, err := getFabricAddress() - if err != nil { - return providers.Metadata{}, err - } - - if err := assertFabricCompatible(addr, FabricProtocolVersion); err != nil { - return providers.Metadata{}, err - } - - config, err := fetchSharedConfig(addr) - if err != nil { - return providers.Metadata{}, err - } - - return providers.Metadata{ - Attributes: map[string]string{ - "AZURE_IPV4_DYNAMIC": providers.String(config.dynamicIPv4), - "AZURE_IPV4_VIRTUAL": providers.String(config.virtualIPv4), - }, - }, nil -} - -func getClient() retry.Client { - client := retry.Client{ - InitialBackoff: time.Second, - MaxBackoff: time.Second * 5, - MaxAttempts: 10, - Header: map[string][]string{ - "x-ms-agent-name": {AgentName}, - "x-ms-version": {FabricProtocolVersion}, - "Content-Type": {"text/xml; charset=utf-8"}, - }, - } - - return client -} - -func findLease() (*os.File, error) { - ifaces, err := net.Interfaces() - if err != nil { - return nil, fmt.Errorf("could not list interfaces: %v", err) - } - - for { - for _, iface := range ifaces { - lease, err := os.Open(fmt.Sprintf("/run/systemd/netif/leases/%d", iface.Index)) - if os.IsNotExist(err) { - continue - } else if err != nil { - return nil, err - } else { - return lease, nil - } - } - - fmt.Printf("No leases found. Waiting...") - time.Sleep(LeaseRetryInterval) - } -} - -func getFabricAddress() (net.IP, error) { - lease, err := findLease() - if err != nil { - return nil, err - } - defer lease.Close() - - var rawEndpoint string - line := bufio.NewScanner(lease) - for line.Scan() { - parts := strings.Split(line.Text(), "=") - if parts[0] == "OPTION_245" && len(parts) == 2 { - rawEndpoint = parts[1] - break - } - } - - if len(rawEndpoint) == 0 || len(rawEndpoint) != 8 { - return nil, fmt.Errorf("fabric endpoint not found in leases") - } - - octets := make([]byte, 4) - for i := 0; i < 4; i++ { - octet, err := strconv.ParseUint(rawEndpoint[2*i:2*i+2], 16, 8) - if err != nil { - return nil, err - } - octets[i] = byte(octet) - } - - return net.IPv4(octets[0], octets[1], octets[2], octets[3]), nil -} - -func assertFabricCompatible(endpoint net.IP, desiredVersion string) error { - body, err := getClient().Getf("http://%s/?comp=versions", endpoint) - if err != nil { - return fmt.Errorf("failed to fetch versions: %v", err) - } - - versions := struct { - Supported struct { - Versions []string `xml:"Version"` - } - Preferred struct { - Version string - } - }{} - - if err := xml.Unmarshal(body, &versions); err != nil { - return fmt.Errorf("failed to unmarshal response: %v", err) - } - - for _, version := range versions.Supported.Versions { - if version == desiredVersion { - return nil - } - } - - return fmt.Errorf("fabric version %s is not compatible", desiredVersion) -} - -func fetchSharedConfig(endpoint net.IP) (metadata, error) { - client := getClient() - - body, err := client.Getf("http://%s/machine/?comp=goalstate", endpoint) - if err != nil { - return metadata{}, fmt.Errorf("failed to fetch goal state: %v", err) - } - - goal := struct { - Container struct { - RoleInstanceList struct { - RoleInstance struct { - Configuration struct { - SharedConfig string - } - } - } - } - }{} - - if err := xml.Unmarshal(body, &goal); err != nil { - return metadata{}, fmt.Errorf("failed to unmarshal response: %v", err) - } - - body, err = client.Get(goal.Container.RoleInstanceList.RoleInstance.Configuration.SharedConfig) - if err != nil { - return metadata{}, fmt.Errorf("failed to fetch shared config: %v", err) - } - - sharedConfig := struct { - Incarnation struct { - Instance string `xml:"instance,attr"` - } - Instances struct { - Instances []struct { - Id string `xml:"id,attr"` - Address string `xml:"address,attr"` - InputEndpoints struct { - Endpoints []struct { - LoadBalancedPublicAddress string `xml:"loadBalancedPublicAddress,attr"` - } `xml:"Endpoint"` - } - } `xml:"Instance"` - } - }{} - - if err := xml.Unmarshal(body, &sharedConfig); err != nil { - return metadata{}, err - } - - config := metadata{} - for _, i := range sharedConfig.Instances.Instances { - if i.Id == sharedConfig.Incarnation.Instance { - config.dynamicIPv4 = net.ParseIP(i.Address) - - for _, e := range i.InputEndpoints.Endpoints { - host, _, err := net.SplitHostPort(e.LoadBalancedPublicAddress) - if err == nil { - config.virtualIPv4 = net.ParseIP(host) - break - } - } - - break - } - } - - return config, nil -} diff --git a/internal/providers/cloudStackConfigDrive/cloudstack.go b/internal/providers/cloudStackConfigDrive/cloudstack.go deleted file mode 100644 index 37996527..00000000 --- a/internal/providers/cloudStackConfigDrive/cloudstack.go +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright 2017 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 cloudStackConfigDrive - -import ( - "fmt" - "path/filepath" - "strings" - - "errors" - "github.com/coreos/coreos-metadata/internal/providers" - "io/ioutil" - "os" - "os/exec" - "syscall" -) - -const ( - diskByLabelPath = "/dev/disk/by-label/" - configDriveMetadataPath = "/cloudstack/metadata/" - configDriveMetadataMountPoint = "/media/ConfigDrive/cloudstack/metadata/" -) - -func FetchMetadata() (providers.Metadata, error) { - m := providers.Metadata{} - m.Attributes = make(map[string]string) - mnt := "" - - if !fileExists(configDriveMetadataMountPoint) { - err := error(nil) - mnt, err = mountConfigDrive("config-2") - if err != nil { - if mnt, err = mountConfigDrive("CONFIG-2"); err != nil { - return m, err - } - } - defer unmountConfigDrive(mnt) - } else { - mnt = configDriveMetadataMountPoint - } - - if err := fetchAndSet("availability_zone.txt", mnt, "CLOUDSTACK_AVAILABILITY_ZONE", m.Attributes); err != nil { - return providers.Metadata{}, err - } - - if err := fetchAndSet("instance_id.txt", mnt, "CLOUDSTACK_INSTANCE_ID", m.Attributes); err != nil { - return providers.Metadata{}, err - } - - if err := fetchAndSet("service_offering.txt", mnt, "CLOUDSTACK_SERVICE_OFFERING", m.Attributes); err != nil { - return providers.Metadata{}, err - } - - if err := fetchAndSet("cloud_identifier.txt", mnt, "CLOUDSTACK_CLOUD_IDENTIFIER", m.Attributes); err != nil { - return providers.Metadata{}, err - } - - if err := fetchAndSet("local_hostname.txt", mnt, "CLOUDSTACK_LOCAL_HOSTNAME", m.Attributes); err != nil { - return providers.Metadata{}, err - } - - if err := fetchAndSet("vm_id.txt", mnt, "CLOUDSTACK_VM_ID", m.Attributes); err != nil { - return providers.Metadata{}, err - } - - keys, err := fetchKeys(mnt) - if err != nil { - return providers.Metadata{}, err - } - m.SshKeys = keys - return m, nil -} - -func fileExists(path string) bool { - _, err := os.Stat(path) - return (err == nil) -} - -func labelExists(label string) bool { - _, err := getPath(label) - return (err == nil) -} - -func getPath(label string) (string, error) { - path := diskByLabelPath + label - - if fileExists(path) { - return path, nil - } - - return "", fmt.Errorf("label not found: %s", label) -} - -func mountConfigDrive(label string) (string, error) { - if !labelExists(label) { - return "", errors.New("Not able to find config drive.") - } - path, err := getPath(label) - - mnt, err := ioutil.TempDir("", "coreos-metadata") - if err != nil { - return "", fmt.Errorf("failed to create temp directory: %v", err) - } - - cmd := exec.Command("/bin/mount", "-o", "ro", "-t", "auto", path, mnt) - if err := cmd.Run(); err != nil { - return "", err - } - - return mnt, nil -} - -func readFromConfigDrive(mnt string, file string) (string, bool, error) { - configDrivePath := filepath.Join(mnt, configDriveMetadataPath, file) - - if !fileExists(configDrivePath) { - return "", false, nil - } - - output, err := ioutil.ReadFile(configDrivePath) - - if err != nil { - return "", false, err - } - - return string(output), true, nil -} - -func unmountConfigDrive(mnt string) { - defer os.Remove(mnt) - defer syscall.Unmount(mnt, 0) -} - -func fetchAndSet(key, mnt string, attrKey string, attributes map[string]string) error { - val, ok, err := readFromConfigDrive(mnt, key) - if err != nil { - return err - } - if !ok || val == "" { - return nil - } - attributes[attrKey] = val - return nil -} - -func fetchKeys(mnt string) ([]string, error) { - keysListBlob, ok, err := readFromConfigDrive(mnt, "public_keys.txt") - if err != nil { - return nil, err - } - if !ok || keysListBlob == "" { - return nil, nil - } - keys := strings.Split(keysListBlob, "\n") - - return keys, nil -} diff --git a/internal/providers/cloudStackMetadata/cloudstack.go b/internal/providers/cloudStackMetadata/cloudstack.go deleted file mode 100644 index 03908c42..00000000 --- a/internal/providers/cloudStackMetadata/cloudstack.go +++ /dev/null @@ -1,139 +0,0 @@ -package cloudStackMetadata - -import ( - "bufio" - "fmt" - "net" - "os" - "strings" - "time" - - "github.com/coreos/coreos-metadata/internal/providers" - "github.com/coreos/coreos-metadata/internal/retry" -) - -const ( - LeaseRetryInterval = 500 * time.Millisecond -) - -func FetchMetadata() (providers.Metadata, error) { - m := providers.Metadata{} - m.Attributes = make(map[string]string) - - metadata := map[string]string{ - "instance-id": "CLOUDSTACK_INSTANCE_ID", - "local-hostname": "CLOUDSTACK_LOCAL_HOSTNAME", - "public-hostname": "CLOUDSTACK_PUBLIC_HOSTNAME", - "availability-zone": "CLOUDSTACK_AVAILABILITY_ZONE", - "public-ipv4": "CLOUDSTACK_IPV4_PUBLIC", - "local-ipv4": "CLOUDSTACK_IPV4_LOCAL", - "service-offering": "CLOUDSTACK_SERVICE_OFFERING", - "cloud-identifier": "CLOUDSTACK_CLOUD_IDENTIFIER", - "vm-id": "CLOUDSTACK_VM_ID", - } - - for key, value := range metadata { - if err := fetchAndSet(key, value, m.Attributes); err != nil { - return providers.Metadata{}, err - } - } - - if err := fetchAndSet("local-hostname", "CLOUDSTACK_HOSTNAME", m.Attributes); err != nil { - return providers.Metadata{}, err - } - - keys, err := fetchKeys("public-keys") - if err != nil { - return providers.Metadata{}, err - } - m.SshKeys = keys - - return m, nil -} - -func fetchMetadata(key string) (string, bool, error) { - addr, err := getDHCPServerAddress() - if err != nil { - return "", false, err - } - - url := "http://" + addr + "/latest/meta-data/" - body, err := retry.Client{ - InitialBackoff: time.Second, - MaxBackoff: time.Second * 5, - MaxAttempts: 10, - }.Get(url + key) - return string(body), (body != nil), err -} - -func fetchAndSet(key, attrKey string, attributes map[string]string) error { - val, ok, err := fetchMetadata(key) - if err != nil { - return err - } - if !ok || val == "" { - return nil - } - attributes[attrKey] = val - return nil -} - -func fetchKeys(key string) ([]string, error) { - keysListBlob, ok, err := fetchMetadata(key) - if err != nil { - return nil, err - } - if !ok || keysListBlob == "" { - return nil, nil - } - keys := strings.Split(keysListBlob, "\n") - - return keys, nil -} - -func findLease() (*os.File, error) { - ifaces, err := net.Interfaces() - if err != nil { - return nil, fmt.Errorf("could not list interfaces: %v", err) - } - - for { - for _, iface := range ifaces { - lease, err := os.Open(fmt.Sprintf("/run/systemd/netif/leases/%d", iface.Index)) - if os.IsNotExist(err) { - continue - } else if err != nil { - return nil, err - } else { - return lease, nil - } - } - - fmt.Printf("No leases found. Waiting...") - time.Sleep(LeaseRetryInterval) - } -} - -func getDHCPServerAddress() (string, error) { - lease, err := findLease() - if err != nil { - return "", err - } - defer lease.Close() - - var address string - line := bufio.NewScanner(lease) - for line.Scan() { - parts := strings.Split(line.Text(), "=") - if parts[0] == "SERVER_ADDRESS" && len(parts) == 2 { - address = parts[1] - break - } - } - - if len(address) == 0 { - return "", fmt.Errorf("dhcp server address not found in leases") - } - - return address, nil -} diff --git a/internal/providers/digitalocean/digitalocean.go b/internal/providers/digitalocean/digitalocean.go deleted file mode 100644 index 9cf29aae..00000000 --- a/internal/providers/digitalocean/digitalocean.go +++ /dev/null @@ -1,276 +0,0 @@ -// Copyright 2016 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 digitalocean - -import ( - "encoding/json" - "fmt" - "net" - "time" - - "github.com/coreos/coreos-metadata/internal/providers" - "github.com/coreos/coreos-metadata/internal/retry" -) - -type Address struct { - IPAddress string `json:"ip_address"` - Netmask string `json:"netmask"` - Cidr int `json:"cidr"` - Gateway string `json:"gateway"` -} - -type Interface struct { - IPv4 *Address `json:"ipv4"` - IPv6 *Address `json:"ipv6"` - AnchorIPv4 *Address `json:"anchor_ipv4"` - MAC string `json:"mac"` - Type string `json:"type"` -} - -type Interfaces struct { - Public []Interface `json:"public"` - Private []Interface `json:"private"` -} - -type DNS struct { - Nameservers []string `json:"nameservers"` -} - -type Metadata struct { - Hostname string `json:"hostname"` - Interfaces Interfaces `json:"interfaces"` - PublicKeys []string `json:"public_keys"` - Region string `json:"region"` - DNS DNS `json:"dns"` -} - -func FetchMetadata() (providers.Metadata, error) { - body, err := retry.Client{ - InitialBackoff: time.Second, - MaxBackoff: time.Second * 5, - MaxAttempts: 10, - }.Get("http://169.254.169.254/metadata/v1.json") - - if err != nil { - return providers.Metadata{}, fmt.Errorf("failed to fetch metadata: %v", err) - } - - var m Metadata - if err = json.Unmarshal(body, &m); err != nil { - return providers.Metadata{}, fmt.Errorf("failed to unmarshal metadata: %v", err) - } - - network, err := parseNetwork(m) - if err != nil { - return providers.Metadata{}, fmt.Errorf("failed to parse network config from metadata: %v", err) - } - - return providers.Metadata{ - Attributes: parseAttributes(m), - Hostname: m.Hostname, - Network: network, - SshKeys: m.PublicKeys, - }, nil -} - -func parseAttributes(metadata Metadata) map[string]string { - attrs := map[string]string{ - "DIGITALOCEAN_HOSTNAME": metadata.Hostname, - "DIGITALOCEAN_REGION": metadata.Region, - } - - for i, iface := range metadata.Interfaces.Public { - if iface.IPv4 != nil { - attrs[fmt.Sprintf("DIGITALOCEAN_IPV4_PUBLIC_%d", i)] = - providers.String(net.ParseIP(iface.IPv4.IPAddress)) - } - if iface.IPv6 != nil { - attrs[fmt.Sprintf("DIGITALOCEAN_IPV6_PUBLIC_%d", i)] = - providers.String(net.ParseIP(iface.IPv6.IPAddress)) - } - if iface.AnchorIPv4 != nil { - attrs[fmt.Sprintf("DIGITALOCEAN_IPV4_ANCHOR_%d", i)] = - providers.String(net.ParseIP(iface.AnchorIPv4.IPAddress)) - } - } - - for i, iface := range metadata.Interfaces.Private { - if iface.IPv4 != nil { - attrs[fmt.Sprintf("DIGITALOCEAN_IPV4_PRIVATE_%d", i)] = - providers.String(net.ParseIP(iface.IPv4.IPAddress)) - } - if iface.IPv6 != nil { - attrs[fmt.Sprintf("DIGITALOCEAN_IPV6_PRIVATE_%d", i)] = - providers.String(net.ParseIP(iface.IPv6.IPAddress)) - } - } - - return attrs -} - -func parseNetwork(metadata Metadata) ([]providers.NetworkInterface, error) { - servers, err := parseNameservers(metadata.DNS.Nameservers) - if err != nil { - return nil, err - } - - ifaceConfigs := map[string]providers.NetworkInterface{} - for _, iface := range append(metadata.Interfaces.Private, metadata.Interfaces.Public...) { - mac, err := net.ParseMAC(iface.MAC) - if err != nil { - return nil, fmt.Errorf("could not parse %q as MAC address", iface.MAC) - } - addrs, routes, err := parseInterface(iface, iface.Type == "public") - if err != nil { - return nil, err - } - - ifaceConfigs[iface.MAC] = providers.NetworkInterface{ - HardwareAddress: mac, - Nameservers: servers, - IPAddresses: append(ifaceConfigs[iface.MAC].IPAddresses, addrs...), - Routes: append(ifaceConfigs[iface.MAC].Routes, routes...), - } - } - - var ifaces []providers.NetworkInterface - for _, iface := range ifaceConfigs { - ifaces = append(ifaces, iface) - } - return ifaces, nil -} - -func parseInterface(iface Interface, public bool) ([]net.IPNet, []providers.NetworkRoute, error) { - var addrs []net.IPNet - var routes []providers.NetworkRoute - - if iface.IPv4 != nil { - addr, err := parseIPv4Address(*iface.IPv4) - if err != nil { - return nil, nil, err - } - addrs = append(addrs, addr) - - route, err := parseRoute(iface.IPv4.Gateway, addr) - if err != nil { - return nil, nil, err - } - - routes = append(routes, route) - - if public { - routes = append(routes, providers.NetworkRoute{ - Destination: net.IPNet{ - IP: net.IPv4zero, - Mask: net.IPMask(net.IPv4zero), - }, - Gateway: route.Gateway, - }) - } - } - if iface.IPv6 != nil { - addr, err := parseIPv6Address(*iface.IPv6) - if err != nil { - return nil, nil, err - } - addrs = append(addrs, addr) - - route, err := parseRoute(iface.IPv6.Gateway, addr) - if err != nil { - return nil, nil, err - } - - routes = append(routes, route) - if public { - routes = append(routes, providers.NetworkRoute{ - Destination: net.IPNet{ - IP: net.IPv6zero, - Mask: net.IPMask(net.IPv6zero), - }, - Gateway: route.Gateway, - }) - } - } - if iface.AnchorIPv4 != nil { - addr, err := parseIPv4Address(*iface.AnchorIPv4) - if err != nil { - return nil, nil, err - } - addrs = append(addrs, addr) - - route, err := parseRoute(iface.AnchorIPv4.Gateway, addr) - if err != nil { - return nil, nil, err - } - - routes = append(routes, route) - } - - return addrs, routes, nil -} - -func parseIPv4Address(address Address) (net.IPNet, error) { - ip := net.ParseIP(address.IPAddress) - if ip == nil { - return net.IPNet{}, fmt.Errorf("could not parse %q as IPv4 address", address.IPAddress) - } - - mask := net.ParseIP(address.Netmask) - if mask == nil { - return net.IPNet{}, fmt.Errorf("could not parse %q as IPv4 mask", address.Netmask) - } - - return net.IPNet{ - IP: ip, - Mask: net.IPMask(mask.To4()), - }, nil -} - -func parseIPv6Address(address Address) (net.IPNet, error) { - ip := net.ParseIP(address.IPAddress) - if ip == nil { - return net.IPNet{}, fmt.Errorf("could not parse %q as IPv6 address", address.IPAddress) - } - - return net.IPNet{ - IP: ip, - Mask: net.CIDRMask(address.Cidr, net.IPv6len*8), - }, nil -} - -func parseRoute(gateway string, destination net.IPNet) (providers.NetworkRoute, error) { - addr := net.ParseIP(gateway) - if addr == nil { - return providers.NetworkRoute{}, fmt.Errorf("could not parse %q as gateway address", gateway) - } - - return providers.NetworkRoute{ - Destination: destination, - Gateway: addr, - }, nil -} - -func parseNameservers(servers []string) ([]net.IP, error) { - var addrs []net.IP - for _, server := range servers { - addr := net.ParseIP(server) - if addr == nil { - return nil, fmt.Errorf("could not parse %q as IP address", server) - } - - addrs = append(addrs, addr) - } - return addrs, nil -} diff --git a/internal/providers/ec2/ec2.go b/internal/providers/ec2/ec2.go deleted file mode 100644 index 1d165752..00000000 --- a/internal/providers/ec2/ec2.go +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright 2015 CoreOS, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package ec2 - -import ( - "bufio" - "encoding/json" - "fmt" - "net" - "strings" - "time" - - "github.com/coreos/coreos-metadata/internal/providers" - "github.com/coreos/coreos-metadata/internal/retry" -) - -type instanceIdDoc struct { - PrivateIp string `json:"privateIp"` - DevpayProductCodes string `json:"devpayProductCodes"` - AvailabilityZone string `json:"availabilityZone"` - Version string `json:"version"` - Region string `json:"region"` - PendingTime string `json:"pendingTime"` - InstanceId string `json:"instanceId"` - BillingProducts string `json:"billingProducts"` - InstanceType string `json:"instanceType"` - AccountId string `json:"accountId"` - Architecture string `json:"architecture"` - KernelId string `json:"kernelId"` - RamdiskId string `json:"ramdiskId"` - ImageId string `json:"imageId"` -} - -func FetchMetadata() (providers.Metadata, error) { - instanceId, _, err := fetchString("meta-data/instance-id") - if err != nil { - return providers.Metadata{}, err - } - - public, err := fetchIP("meta-data/public-ipv4") - if err != nil { - return providers.Metadata{}, err - } - local, err := fetchIP("meta-data/local-ipv4") - if err != nil { - return providers.Metadata{}, err - } - hostname, _, err := fetchString("meta-data/hostname") - if err != nil { - return providers.Metadata{}, err - } - availabilityZone, _, err := fetchString("meta-data/placement/availability-zone") - if err != nil { - return providers.Metadata{}, err - } - - instanceIdDocBlob, _, err := fetchString("dynamic/instance-identity/document") - if err != nil { - return providers.Metadata{}, err - } - var instanceIdDoc instanceIdDoc - err = json.Unmarshal([]byte(instanceIdDocBlob), &instanceIdDoc) - if err != nil { - return providers.Metadata{}, err - } - - sshKeys, err := fetchSshKeys() - if err != nil { - return providers.Metadata{}, err - } - - return providers.Metadata{ - Attributes: map[string]string{ - "EC2_INSTANCE_ID": instanceId, - "EC2_IPV4_LOCAL": providers.String(local), - "EC2_IPV4_PUBLIC": providers.String(public), - "EC2_HOSTNAME": hostname, - "EC2_AVAILABILITY_ZONE": availabilityZone, - "EC2_REGION": instanceIdDoc.Region, - }, - Hostname: hostname, - SshKeys: sshKeys, - }, nil -} - -func fetchString(key string) (string, bool, error) { - body, err := retry.Client{ - InitialBackoff: time.Second, - MaxBackoff: time.Second * 5, - MaxAttempts: 10, - }.Get("http://169.254.169.254/2009-04-04/" + key) - return string(body), (body != nil), err -} - -func fetchIP(key string) (net.IP, error) { - str, present, err := fetchString(key) - if err != nil { - return nil, err - } - - if !present { - return nil, nil - } - - if ip := net.ParseIP(str); ip != nil { - return ip, nil - } else { - return nil, fmt.Errorf("couldn't parse %q as IP address", str) - } -} - -func fetchSshKeys() ([]string, error) { - keydata, present, err := fetchString("meta-data/public-keys") - if err != nil { - return nil, fmt.Errorf("error reading keys: %v", err) - } - - if !present { - return nil, nil - } - - scanner := bufio.NewScanner(strings.NewReader(keydata)) - keynames := []string{} - for scanner.Scan() { - keynames = append(keynames, scanner.Text()) - } - if err := scanner.Err(); err != nil { - return nil, fmt.Errorf("error parsing keys: %v", err) - } - - keyIDs := make(map[string]string) - for _, keyname := range keynames { - tokens := strings.SplitN(keyname, "=", 2) - if len(tokens) != 2 { - return nil, fmt.Errorf("malformed public key: %q", keyname) - } - keyIDs[tokens[1]] = tokens[0] - } - - keys := []string{} - for _, id := range keyIDs { - sshkey, _, err := fetchString(fmt.Sprintf("meta-data/public-keys/%s/openssh-key", id)) - if err != nil { - return nil, err - } - keys = append(keys, sshkey) - } - - return keys, nil -} diff --git a/internal/providers/gce/gce.go b/internal/providers/gce/gce.go deleted file mode 100644 index b15d5da8..00000000 --- a/internal/providers/gce/gce.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2016 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 gce - -import ( - "fmt" - "net" - "strconv" - "strings" - "time" - - "github.com/coreos/coreos-metadata/internal/providers" - "github.com/coreos/coreos-metadata/internal/retry" -) - -func FetchMetadata() (providers.Metadata, error) { - public, err := fetchIP("instance/network-interfaces/0/access-configs/0/external-ip") - if err != nil { - return providers.Metadata{}, err - } - local, err := fetchIP("instance/network-interfaces/0/ip") - if err != nil { - return providers.Metadata{}, err - } - hostname, _, err := fetchString("instance/hostname") - if err != nil { - return providers.Metadata{}, err - } - sshKeys, err := fetchAllSshKeys() - if err != nil { - return providers.Metadata{}, err - } - - return providers.Metadata{ - Attributes: map[string]string{ - "GCE_IP_LOCAL_0": providers.String(local), - "GCE_IP_EXTERNAL_0": providers.String(public), - "GCE_HOSTNAME": hostname, - }, - Hostname: hostname, - SshKeys: sshKeys, - }, nil -} - -func fetchAllSshKeys() ([]string, error) { - deprecatedInstanceSshKeys, err := fetchSshKeys("instance/attributes/sshKeys") - if err != nil { - return nil, err - } - - if deprecatedInstanceSshKeys != nil { - return deprecatedInstanceSshKeys, nil - } - - instanceSshKeys, err := fetchSshKeys("instance/attributes/ssh-keys") - if err != nil { - return nil, err - } - - blockProjectKeys, _, err := fetchString("instance/attributes/block-project-ssh-keys") - if err != nil { - return nil, err - } - - if block, err := strconv.ParseBool(blockProjectKeys); err == nil && block { - return instanceSshKeys, nil - } - - projectSshKeys, err := fetchSshKeys("project/attributes/sshKeys") - if err != nil { - return nil, err - } - - return append(instanceSshKeys, projectSshKeys...), nil -} - -func fetchString(key string) (string, bool, error) { - body, err := retry.Client{ - InitialBackoff: time.Second, - MaxBackoff: time.Second * 5, - MaxAttempts: 10, - Header: map[string][]string{ - "Metadata-Flavor": {"Google"}, - }, - }.Get("http://metadata.google.internal/computeMetadata/v1/" + key) - - // Google's metadata service returns a 200 success even if there is no - // resource. Instead of checking to see if there is a body, check to see - // if the body is empty. - return string(body), len(body) > 0, err -} - -func fetchIP(key string) (net.IP, error) { - str, present, err := fetchString(key) - if err != nil { - return nil, err - } - - if !present { - return nil, nil - } - - if ip := net.ParseIP(str); ip != nil { - return ip, nil - } else { - return nil, fmt.Errorf("couldn't parse %q as IP address", str) - } -} - -func fetchSshKeys(prefix string) ([]string, error) { - keydata, present, err := fetchString(prefix) - if err != nil { - return nil, fmt.Errorf("error reading keys: %v", err) - } - - if !present { - return nil, nil - } - - keys := []string{} - for _, key := range strings.Split(keydata, "\n") { - if len(key) == 0 { - continue - } - - tokens := strings.SplitN(key, ":", 2) - if len(tokens) != 2 { - return nil, fmt.Errorf("malformed public key '%s'", key) - } - keys = append(keys, tokens[1]) - } - - return keys, nil -} diff --git a/internal/providers/metadata.go b/internal/providers/metadata.go deleted file mode 100644 index fc0e7ec6..00000000 --- a/internal/providers/metadata.go +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2016 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 providers - -import ( - "fmt" - "net" - "reflect" -) - -type Metadata struct { - Attributes map[string]string - Hostname string - SshKeys []string - Network []NetworkInterface - NetDev []NetworkDevice -} - -type NetworkInterface struct { - Name string - HardwareAddress net.HardwareAddr - Priority int - Nameservers []net.IP - IPAddresses []net.IPNet - Routes []NetworkRoute - Bond string -} - -type NetworkRoute struct { - Destination net.IPNet - Gateway net.IP -} - -type NetworkDevice struct { - Name string - Kind string - HardwareAddress net.HardwareAddr - Priority int - Sections []Section -} - -type Section struct { - Name string - Attributes [][2]string -} - -func (i NetworkInterface) UnitName() string { - priority := i.Priority - if priority == 0 { - priority = 10 - } - name := i.Name - if name == "" { - name = i.HardwareAddress.String() - } - return fmt.Sprintf("%02d-%s.network", priority, name) -} - -func (i NetworkInterface) NetworkConfig() string { - config := "[Match]\n" - if i.Name != "" { - config += fmt.Sprintf("Name=%s\n", i.Name) - } - if i.HardwareAddress != nil { - config += fmt.Sprintf("MACAddress=%s\n", i.HardwareAddress) - } - - config += "\n[Network]\n" - for _, nameserver := range i.Nameservers { - config += fmt.Sprintf("DNS=%s\n", nameserver) - } - if i.Bond != "" { - config += fmt.Sprintf("Bond=%s\n", i.Bond) - } - - for _, addr := range i.IPAddresses { - config += fmt.Sprintf("\n[Address]\nAddress=%s\n", addr.String()) - } - for _, route := range i.Routes { - config += fmt.Sprintf("\n[Route]\nDestination=%s\nGateway=%s\n", route.Destination.String(), route.Gateway) - } - - return config -} - -func (d NetworkDevice) UnitName() string { - priority := d.Priority - if priority == 0 { - priority = 10 - } - return fmt.Sprintf("%02d-%s.netdev", priority, d.Name) -} - -func (d NetworkDevice) NetdevConfig() string { - config := fmt.Sprintf("[NetDev]\nName=%s\nKind=%s\nMACAddress=%s\n", d.Name, d.Kind, d.HardwareAddress) - - for _, section := range d.Sections { - config += fmt.Sprintf("\n[%s]\n", section.Name) - for _, attr := range section.Attributes { - config += fmt.Sprintf("%s=%s\n", attr[0], attr[1]) - } - } - - return config -} - -func String(s fmt.Stringer) string { - if reflect.ValueOf(s).IsNil() { - return "" - } - - return s.String() -} diff --git a/internal/providers/openstackMetadata/metadata.go b/internal/providers/openstackMetadata/metadata.go deleted file mode 100644 index 684fe054..00000000 --- a/internal/providers/openstackMetadata/metadata.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2017 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 openstackMetadata - -import ( - "fmt" - "path" - "strings" - "time" - - "github.com/coreos/coreos-metadata/internal/providers" - "github.com/coreos/coreos-metadata/internal/retry" -) - -const ( - metadataEndpoint = "http://169.254.169.254/latest/meta-data/" -) - -func FetchMetadata() (providers.Metadata, error) { - m := providers.Metadata{} - m.Attributes = make(map[string]string) - - if err := fetchAndSet("instance-id", "OPENSTACK_INSTANCE_ID", m.Attributes); err != nil { - return providers.Metadata{}, err - } - - if err := fetchAndSet("local-ipv4", "OPENSTACK_IPV4_LOCAL", m.Attributes); err != nil { - return providers.Metadata{}, err - } - - if err := fetchAndSet("public-ipv4", "OPENSTACK_IPV4_PUBLIC", m.Attributes); err != nil { - return providers.Metadata{}, err - } - - if err := fetchAndSet("hostname", "OPENSTACK_HOSTNAME", m.Attributes); err != nil { - return providers.Metadata{}, err - } - - keys, err := fetchKeys() - if err != nil { - return providers.Metadata{}, err - } - m.SshKeys = keys - - return m, nil -} - -func fetchAndSet(key, attrKey string, attributes map[string]string) error { - val, ok, err := fetchMetadata(key) - if err != nil { - return err - } - if !ok || val == "" { - return nil - } - attributes[attrKey] = val - return nil -} - -func fetchKeys() ([]string, error) { - keysListBlob, ok, err := fetchMetadata("public-keys") - if err != nil { - return nil, err - } - if !ok || keysListBlob == "" { - return nil, nil - } - keysList := strings.Split(keysListBlob, "\n") - - var keys []string - - if len(keysList) > 0 { - keyID := keysList[0] - keyTokens := strings.Split(keyID, "=") - if len(keyTokens) != 2 { - return nil, fmt.Errorf("error parsing keyID %s", keyID) - } - keyNum := keyTokens[0] - // keyTokens[1] is the name of the key, but is currently unused here - key, ok, err := fetchMetadata(path.Join("public-keys", keyNum, "openssh-key")) - if err != nil { - return nil, err - } - if !ok || key == "" { - return nil, fmt.Errorf("problem fetching key %s", keyID) - } - keys = append(keys, key) - } - return keys, nil -} - -func fetchMetadata(key string) (string, bool, error) { - body, err := retry.Client{ - InitialBackoff: time.Second, - MaxBackoff: time.Second * 5, - MaxAttempts: 10, - }.Get(metadataEndpoint + key) - return string(body), (body != nil), err -} diff --git a/internal/providers/oracleoci/oracleoci.go b/internal/providers/oracleoci/oracleoci.go deleted file mode 100644 index 9e116b8b..00000000 --- a/internal/providers/oracleoci/oracleoci.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2017 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 oracleoci - -import ( - "encoding/json" - "strings" - "time" - - "github.com/coreos/coreos-metadata/internal/providers" - "github.com/coreos/coreos-metadata/internal/retry" -) - -type instanceData struct { - AvailabilityDomain string `json:"availabilityDomain"` - CompartmentId string `json:"compartmentId"` - DisplayName string `json:"displayName"` - Id string `json:"id"` - Image string `json:"image"` - Region string `json:"region"` - Shape string `json:"shape"` - TimeCreated uint64 `json:"timeCreated"` - Metadata metadata `json:"metadata"` -} - -type metadata struct { - SshAuthorizedKeys string `json:"ssh_authorized_keys"` -} - -func FetchMetadata() (providers.Metadata, error) { - instanceDataBlob, err := retry.Client{ - InitialBackoff: time.Second, - MaxBackoff: time.Second * 5, - MaxAttempts: 10, - }.Get("http://169.254.169.254/opc/v1/instance/") - if err != nil { - return providers.Metadata{}, err - } - - var data instanceData - err = json.Unmarshal(instanceDataBlob, &data) - if err != nil { - return providers.Metadata{}, err - } - return providers.Metadata{ - Attributes: map[string]string{ - "ORACLE_OCI_DISPLAY_NAME": data.DisplayName, - "ORACLE_OCI_INSTANCE_ID": data.Id, - "ORACLE_OCI_REGION": data.Region, - }, - SshKeys: strings.Split(data.Metadata.SshAuthorizedKeys, "\n"), - }, nil -} diff --git a/internal/providers/packet/packet.go b/internal/providers/packet/packet.go deleted file mode 100644 index da7db958..00000000 --- a/internal/providers/packet/packet.go +++ /dev/null @@ -1,224 +0,0 @@ -// Copyright 2016 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 ( - "bufio" - "encoding/json" - "errors" - "fmt" - "net" - "os" - "strings" - "time" - - "github.com/coreos/coreos-metadata/internal/providers" - "github.com/coreos/coreos-metadata/internal/retry" - "github.com/packethost/packngo/metadata" -) - -func FetchMetadata() (providers.Metadata, error) { - body, err := retry.Client{ - InitialBackoff: time.Second, - MaxBackoff: time.Second * 5, - MaxAttempts: 10, - }.Get(metadata.BaseURL + "/metadata") - if err != nil { - return providers.Metadata{}, err - } - - var data struct { - Error string `json:"error"` - PhoneHomeURL string `json:"phone_home_url"` - *metadata.CurrentDevice - } - - if err := json.Unmarshal(body, &data); err != nil { - return providers.Metadata{}, err - } - - if data.Error != "" { - return providers.Metadata{}, errors.New(data.Error) - } - - network, netdev, err := parseNetwork(data.Network) - if err != nil { - return providers.Metadata{}, fmt.Errorf("failed to parse network config from metadata: %v", err) - } - - attrs := getNetworkAttrs(data.Network) - attrs["PACKET_HOSTNAME"] = data.Hostname - attrs["PACKET_PHONE_HOME_URL"] = data.PhoneHomeURL - - return providers.Metadata{ - Attributes: attrs, - Hostname: data.Hostname, - SshKeys: data.SSHKeys, - Network: network, - NetDev: netdev, - }, nil -} - -func parseNetwork(network metadata.NetworkInfo) ([]providers.NetworkInterface, []providers.NetworkDevice, error) { - nameservers, err := getDNSServers() - if err != nil { - return nil, nil, fmt.Errorf("failed to obtain DNS servers: %v", err) - } - - ifaces := []providers.NetworkInterface{} - bondDev := "bond0" - - for _, iface := range network.Interfaces { - mac, err := net.ParseMAC(iface.MAC) - if err != nil { - return nil, nil, fmt.Errorf("parsing MAC address %q: %v", iface.MAC, err) - } - - ifaces = append(ifaces, providers.NetworkInterface{ - HardwareAddress: mac, - Bond: bondDev, - }) - } - - iface := providers.NetworkInterface{ - Name: bondDev, - Priority: 5, - Nameservers: nameservers, - } - for _, addr := range network.Addresses { - addrlen := 16 - if addr.Address.To4() != nil { - addrlen = 4 - } - dest := net.IPNet{ - IP: make([]byte, addrlen), - Mask: make([]byte, addrlen), - } - if !addr.Public { - if addrlen == 16 { - // private IPv6 address?? - continue - } - dest = net.IPNet{ - IP: net.IPv4(10, 0, 0, 0), - Mask: net.IPMask(net.IPv4(255, 0, 0, 0)), - } - } - - iface.IPAddresses = append(iface.IPAddresses, net.IPNet{ - IP: addr.Address, - Mask: []byte(addr.NetworkMask), - }) - iface.Routes = append(iface.Routes, providers.NetworkRoute{ - Destination: dest, - Gateway: addr.Gateway, - }) - } - ifaces = append(ifaces, iface) - - attrs := [][2]string{ - // yay hardcoded stuff - {"TransmitHashPolicy", "layer3+4"}, - {"MIIMonitorSec", ".1"}, - {"UpDelaySec", ".2"}, - {"DownDelaySec", ".2"}, - {"Mode", network.BondingMode().String()}, - } - if network.BondingMode() == metadata.BondingLACP { - attrs = append(attrs, [2]string{"LACPTransmitRate", "fast"}) - } - netdevs := []providers.NetworkDevice{ - { - Name: bondDev, - Kind: "bond", - HardwareAddress: ifaces[0].HardwareAddress, - Priority: 5, - Sections: []providers.Section{ - { - Name: "Bond", - Attributes: attrs, - }, - }, - }, - } - - return ifaces, netdevs, nil -} - -func getDNSServers() ([]net.IP, error) { - state, err := os.Open("/run/systemd/netif/state") - if err != nil { - return nil, err - } - defer state.Close() - - var addrs []net.IP - line := bufio.NewScanner(state) - for line.Scan() { - parts := strings.Split(line.Text(), "=") - if len(parts) == 2 && parts[0] == "DNS" { - for _, addr := range strings.Split(parts[1], " ") { - ip := net.ParseIP(addr) - if ip != nil { - addrs = append(addrs, ip) - } - } - } - } - if len(addrs) == 0 { - return nil, fmt.Errorf("no DNS servers in %v", state.Name()) - } - return addrs, nil -} - -func getNetworkAttrs(network metadata.NetworkInfo) map[string]string { - var publicIPv4, privateIPv4, publicIPv6, privateIPv6 []net.IP - - for _, addr := range network.Addresses { - switch { - case addr.Family == 4 && addr.Public: - publicIPv4 = append(publicIPv4, addr.Address) - - case addr.Family == 4 && !addr.Public: - privateIPv4 = append(privateIPv4, addr.Address) - - case addr.Family == 6 && addr.Public: - publicIPv6 = append(publicIPv6, addr.Address) - - case addr.Family == 6 && !addr.Public: - privateIPv6 = append(privateIPv6, addr.Address) - } - } - - addresses := make(map[string]string) - - for i, ip := range publicIPv4 { - addresses[fmt.Sprintf("PACKET_IPV4_PUBLIC_%d", i)] = providers.String(ip) - } - - for i, ip := range privateIPv4 { - addresses[fmt.Sprintf("PACKET_IPV4_PRIVATE_%d", i)] = providers.String(ip) - } - - for i, ip := range publicIPv6 { - addresses[fmt.Sprintf("PACKET_IPV6_PUBLIC_%d", i)] = providers.String(ip) - } - - for i, ip := range privateIPv6 { - addresses[fmt.Sprintf("PACKET_IPV6_PRIVATE_%d", i)] = providers.String(ip) - } - - return addresses -} diff --git a/internal/providers/vagrant_virtualbox/vagrant_virtualbox.go b/internal/providers/vagrant_virtualbox/vagrant_virtualbox.go deleted file mode 100644 index 1bcf621b..00000000 --- a/internal/providers/vagrant_virtualbox/vagrant_virtualbox.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2017 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 vagrant_virtualbox - -import ( - "errors" - "fmt" - "net" - "os" - "time" - - "github.com/coreos/coreos-metadata/internal/providers" -) - -type metadata struct { - privateIPv4 net.IP - hostname string -} - -func FetchMetadata() (providers.Metadata, error) { - config, err := genConfig() - if err != nil { - return providers.Metadata{}, err - } - - return providers.Metadata{ - Attributes: map[string]string{ - "VAGRANT_VIRTUALBOX_PRIVATE_IPV4": providers.String(config.privateIPv4), - "VAGRANT_VIRTUALBOX_HOSTNAME": config.hostname, - }, - }, nil -} - -func genConfig() (metadata, error) { - config := metadata{} - var err error - config.privateIPv4, err = getIP() - if err != nil { - return metadata{}, err - } - config.hostname, err = os.Hostname() - if err != nil { - return metadata{}, err - } - return config, nil -} - -func getIP() (net.IP, error) { - // eth1 may not yet be available or configured; wait - maxAttempts := 30 - for attempt := 1; attempt <= maxAttempts; attempt++ { - interfaces, err := net.Interfaces() - if err != nil { - return nil, err - } - for _, iface := range interfaces { - if iface.Name == "eth1" { - addrs, err := iface.Addrs() - if err != nil { - return nil, err - } - for _, addr := range addrs { - ip, _, err := net.ParseCIDR(addr.String()) - if err != nil { - return nil, err - } - if ip.To4() != nil { - return ip, nil - } - } - } - } - fmt.Println("eth1 not found; waiting 2 seconds") - time.Sleep(2 * time.Second) - } - return nil, errors.New("eth1 was not found!") -} diff --git a/internal/retry/client.go b/internal/retry/client.go deleted file mode 100644 index 5dcfb2ce..00000000 --- a/internal/retry/client.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2015 CoreOS, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package retry - -import ( - "fmt" - "io/ioutil" - "net/http" - "time" -) - -type Client struct { - InitialBackoff time.Duration - MaxBackoff time.Duration - MaxAttempts int - Header http.Header -} - -func (c Client) Get(url string) ([]byte, error) { - delay := c.InitialBackoff - for attempt := 1; attempt <= c.MaxAttempts; attempt++ { - fmt.Printf("Fetching %q: Attempt #%d\n", url, attempt) - - request, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, err - } - - request.Header = c.Header - - if response, err := (&http.Client{}).Do(request); err != nil { - fmt.Printf("Failed to fetch: %v\n", err) - } else if response.StatusCode == http.StatusNotFound { - return nil, nil - } else if response.StatusCode != http.StatusOK { - fmt.Printf("Failed to fetch: %s\n", http.StatusText(response.StatusCode)) - } else { - defer response.Body.Close() - return ioutil.ReadAll(response.Body) - } - - time.Sleep(delay) - delay *= 2 - if delay > c.MaxBackoff { - delay = c.MaxBackoff - } - } - - return nil, fmt.Errorf("timed out while fetching %q", url) -} - -func (c Client) Getf(format string, a ...interface{}) ([]byte, error) { - return c.Get(fmt.Sprintf(format, a...)) -} diff --git a/internal/vendor/github.com/coreos/update-ssh-keys/authorized_keys_d b/internal/vendor/github.com/coreos/update-ssh-keys/authorized_keys_d deleted file mode 120000 index bf963fef..00000000 --- a/internal/vendor/github.com/coreos/update-ssh-keys/authorized_keys_d +++ /dev/null @@ -1 +0,0 @@ -src/authorized_keys_d \ No newline at end of file diff --git a/internal/vendor/github.com/coreos/update-ssh-keys/src/authorized_keys_d/as_user/as_user.c b/internal/vendor/github.com/coreos/update-ssh-keys/src/authorized_keys_d/as_user/as_user.c deleted file mode 100644 index f84b4648..00000000 --- a/internal/vendor/github.com/coreos/update-ssh-keys/src/authorized_keys_d/as_user/as_user.c +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright 2015 CoreOS, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "as_user.h" - -// By doing these operations in CGO, we prevent the Go scheduler from -// potentially executing while our uid/gid/umask is changed. -// -// By doing them in a new !CLONE_FS thread we also localize the effects of the -// umask() switching. -// -// The calling go code must supply the appropriate args struct. -// We create a new thread passing the args down verbatim, it's all fairly -// mechanical. There's no need to restore umask/eids since we just exit the -// created thread when done. -// -// The return value and errno is simply fed back to the caller from the thread -// through some variables in ctxt. Synchronization is achieved via waitpid(). - -#define STACK_SIZE (64 * 1024) - -typedef struct au_thread_ctxt { - au_ids_t *ids; - - void *stack; - int (*fn)(void *); - void *fn_args; - - int ret; - int err; -} au_thread_ctxt_t; - -/* set_eids() sets the effective gid and uid of the calling thread to ids */ -static int set_eids(au_ids_t *ids) { - uid_t cu; - gid_t cg; - - umask(077); - - cu = geteuid(); - cg = getegid(); - - if(cg != ids->gid && setregid(-1, ids->gid) == -1) - return -1; - - if(cu != ids->uid && setreuid(-1, ids->uid) == -1) - return -1; - - return 0; -} - -/* in_thread_fn() switches users and calls ctxt->fn */ -static int in_thread_fn(au_thread_ctxt_t *ctxt) { - if((ctxt->ret = set_eids(ctxt->ids)) == 0) - ctxt->ret = ctxt->fn(ctxt->fn_args); - - ctxt->err = errno; - - return 0; -} - -/* in_thread() calls in_thread_fn() in a new thread passing fn and args along in - * via an au_thread_ctxt_t. - */ -static int in_thread(au_ids_t *ids, int (*fn)(void *), void *fn_args) { - int pid, ret = 0; - au_thread_ctxt_t ctxt = { - .ids = ids, - .fn = fn, - .fn_args = fn_args, - .err = 0, - .ret = -1, - }; - sigset_t allsigs, orig; - - if(!(ctxt.stack = malloc(STACK_SIZE))) { - ret = -1; - goto out; - } - - /* It's necessary to block all signals before cloning, so the child - * doesn't run any of the Go runtime's signal handlers. - */ - if((ret = sigemptyset(&orig)) == -1 || - (ret = sigfillset(&allsigs)) == -1) - goto out_stack; - - if((ret = sigprocmask(SIG_BLOCK, &allsigs, &orig)) == -1) - goto out_stack; - - pid = clone((int(*)(void *))in_thread_fn, ctxt.stack + STACK_SIZE, - CLONE_FILES|CLONE_VM, &ctxt); - - ret = sigprocmask(SIG_SETMASK, &orig, NULL); - - if(pid != -1) { - if(waitpid(pid, NULL, __WCLONE) == -1 && errno != ECHILD) { - ret = -1; - goto out_stack; - } - } else { - ret = -1; - } - - if(ret != -1) { - errno = ctxt.err; - ret = ctxt.ret; - } - -out_stack: - free(ctxt.stack); - -out: - return ret; -} - -/* au_open() */ -static int au_open_fn(au_open_args_t *args) { - return open(args->path, args->flags, args->mode); -} - -int au_open(au_open_args_t *args) { - return in_thread(&args->ids, (int(*)(void *))au_open_fn, args); -} - -/* au_mkdir_all() */ -static int au_mkdir_all_fn(au_mkdir_all_args_t *args) { - int ret = 0; - char *sep, *dup; - - /* TODO(vc): rewrite this, it's difficult to follow and probably buggy */ - - if(!*(args->path)) - goto out; - - if(!(dup = strdup(args->path))) { - ret = -1; - goto out; - } - - for(sep = dup + 1; sep;) { - /* find next slash or end of path */ - while(*sep && (*sep) != '/') sep++; - if(*sep) - *sep = '\0'; - else - sep = NULL; - - if(((ret = mkdir(dup, args->mode)) == -1) && errno != EEXIST) - goto out_free; - - if(sep) { - /* restore the '/' and skip '/'s */ - *sep = '/'; - while(*sep && *sep == '/') sep++; - } - } - - if(ret == -1 && errno == EEXIST) - ret = 0; - -out_free: - free(dup); - -out: - return ret; -} - -int au_mkdir_all(au_mkdir_all_args_t *args) { - return in_thread(&args->ids, (int(*)(void *))au_mkdir_all_fn, args); -} - -/* au_rename() */ -static int au_rename_fn(au_rename_args_t *args) { - return rename(args->oldpath, args->newpath); -} - -int au_rename(au_rename_args_t *args) { - return in_thread(&args->ids, (int(*)(void *))au_rename_fn, args); -} diff --git a/internal/vendor/github.com/coreos/update-ssh-keys/src/authorized_keys_d/as_user/as_user.go b/internal/vendor/github.com/coreos/update-ssh-keys/src/authorized_keys_d/as_user/as_user.go deleted file mode 100644 index b3d27fdc..00000000 --- a/internal/vendor/github.com/coreos/update-ssh-keys/src/authorized_keys_d/as_user/as_user.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2015 CoreOS, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// +build linux - -package as_user - -// #include "as_user.h" -import "C" - -import ( - "fmt" - "os" - "os/user" - "strconv" -) - -// TODO(vc): do this at akd.Open() and make our own typed user struct. -// getIds extracts the uid and guid as integers from a user.User. -func getIds(u *user.User) (C.int, C.int, error) { - uid, err := strconv.Atoi(u.Uid) - if err != nil { - return -1, -1, fmt.Errorf("invalid uid: %v", err) - } - - gid, err := strconv.Atoi(u.Gid) - if err != nil { - return -1, -1, fmt.Errorf("invalid gid: %v", err) - } - return C.int(uid), C.int(gid), nil -} - -// OpenFile reimplements os.OpenFile but switching euid/egid first if necessary. -func OpenFile(u *user.User, name string, flags int, perm uint32) (*os.File, error) { - uid, gid, err := getIds(u) - if err != nil { - return nil, err - } - args := C.au_open_args_t{ - ids: C.au_ids_t{uid: uid, gid: gid}, - path: C.CString(name), - flags: C.uint(flags), - mode: C.uint(perm), - } - - fd, err := C.au_open(&args) - if fd < 0 { - return nil, err - } - return os.NewFile(uintptr(fd), name), nil -} - -// MkdirAll reimplements os.MkdirAll but switching euid/egid first if necessary. -func MkdirAll(u *user.User, path string, perm uint32) error { - uid, gid, err := getIds(u) - if err != nil { - return err - } - args := C.au_mkdir_all_args_t{ - ids: C.au_ids_t{uid: uid, gid: gid}, - path: C.CString(path), - mode: C.uint(perm), - } - - if ret, err := C.au_mkdir_all(&args); ret < 0 { - return err - } - return nil -} - -// Rename reimplements os.Rename but switching euid/egid first if necessary. -func Rename(u *user.User, oldpath, newpath string) error { - uid, gid, err := getIds(u) - if err != nil { - return err - } - args := C.au_rename_args_t{ - ids: C.au_ids_t{uid: uid, gid: gid}, - oldpath: C.CString(oldpath), - newpath: C.CString(newpath), - } - - if ret, err := C.au_rename(&args); ret < 0 { - return err - } - return nil -} diff --git a/internal/vendor/github.com/coreos/update-ssh-keys/src/authorized_keys_d/as_user/as_user.h b/internal/vendor/github.com/coreos/update-ssh-keys/src/authorized_keys_d/as_user/as_user.h deleted file mode 100644 index d94911ae..00000000 --- a/internal/vendor/github.com/coreos/update-ssh-keys/src/authorized_keys_d/as_user/as_user.h +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2015 CoreOS, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -typedef struct au_ids { - int uid; - int gid; -} au_ids_t; - -typedef struct au_open_args { - au_ids_t ids; - const char *path; - unsigned int flags; - unsigned int mode; -} au_open_args_t; - -typedef struct au_mkdir_all_args { - au_ids_t ids; - const char *path; - unsigned int mode; -} au_mkdir_all_args_t; - -typedef struct au_rename_args { - au_ids_t ids; - const char *oldpath; - const char *newpath; -} au_rename_args_t; - -int au_open(au_open_args_t *); -int au_mkdir_all(au_mkdir_all_args_t *); -int au_rename(au_rename_args_t *); diff --git a/internal/vendor/github.com/coreos/update-ssh-keys/src/authorized_keys_d/authorized_keys_d.go b/internal/vendor/github.com/coreos/update-ssh-keys/src/authorized_keys_d/authorized_keys_d.go deleted file mode 100644 index 0c4bd785..00000000 --- a/internal/vendor/github.com/coreos/update-ssh-keys/src/authorized_keys_d/authorized_keys_d.go +++ /dev/null @@ -1,369 +0,0 @@ -// Copyright 2015 CoreOS, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// +build linux - -// authorized_keys_d manages a user's ~/.ssh/authorized_keys.d and can produce -// a ~/.ssh/authorized_keys file from the authorized_keys.d contents. -package authorized_keys_d - -import ( - "fmt" - "io/ioutil" - "os" - "os/user" - "path/filepath" - "sort" - "strings" - "syscall" - - "github.com/coreos/update-ssh-keys/authorized_keys_d/as_user" -) - -const ( - AuthorizedKeysFile = "authorized_keys" - AuthorizedKeysDir = "authorized_keys.d" - PreservedKeysName = "orig_authorized_keys" - SSHDir = ".ssh" - - lockFile = ".authorized_keys.d.lock" // In "~/". - stageFile = ".authorized_keys.d.stage_file" // In "~/.ssh". - stageDir = ".authorized_keys.d.stage_dir" // In "~/.ssh". -) - -// SSHAuthorizedKeysDir represents an opened user's authorized_keys.d. -type SSHAuthorizedKeysDir struct { - path string // Path to authorized_keys.d directory. - user *user.User // User of the directory. - lock *os.File // Lock file for serializing Open()-Close(). -} - -// SSHAuthorizedKey represents an opened user's authorized_keys.d/ entry. -type SSHAuthorizedKey struct { - Name string // Name given to the key. - Disabled bool // Disabled state of the key. - Path string // Path to the file backing the key. - origin *SSHAuthorizedKeysDir // Originating dir for this key. -} - -// sshDirPath returns the path to the .ssh dir for the user. -func sshDirPath(u *user.User) string { - return filepath.Join(u.HomeDir, SSHDir) -} - -// authKeysFilePath returns the path to the authorized_keys file for the user. -func authKeysFilePath(u *user.User) string { - return filepath.Join(sshDirPath(u), AuthorizedKeysFile) -} - -// authKeysDirPath returns the path to the authorized_keys.d for the user. -func authKeysDirPath(u *user.User) string { - return filepath.Join(sshDirPath(u), AuthorizedKeysDir) -} - -// lockFilePath returns the path to the lock file for the user. -func lockFilePath(u *user.User) string { - return filepath.Join(u.HomeDir, lockFile) -} - -// stageDirPath returns the path to the staging directory for the user. -func stageDirPath(u *user.User) string { - return filepath.Join(sshDirPath(u), stageDir) -} - -// stageFilePath returns the path to the staging file for the user. -func stageFilePath(u *user.User) string { - return filepath.Join(sshDirPath(u), stageFile) -} - -// opendir opens the authorized keys directory. -func opendir(dir string) (*SSHAuthorizedKeysDir, error) { - fi, err := os.Stat(dir) - if err != nil { - return nil, err - } - if !fi.IsDir() { - return nil, fmt.Errorf("%q is not a directory", dir) - } - return &SSHAuthorizedKeysDir{path: dir}, nil -} - -// acquireLock locks the lock file for the given user's authorized_keys.d. -// A lock file is created if it doesn't already exist. -// The locking is currently a simple coarse-grained mutex held for the -// Open()-Close() duration, implemented using a lock file in the user's ~/. -func acquireLock(u *user.User) (*os.File, error) { - f, err := as_user.OpenFile(u, lockFilePath(u), - syscall.O_CREAT|syscall.O_RDONLY, 0600) - if err != nil { - return nil, err - } - if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX); err != nil { - f.Close() - return nil, err - } - return f, nil -} - -// createAuthorizedKeysDir creates an authorized keys directory for the user. -// If the user has an authorized_keys file, it is migrated. -func createAuthorizedKeysDir(u *user.User) (*SSHAuthorizedKeysDir, error) { - td := stageDirPath(u) - if err := as_user.MkdirAll(u, td, 0700); err != nil { - return nil, err - } - defer os.RemoveAll(td) - - akd, err := opendir(td) - if err != nil { - return nil, err - } - akd.user = u - - akfb, err := ioutil.ReadFile(authKeysFilePath(u)) - if err != nil && !os.IsNotExist(err) { - return nil, err - } else if err == nil { - err = akd.Add(PreservedKeysName, akfb, false, false) - if err != nil { - return nil, err - } - } - if err = akd.rename(authKeysDirPath(u)); err != nil { - return nil, err - } - return akd, err -} - -// Open opens the authorized keys directory for the supplied user. -// If create is false, Open will fail if no directory exists yet. -// If create is true, Open will create the directory if it doesn't exist, -// preserving the authorized_keys file in the process. -// After a successful open, Close should be called when finished to unlock -// the directory. -func Open(usr *user.User, create bool) (*SSHAuthorizedKeysDir, error) { - l, err := acquireLock(usr) - if err != nil { - return nil, err - } - defer func() { - if err != nil { - l.Close() - } - }() - - akd, err := opendir(authKeysDirPath(usr)) - if err != nil && (!create || !os.IsNotExist(err)) { - return nil, err - } else if os.IsNotExist(err) { - akd, err = createAuthorizedKeysDir(usr) - if err != nil { - return nil, err - } - } - - akd.lock = l - akd.user = usr - return akd, nil -} - -// Close closes the authorized keys directory. -func (akd *SSHAuthorizedKeysDir) Close() error { - return akd.lock.Close() -} - -// rename renames the authorized_keys dir to the supplied path. -func (akd *SSHAuthorizedKeysDir) rename(to string) error { - err := as_user.Rename(akd.user, akd.path, to) - if err != nil { - return err - } - akd.path = to - return nil -} - -// keyPath returns the path to the named key. -func (akd *SSHAuthorizedKeysDir) keyPath(n string) string { - return filepath.Join(akd.path, n) -} - -// WalkKeys iterates across all keys in akd, calling f for each key. -// Iterating stops on error, and the error is propagated out. -func (akd *SSHAuthorizedKeysDir) WalkKeys(f func(*SSHAuthorizedKey) error) error { - d, err := os.Open(akd.path) - if err != nil { - return err - } - - names, err := d.Readdirnames(0) - if err != nil { - return err - } - - sort.Strings(names) - for _, n := range names { - ak, err := akd.Open(n) - if err != nil { - return err - } - if err := f(ak); err != nil { - return err - } - } - - return nil -} - -// Open opens the key at name. -func (akd *SSHAuthorizedKeysDir) Open(name string) (*SSHAuthorizedKey, error) { - p := akd.keyPath(name) - fi, err := os.Stat(p) - if err != nil { - return nil, err - } - ak := &SSHAuthorizedKey{ - Name: name, - Disabled: (fi.Size() == 0), - Path: p, - origin: akd, - } - return ak, nil -} - -// Remove removes the key at name. -func (akd *SSHAuthorizedKeysDir) Remove(name string) error { - ak, err := akd.Open(name) - if err != nil { - return err - } - return ak.Remove() -} - -// Disable disables the key at name. -func (akd *SSHAuthorizedKeysDir) Disable(name string) error { - ak, err := akd.Open(name) - if err != nil { - return err - } - return ak.Disable() -} - -// Add adds the supplied key at name. -// replace enables replacing keys already existing at name. -// force enables adding keys to a disabled name, enabling it in the process. -// Names starting wtih ".", and anything containing "/" are disallowed. -func (akd *SSHAuthorizedKeysDir) Add(name string, keys []byte, replace, force bool) error { - if strings.HasPrefix(name, ".") || strings.Contains(name, "/") { - return fmt.Errorf(`illegal name`) - } - - p := akd.keyPath(name) - fi, err := os.Stat(p) - if err == nil { - if fi.Size() > 0 && !replace { - return fmt.Errorf("key %q already exists", name) - } else if fi.Size() == 0 && !force { - return fmt.Errorf("key %q disabled", name) - } - } else if !os.IsNotExist(err) { - return err - } - ak := &SSHAuthorizedKey{Path: p, origin: akd} - return ak.Replace(keys) -} - -// KeysFilePath returns the backing authorized_keys file path for this -// SSHAuthorizedKeysDir. This is the file written to by Sync(). -func (akd *SSHAuthorizedKeysDir) KeysFilePath() string { - return authKeysFilePath(akd.user) -} - -// KeysDirPath returns the authorized_keys.d directory path for this -// SSHAuthorizedKeysDir. This is the directory containing the discrete key -// files. -func (akd *SSHAuthorizedKeysDir) KeysDirPath() string { - return authKeysDirPath(akd.user) -} - -// Sync synchronizes the user's ~/.ssh/authorized_keys file with the -// current authorized_keys.d directory state. -func (akd *SSHAuthorizedKeysDir) Sync() error { - sp := stageFilePath(akd.user) - sf, err := as_user.OpenFile(akd.user, sp, - syscall.O_CREAT|syscall.O_TRUNC|syscall.O_WRONLY, 0600) - if err != nil { - return err - } - defer func() { - if err != nil { - sf.Close() - os.Remove(sp) - } - }() - - if err := akd.WalkKeys(func(k *SSHAuthorizedKey) error { - if !k.Disabled { - kb, err := ioutil.ReadFile(k.Path) - if err != nil { - return err - } - kb = append(kb, '\n') - if _, err := sf.Write(kb); err != nil { - return err - } - } - return nil - }); err != nil { - return err - } - - if err := sf.Close(); err != nil { - return err - } - - err = as_user.Rename(akd.user, sp, authKeysFilePath(akd.user)) - if err != nil { - return err - } - - return nil -} - -// Remove removes the opened key. -func (ak *SSHAuthorizedKey) Remove() error { - return os.Remove(ak.Path) -} - -// Disable disables the opened key. -func (ak *SSHAuthorizedKey) Disable() error { - return os.Truncate(ak.Path, 0) -} - -// Replace replaces the opened key with the supplied data. -func (ak *SSHAuthorizedKey) Replace(keys []byte) error { - sp := stageFilePath(ak.origin.user) - sf, err := as_user.OpenFile(ak.origin.user, sp, - syscall.O_WRONLY|syscall.O_CREAT|syscall.O_TRUNC, 0600) - if err != nil { - return err - } - defer os.Remove(sp) - if _, err = sf.Write(keys); err != nil { - return err - } - if err := sf.Close(); err != nil { - return err - } - return as_user.Rename(ak.origin.user, sp, ak.Path) -} diff --git a/internal/vendor/github.com/coreos/update-ssh-keys/src/authorized_keys_d/authorized_keys_d_test.go b/internal/vendor/github.com/coreos/update-ssh-keys/src/authorized_keys_d/authorized_keys_d_test.go deleted file mode 100644 index dc37c3e4..00000000 --- a/internal/vendor/github.com/coreos/update-ssh-keys/src/authorized_keys_d/authorized_keys_d_test.go +++ /dev/null @@ -1,654 +0,0 @@ -// Copyright 2015 CoreOS, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package authorized_keys_d - -import ( - "crypto/rand" - "fmt" - "io/ioutil" - "os" - "os/user" - "path/filepath" - "reflect" - "strconv" - "testing" -) - -const ( - DirNode = iota - FileNode -) - -type tree []node -type node struct { - path string - typ int - mode os.FileMode - bytes []byte -} - -// create creates the tree rooted at the path where. -// If tree is empty, where will not be created. -func (t tree) create(where string) error { - if len(t) == 0 { - os.RemoveAll(where) - return nil - } - - for i, n := range t { - p := filepath.Join(where, n.path) - switch n.typ { - case DirNode: - if err := os.MkdirAll(p, n.mode); err != nil { - return fmt.Errorf("%d: mkdir error: %v", i, err) - } - if err := os.Chmod(p, n.mode); err != nil { - return fmt.Errorf("%d: chown error: %v", i, err) - } - case FileNode: - err := ioutil.WriteFile(p, n.bytes, n.mode) - if err != nil { - return fmt.Errorf("%d: writefile error: %v", i, err) - } - default: - return fmt.Errorf("%d: unknown node type: %v", i, n.typ) - } - } - - return nil -} - -// testUser populates a user.User for testing. -func testUser(home string) *user.User { - return &user.User{ - Uid: strconv.Itoa(os.Getuid()), - Gid: strconv.Itoa(os.Getgid()), - Username: "test", - Name: "test", - HomeDir: home, - } -} - -// testDir creates a temp directory for testing. -func testDir(t *testing.T) string { - td, err := ioutil.TempDir("", "akd-test-") - if err != nil { - t.Fatalf("Failed to create temp dir: %v", err) - } - return td -} - -func TestOpenClose(t *testing.T) { - tests := []struct { - create bool - home tree - success bool - }{ - { // 0: No home, no create, fail. - }, - { // 1: No home, yes create, fail. - create: true, - }, - { // 2: Empty home, no create, fail. - create: false, - home: tree{{path: ".", typ: DirNode, mode: 0700}}, - success: false, - }, - { // 3: Empty home, yes create, success. - create: true, - home: tree{{path: ".", typ: DirNode, mode: 0700}}, - success: true, - }, - { // 4: Empty home, no create, fail. - create: false, - home: tree{{path: ".", typ: DirNode, mode: 0700}}, - success: false, - }, - { // 5: Intact home, no create, fail. - create: false, - home: tree{ - {path: ".", typ: DirNode, mode: 0700}, - {path: SSHDir, typ: DirNode, mode: 0700}, - { - path: filepath.Join(SSHDir, AuthorizedKeysFile), - typ: FileNode, mode: 0600, - }, - }, - success: false, - }, - { // 6: Intact home, yes create, succeed. - create: true, - home: tree{ - {path: ".", typ: DirNode, mode: 0700}, - {path: SSHDir, typ: DirNode, mode: 0700}, - { - path: filepath.Join(SSHDir, AuthorizedKeysFile), - typ: FileNode, mode: 0600, - }, - }, - success: true, - }, - { // 7: Managed home, no create, succeed. - create: false, - home: tree{ - {path: ".", typ: DirNode, mode: 0700}, - {path: SSHDir, typ: DirNode, mode: 0700}, - { - path: filepath.Join(SSHDir, AuthorizedKeysFile), - typ: FileNode, mode: 0600, - }, - { - path: filepath.Join(SSHDir, AuthorizedKeysDir), - typ: DirNode, mode: 0700, - }, - }, - success: true, - }, - { // 8: Managed home, yes create, succeed. - create: true, - home: tree{ - {path: ".", typ: DirNode, mode: 0700}, - {path: SSHDir, typ: DirNode, mode: 0700}, - { - path: filepath.Join(SSHDir, AuthorizedKeysFile), - typ: FileNode, mode: 0600, - }, - { - path: filepath.Join(SSHDir, AuthorizedKeysDir), - typ: DirNode, mode: 0700, - }, - }, - success: true, - }, - } - - for i, tt := range tests { - td := testDir(t) - if err := tt.home.create(td); err != nil { - t.Errorf("%d: failed to create home tree: %v", i, err) - continue - } - - akd, err := Open(testUser(td), tt.create) - if (err == nil) != tt.success { - t.Errorf("%d: expected %v got %v", i, tt.success, bool(err == nil)) - if err != nil { - t.Errorf("%d: unexpected error: %v", i, err) - } - } - if err == nil { - if err := akd.Close(); err != nil { - t.Errorf("%d: close failed: %v", i, err) - } - } - os.RemoveAll(td) - } -} - -func TestAddKey(t *testing.T) { - tests := []struct { - name string - disable bool - replace bool - force bool - success bool - }{ - { // 0: Doesn't exist, no replace, success. - name: "test", - success: true, - }, - { // 1: Exists, no replace, fail. - name: "test", - }, - { // 2: Exists, replace, succeed. - name: "test", - replace: true, - success: true, - }, - { // 2: Exists (disabled), force, succeed. - name: "test", - disable: true, - force: true, - success: true, - }, - { // 2: Exists (disabled), !force, fail. - name: "test", - disable: true, - }, - { // 3: Leading dot, fail. - name: ".hidden", - }, - { // 4: Leading dot, fail. - name: ".", - }, - { // 5: Leading dot, fail. - name: "..", - }, - { // 6: Contains slash, fail. - name: "no/slash", - }, - } - - td := testDir(t) - akd, err := Open(testUser(td), true) - if err != nil { - t.Fatalf("open error: %v", err) - } - defer func() { - akd.Close() - os.RemoveAll(td) - }() - - for i, tt := range tests { - if tt.disable { - if err := akd.Disable(tt.name); err != nil { - t.Errorf("%d: disable failed: %v", i, err) - } - } - err := akd.Add(tt.name, []byte("test"), tt.replace, tt.force) - if (err == nil) != tt.success { - t.Errorf("%d: expected %v got %v", i, tt.success, bool(err == nil)) - } - } -} - -func TestWalkKeys(t *testing.T) { - tests := []struct { - names []string - asWalked []string - }{ - { // 0: Empty, in-order. - names: []string{}, - }, - { // 1: One, in-order. - names: []string{"0_one"}, - }, - { // 2: Many, in-order. - names: []string{"0_one", "1_two", "2_three"}, - }, - { // 3: Many, out-of-order. - // XXX: add order reflecting readdir order is fs-specific, harmless to try. - names: []string{"0_one", "2_three", "1_two"}, - asWalked: []string{"0_one", "1_two", "2_three"}, - }, - } - - for i, tt := range tests { - td := testDir(t) - akd, err := Open(testUser(td), true) - if err != nil { - t.Errorf("%d: open error: %v", i, err) - os.RemoveAll(td) - continue - } - - for _, n := range tt.names { - err := akd.Add(n, []byte("foobar"), false, false) - if err != nil { - t.Errorf("%d: add error: %v", i, err) - } - } - - walked := []string{} - if err := akd.WalkKeys(func(k *SSHAuthorizedKey) error { - walked = append(walked, k.Name) - return nil - }); err != nil { - t.Errorf("%d: walk error: %v", i, err) - } - - against := tt.names - if len(tt.asWalked) != 0 { - against = tt.asWalked - } - - if !reflect.DeepEqual(walked, against) { - t.Errorf("%d: inconsistent result: %v vs. %v", i, walked, against) - } - - akd.Close() - os.RemoveAll(td) - } -} - -func TestOpenName(t *testing.T) { - tests := []struct { - names []string - open string - success bool - }{ - { // 0: Empty, absent, fail. - open: "missing", - success: false, - }, - { // 1: Populated, absent, fail. - names: []string{"a", "b", "c"}, - open: "missing", - success: false, - }, - { // 2: Populated, present, succeed. - names: []string{"a", "b", "c"}, - open: "b", - success: true, - }, - } - - for i, tt := range tests { - td := testDir(t) - akd, err := Open(testUser(td), true) - if err != nil { - t.Errorf("%d: open error: %v", i, err) - os.RemoveAll(td) - continue - } - - for _, n := range tt.names { - err := akd.Add(n, []byte("foobar"), false, false) - if err != nil { - t.Errorf("%d: add error: %v", i, err) - } - } - - k, err := akd.Open(tt.open) - if (err == nil) != tt.success { - t.Errorf("%d: expected %v got %v", i, tt.success, bool(err == nil)) - } - if err == nil && k.Name != tt.open { - t.Errorf("%d: expected %v got %v", i, tt.open, k.Name) - } - - akd.Close() - os.RemoveAll(td) - } -} - -func TestRemoveName(t *testing.T) { - tests := []struct { - names []string - remove string - success bool - }{ - { // 0: Empty, absent, fail. - remove: "missing", - success: false, - }, - { // 1: Populated, absent, fail. - names: []string{"a", "b", "c"}, - remove: "missing", - success: false, - }, - { // 2: Populated, present, succeed. - names: []string{"a", "b", "c"}, - remove: "b", - success: true, - }, - } - - for i, tt := range tests { - td := testDir(t) - akd, err := Open(testUser(td), true) - if err != nil { - t.Errorf("%d: open error: %v", i, err) - os.RemoveAll(td) - continue - } - - for _, n := range tt.names { - err := akd.Add(n, []byte("foobar"), false, false) - if err != nil { - t.Errorf("%d: add error: %v", i, err) - } - } - - err = akd.Remove(tt.remove) - if (err == nil) != tt.success { - t.Errorf("%d: expected %v got %v", i, tt.success, bool(err == nil)) - } - - akd.Close() - os.RemoveAll(td) - } -} - -func TestDisableName(t *testing.T) { - name := "test" - td := testDir(t) - defer os.RemoveAll(td) - akd, err := Open(testUser(td), true) - if err != nil { - t.Errorf("open error: %v", err) - return - } - defer akd.Close() - - if err := akd.Add(name, []byte("foobar"), false, false); err != nil { - t.Errorf("add error: %v", err) - return - } - - ak, err := akd.Open(name) - if err != nil { - t.Errorf("open name error: %v", err) - return - } - - if ak.Disabled { - t.Errorf("unexpectedly disabled") - return - } - - if err := akd.Disable(name); err != nil { - t.Errorf("disable failed: %v", err) - return - } - - ak, err = akd.Open(name) - if err != nil { - t.Errorf("open name error: %v", err) - return - } - - if !ak.Disabled { - t.Errorf("unexpected enabled") - return - } -} - -func TestSync(t *testing.T) { - td := testDir(t) - defer os.RemoveAll(td) - akd, err := Open(testUser(td), true) - if err != nil { - t.Errorf("open error: %v", err) - return - } - defer akd.Close() - - bytes := []byte{} - for i := 0; i < 10; i++ { - b := make([]byte, 512) - if _, err := rand.Read(b); err != nil { - t.Errorf("rand read err: %v", err) - return - } - if err := akd.Add(strconv.Itoa(i), b, false, false); err != nil { - t.Errorf("add error: %v", err) - return - } - bytes = append(bytes, b...) - bytes = append(bytes, '\n') - } - - if err := akd.Sync(); err != nil { - t.Errorf("sync failed: %v", err) - return - } - - rb, err := ioutil.ReadFile(akd.KeysFilePath()) - if err != nil { - t.Errorf("read failed: %v", err) - return - } - - if !reflect.DeepEqual(bytes, rb) { - t.Errorf("inconsistent output") - } -} - -func TestRemove(t *testing.T) { - tests := []struct { - names []string - remove string - success bool - }{ - { // 0: Empty, absent, fail. - remove: "missing", - success: false, - }, - { // 1: Populated, absent, fail. - names: []string{"a", "b", "c"}, - remove: "missing", - success: false, - }, - { // 2: Populated, present, succeed. - names: []string{"a", "b", "c"}, - remove: "b", - success: true, - }, - } - - for i, tt := range tests { - td := testDir(t) - akd, err := Open(testUser(td), true) - if err != nil { - t.Errorf("%d: open error: %v", i, err) - os.RemoveAll(td) - continue - } - - for _, n := range tt.names { - err := akd.Add(n, []byte("foobar"), false, false) - if err != nil { - t.Errorf("%d: add error: %v", i, err) - } - } - - ak, err := akd.Open(tt.remove) - if (err == nil) != tt.success { - t.Errorf("%d: expected %v got %v", i, tt.success, bool(err == nil)) - } - if err != nil { - akd.Close() - os.RemoveAll(td) - continue - } - - err = ak.Remove() - if (err == nil) != tt.success { - t.Errorf("%d: expected %v got %v", i, tt.success, bool(err == nil)) - } - - akd.Close() - os.RemoveAll(td) - } -} - -func TestDisable(t *testing.T) { - name := "test" - td := testDir(t) - defer os.RemoveAll(td) - akd, err := Open(testUser(td), true) - if err != nil { - t.Errorf("open error: %v", err) - return - } - defer akd.Close() - - if err := akd.Add(name, []byte("foobar"), false, false); err != nil { - t.Errorf("add error: %v", err) - return - } - - ak, err := akd.Open(name) - if err != nil { - t.Errorf("open name error: %v", err) - return - } - - if ak.Disabled { - t.Errorf("unexpectedly disabled") - return - } - - if err := ak.Disable(); err != nil { - t.Errorf("disable failed: %v", err) - return - } - - ak, err = akd.Open(name) - if err != nil { - t.Errorf("open name error: %v", err) - return - } - - if !ak.Disabled { - t.Errorf("unexpected enabled") - return - } -} - -func TestReplace(t *testing.T) { - name := "test" - td := testDir(t) - defer os.RemoveAll(td) - akd, err := Open(testUser(td), true) - if err != nil { - t.Errorf("open error: %v", err) - return - } - defer akd.Close() - - if err := akd.Add(name, []byte("foobar"), false, false); err != nil { - t.Errorf("add error: %v", err) - return - } - - ak, err := akd.Open(name) - if err != nil { - t.Errorf("open name error: %v", err) - return - } - - if err := ak.Replace([]byte("moop")); err != nil { - t.Errorf("replace failed: %v", err) - return - } - - ak, err = akd.Open(name) - if err != nil { - t.Errorf("open name error: %v", err) - return - } - - b, err := ioutil.ReadFile(ak.Path) - if err != nil { - t.Errorf("readfile failed: %v", err) - return - } - - if string(b) != "moop" { - t.Errorf("unexpected results: %q", string(b)) - } -} diff --git a/internal/vendor/github.com/coreos/update-ssh-keys/src/main.go b/internal/vendor/github.com/coreos/update-ssh-keys/src/main.go deleted file mode 100644 index d4382450..00000000 --- a/internal/vendor/github.com/coreos/update-ssh-keys/src/main.go +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright 2015 CoreOS, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "flag" - "fmt" - "io/ioutil" - "os" - "os/exec" - "os/user" - "strings" - - keys "github.com/coreos/update-ssh-keys/authorized_keys_d" -) - -var ( - flagUser = flag.String("u", "core", "Update the given user's authorized_keys file.") - flagAdd = flag.String("a", "", "Add the given keys, using the given name to identify them.") - flagAddForce = flag.String("A", "", "Add the given keys, even if it was disabled with '-D'.") - flagDelete = flag.String("d", "", "Delete keys identified by the given name.") - flagDisable = flag.String("D", "", "Disable the given set from being added with '-a'.") - flagList = flag.Bool("l", false, "List the names and number of keys currently installed.") - flagReplace = flag.Bool("n", true, "When adding, don't replace an existing key with the given name.") - flagHelp = flag.Bool("h", false, "This ;-)") -) - -func stderr(f string, a ...interface{}) { - out := fmt.Sprintf(f, a...) - fmt.Fprintln(os.Stderr, strings.TrimSuffix(out, "\n")) -} - -func stdout(f string, a ...interface{}) { - out := fmt.Sprintf(f, a...) - fmt.Fprintln(os.Stdout, strings.TrimSuffix(out, "\n")) -} - -func panicf(f string, a ...interface{}) { - panic(fmt.Sprintf(f, a...)) -} - -// printKeys prints all the keys currently managed. -func printKeys(akd *keys.SSHAuthorizedKeysDir) error { - stdout("All keys for %s", *flagUser) - return akd.WalkKeys(func(k *keys.SSHAuthorizedKey) error { - if !k.Disabled { - cmd := exec.Command("ssh-keygen", "-l", "-f", k.Path) - out, err := cmd.CombinedOutput() - if err != nil { - return err - } - stdout("%s: %s", k.Name, string(out)) - } - return nil - }) -} - -// addKeys adds keys to akd as name. -func addKeys(akd *keys.SSHAuthorizedKeysDir, name string, force bool) error { - k := []byte{} - files := []*os.File{} - - if flag.NArg() > 0 { - // read from files - for _, fp := range flag.Args() { - f, err := os.Open(fp) - if err != nil { - return err - } - defer f.Close() - files = append(files, f) - } - } else { - // read from stdin - files = append(files, os.Stdin) - } - - for _, f := range files { - b, err := ioutil.ReadAll(f) - if err != nil { - return err - } - k = append(k, b...) - } - - if !validKeys(k) { - return fmt.Errorf("key(s) invalid.") - } - - return akd.Add(name, k, *flagReplace, force) -} - -// validKeys validates a byte slice contains valid ssh keys. -func validKeys(keys []byte) bool { - // we need to write out a temp file for ssh-keygen to consume. - tf, err := ioutil.TempFile("", "update-ssh-keys-") - if err != nil { - panicf("unable to create temporary file: %v", err) - } - defer func() { - os.Remove(tf.Name()) - tf.Close() - }() - - if _, err := tf.Write(keys); err != nil { - panicf("unable to write temporary file: %v", err) - } - - cmd := exec.Command("ssh-keygen", "-l", "-f", tf.Name()) - out, err := cmd.CombinedOutput() - if err != nil { - return false - } - stdout("%s", out) - - return true -} - -func main() { - flag.Parse() - - if *flagHelp { - flag.Usage() - os.Exit(1) - } - - usr, err := user.Lookup(*flagUser) - if err != nil { - stderr("Failed to lookup user: %v", *flagUser) - os.Exit(2) - } - - akd, err := keys.Open(usr, true) - if err != nil { - stderr("Failed to open: %v", err) - os.Exit(3) - } - defer akd.Close() - - switch { - case *flagList: - if err := printKeys(akd); err != nil { - stderr("Failed to print keys: %v", err) - os.Exit(4) - } - case *flagAdd != "": - if err := addKeys(akd, *flagAdd, false); err != nil { - stderr("failed to add keys %q: %v", *flagAdd, err) - os.Exit(5) - } - case *flagAddForce != "": - if err := addKeys(akd, *flagAddForce, true); err != nil { - stderr("failed to add keys %q: %v", *flagAddForce, err) - os.Exit(6) - } - case *flagDelete != "": - if err := akd.Remove(*flagDelete); err != nil { - stderr("failed to delete key %q: %v", *flagDelete, err) - os.Exit(7) - } - case *flagDisable != "": - if err := akd.Disable(*flagDisable); err != nil { - stderr("failed to disable key %q: %v", *flagDisable, err) - os.Exit(8) - } - } - - if err := akd.Sync(); err != nil { - stderr("Failed sync: %v", err) - os.Exit(9) - } - stdout("Updated %s", akd.KeysFilePath()) - - os.Exit(0) -} diff --git a/internal/vendor/github.com/packethost/packngo/LICENSE.txt b/internal/vendor/github.com/packethost/packngo/LICENSE.txt deleted file mode 100644 index 57c50110..00000000 --- a/internal/vendor/github.com/packethost/packngo/LICENSE.txt +++ /dev/null @@ -1,56 +0,0 @@ -Copyright (c) 2014 The packngo AUTHORS. All rights reserved. - -MIT License - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -====================== -Portions of the client are based on code at: -https://github.com/google/go-github/ and -https://github.com/digitalocean/godo - -Copyright (c) 2013 The go-github AUTHORS. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - diff --git a/internal/vendor/github.com/packethost/packngo/metadata/metadata.go b/internal/vendor/github.com/packethost/packngo/metadata/metadata.go deleted file mode 100644 index 1ac26968..00000000 --- a/internal/vendor/github.com/packethost/packngo/metadata/metadata.go +++ /dev/null @@ -1,153 +0,0 @@ -package metadata - -import ( - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "net" - "net/http" -) - -const BaseURL = "https://metadata.packet.net" - -func GetMetadata() (*CurrentDevice, error) { - res, err := http.Get(BaseURL + "/metadata") - if err != nil { - return nil, err - } - - b, err := ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - return nil, err - } - - var result struct { - Error string `json:"error"` - *CurrentDevice - } - if err := json.Unmarshal(b, &result); err != nil { - return nil, err - } - if result.Error != "" { - return nil, errors.New(result.Error) - } - return result.CurrentDevice, nil -} - -func GetUserData() ([]byte, error) { - res, err := http.Get(BaseURL + "/userdata") - if err != nil { - return nil, err - } - - b, err := ioutil.ReadAll(res.Body) - res.Body.Close() - return b, err -} - -type AddressFamily int - -const ( - IPv4 = AddressFamily(4) - IPv6 = AddressFamily(6) -) - -type AddressInfo struct { - ID string `json:"id"` - Family AddressFamily `json:"address_family"` - Public bool `json:"public"` - Management bool `json:"management"` - Address net.IP `json:"address"` - NetworkMask net.IP `json:"netmask"` - Gateway net.IP `json:"gateway"` - - // These are available, but not really needed: - // Network net.IP `json:"network"` - // NetworkBits int `json:"cidr"` -} - -type BondingMode int - -const ( - BondingBalanceRR = BondingMode(0) - BondingActiveBackup = BondingMode(1) - BondingBalanceXOR = BondingMode(2) - BondingBroadcast = BondingMode(3) - BondingLACP = BondingMode(4) - BondingBalanceTLB = BondingMode(5) - BondingBalanceALB = BondingMode(6) -) - -var bondingModeStrings = map[BondingMode]string{ - BondingBalanceRR: "balance-rr", - BondingActiveBackup: "active-backup", - BondingBalanceXOR: "balance-xor", - BondingBroadcast: "broadcast", - BondingLACP: "802.3ad", - BondingBalanceTLB: "balance-tlb", - BondingBalanceALB: "balance-alb", -} - -func (m BondingMode) String() string { - if str, ok := bondingModeStrings[m]; ok { - return str - } - return fmt.Sprintf("%d", m) -} - -type CurrentDevice struct { - ID string `json:"id"` - Hostname string `json:"hostname"` - IQN string `json:"iqn"` - Plan string `json:"plan"` - Facility string `json:"facility"` - Tags []string `json:"tags"` - SSHKeys []string `json:"ssh_keys"` - OS OperatingSystem `json:"operating_system"` - Network NetworkInfo `json:"network"` - Volumes []VolumeInfo `json:"volume"` - - // This is available, but is actually inaccurate, currently: - // APIBaseURL string `json:"api_url"` -} - -type InterfaceInfo struct { - Name string `json:"name"` - MAC string `json:"mac"` -} - -func (i *InterfaceInfo) ParseMAC() (net.HardwareAddr, error) { - return net.ParseMAC(i.MAC) -} - -type NetworkInfo struct { - Interfaces []InterfaceInfo `json:"interfaces"` - Addresses []AddressInfo `json:"addresses"` - - Bonding struct { - Mode BondingMode `json:"mode"` - } `json:"bonding"` -} - -func (n *NetworkInfo) BondingMode() BondingMode { - return n.Bonding.Mode -} - -type OperatingSystem struct { - Slug string `json:"slug"` - Distro string `json:"distro"` - Version string `json:"version"` -} - -type VolumeInfo struct { - Name string `json:"name"` - IQN string `json:"iqn"` - IPs []net.IP `json:"ips"` - - Capacity struct { - Size int `json:"size,string"` - Unit string `json:"unit"` - } `json:"capacity"` -} diff --git a/src/bin/coreos-metadata.rs b/src/bin/coreos-metadata.rs new file mode 100644 index 00000000..1aaef83d --- /dev/null +++ b/src/bin/coreos-metadata.rs @@ -0,0 +1,167 @@ +// Copyright 2017 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. + +#![cfg_attr(feature="clippy", feature(plugin))] +#![cfg_attr(feature="clippy", plugin(clippy))] + +#[macro_use] +extern crate clap; +#[macro_use] +extern crate error_chain; +#[macro_use] +extern crate slog; +extern crate slog_term; +extern crate slog_async; +#[macro_use] +extern crate slog_scope; + +extern crate coreos_metadata; + +use std::fs::File; +use std::io::prelude::*; +use clap::{Arg, App}; +use slog::Drain; + +use coreos_metadata::fetch_metadata; +use coreos_metadata::errors::*; + +const CMDLINE_PATH: &'static str = "/proc/cmdline"; +const CMDLINE_OEM_FLAG:&'static str = "coreos.oem.id"; + +#[derive(Debug)] +struct Config { + provider: String, + attributes_file: Option, + ssh_keys_user: Option, + hostname_file: Option, + network_units_dir: Option, +} + +quick_main!(run); + +fn run() -> Result<()> { + // setup logging + let decorator = slog_term::TermDecorator::new().build(); + let drain = slog_term::FullFormat::new(decorator).build().fuse(); + let drain = slog_async::Async::new(drain).build().fuse(); + let log = slog::Logger::root(drain, slog_o!()); + let _guard = slog_scope::set_global_logger(log); + + debug!("Logging initialized"); + + // initialize program + let config = init() + .chain_err(|| "initialization")?; + + trace!("cli configuration - {:?}", config); + + // fetch the metadata from the configured provider + let metadata = fetch_metadata(&config.provider) + .chain_err(|| "fetching metadata from provider")?; + + // write attributes if configured to do so + config.attributes_file + .map_or(Ok(()), |x| metadata.write_attributes(x)) + .chain_err(|| "writing metadata attributes")?; + + // write ssh keys if configured to do so + config.ssh_keys_user + .map_or(Ok(()), |x| metadata.write_ssh_keys(x)) + .chain_err(|| "writing ssh keys")?; + + // write hostname if configured to do so + config.hostname_file + .map_or(Ok(()), |x| metadata.write_hostname(x)) + .chain_err(|| "writing hostname")?; + + // write network units if configured to do so + config.network_units_dir + .map_or(Ok(()), |x| metadata.write_network_units(x)) + .chain_err(|| "writing network units")?; + + debug!("Done!"); + + Ok(()) +} + +fn init() -> Result { + // setup cli + let matches = App::new("coreos-metadata") + .version(crate_version!()) + .arg(Arg::with_name("attributes") + .long("attributes") + .help("The file into which the metadata attributes are written") + .takes_value(true)) + .arg(Arg::with_name("cmdline") + .long("cmdline") + .help("Read the cloud provider from the kernel cmdline")) + .arg(Arg::with_name("hostname") + .long("hostname") + .help("The file into which the hostname should be written") + .takes_value(true)) + .arg(Arg::with_name("network-units") + .long("network-units") + .help("The directory into which network units are written") + .takes_value(true)) + .arg(Arg::with_name("provider") + .long("provider") + .help("The name of the cloud provider") + .takes_value(true)) + .arg(Arg::with_name("ssh-keys") + .long("ssh-keys") + .help("Update SSH keys for the given user") + .takes_value(true)) + .get_matches(); + + // return configuration + Ok(Config { + provider: match matches.value_of("provider") { + Some(provider) => String::from(provider), + None => if matches.is_present("cmdline") { + get_oem()? + } else { + return Err("Must set either --provider or --cmdline".into()); + } + }, + attributes_file: matches.value_of("attributes").map(String::from), + ssh_keys_user: matches.value_of("ssh-keys").map(String::from), + hostname_file: matches.value_of("hostname").map(String::from), + network_units_dir: matches.value_of("network-units").map(String::from), + }) +} + +fn get_oem() -> Result { + // open the cmdline file + let mut file = File::open(CMDLINE_PATH) + .chain_err(|| format!("Failed to open cmdline file ({})", CMDLINE_PATH))?; + + // read the contents + let mut contents = String::new(); + file.read_to_string(&mut contents) + .chain_err(|| format!("Failed to read cmdline file ({})", CMDLINE_PATH))?; + + // split the contents into elements + let params: Vec> = contents.split(' ') + .map(|s| s.split('=').collect()) + .collect(); + + // find the oem flag + for p in params { + if p.len() > 1 && p[0] == CMDLINE_OEM_FLAG { + return Ok(String::from(p[1])); + } + } + + Err(format!("Couldn't find '{}' flag in cmdline file ({})", CMDLINE_OEM_FLAG, CMDLINE_PATH).into()) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..f9b4546d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,105 @@ +// Copyright 2017 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. + +#![cfg_attr(feature="clippy", feature(plugin))] +#![cfg_attr(feature="clippy", plugin(clippy))] + +#[macro_use] +extern crate hyper; +extern crate reqwest; +#[cfg(test)] +extern crate mockito; + +extern crate base64; + +#[macro_use] +extern crate error_chain; + +#[macro_use] +extern crate slog; +#[macro_use] +extern crate slog_scope; + +#[macro_use] +extern crate serde_derive; +extern crate serde; +extern crate serde_xml_rs; +extern crate serde_json; + +extern crate pnet; + +extern crate openssl; +extern crate openssh_keys; +extern crate update_ssh_keys; + +extern crate users; +extern crate hostname; +extern crate tempdir; +extern crate nix; + +extern crate ipnetwork; + + +mod providers; +mod metadata; +mod network; +mod retry; +mod util; + +pub mod errors { + error_chain!{ + links { + PublicKey(::openssh_keys::errors::Error, ::openssh_keys::errors::ErrorKind); + AuthorizedKeys(::update_ssh_keys::errors::Error, ::update_ssh_keys::errors::ErrorKind); + } + foreign_links { + Log(::slog::Error); + XmlDeserialize(::serde_xml_rs::Error); + Base64Decode(::base64::DecodeError); + Io(::std::io::Error); + Reqwest(::reqwest::Error); + Hyper(::hyper::error::Error); + } + errors { + UnknownProvider(p: String) { + description("unknown provider") + display("unknown provider '{}'", p) + } + } + } +} + +use providers::*; +use metadata::Metadata; + +use errors::*; + +/// `fetch_metadata` is the generic, top-level function that is used by the main +/// function to fetch metadata. The configured provider is passed in and this +/// function dispatches the call to the correct provider-specific fetch function +pub fn fetch_metadata(provider: &str) -> Result { + match provider { + "azure" => azure::fetch_metadata(), + "cloudstack-metadata" => cloudstack::network::fetch_metadata(), + "cloudstack-configdrive" => cloudstack::configdrive::fetch_metadata(), + "digitalocean" => digitalocean::fetch_metadata(), + "ec2" => ec2::fetch_metadata(), + "gce" => gce::fetch_metadata(), + "openstack-metadata" => openstack::network::fetch_metadata(), + "oracle-oci" => oracle::fetch_metadata(), + "packet" => packet::fetch_metadata(), + "vagrant-virtualbox" => vagrant_virtualbox::fetch_metadata(), + _ => Err(errors::ErrorKind::UnknownProvider(provider.to_owned()).into()), + } +} diff --git a/src/metadata.rs b/src/metadata.rs new file mode 100644 index 00000000..06fe7bb9 --- /dev/null +++ b/src/metadata.rs @@ -0,0 +1,181 @@ +// Copyright 2017 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. + +use std::fs; +use std::fs::File; +use std::path::Path; +use std::io::prelude::*; +use std::collections::HashMap; +use users; +use openssh_keys::PublicKey; +use update_ssh_keys::AuthorizedKeys; +use network; + +use errors::*; + +#[derive(Default, Debug, Clone)] +pub struct MetadataBuilder { + metadata: Metadata, +} + +#[derive(Default, Debug, Clone)] +pub struct Metadata { + attributes: HashMap, + hostname: Option, + ssh_keys: Vec, + network: Vec, + net_dev: Vec, +} + +fn create_file(filename: &str) -> Result { + let file_path = Path::new(&filename); + // create the directories if they don't exist + let folder = file_path.parent() + .ok_or_else(|| format!("could not get parent directory of {:?}", file_path))?; + fs::create_dir_all(&folder) + .chain_err(|| format!("failed to create directory {:?}", folder))?; + // create (or truncate) the file we want to write to + File::create(file_path) + .chain_err(|| format!("failed to create file {:?}", file_path)) +} + +impl MetadataBuilder { + pub fn new() -> Self { + MetadataBuilder { + metadata: Metadata::new(), + } + } + + pub fn add_attribute(mut self, key: String, value: String) -> Self { + self.metadata.attributes.insert(key, value); + self + } + + pub fn add_attribute_if_exists(self, key: String, value: Option) -> Self { + match value { + Some(v) => self.add_attribute(key, v), + None => self + } + } + + pub fn set_hostname(mut self, hostname: String) -> Self { + self.metadata.hostname = Some(hostname); + self + } + + pub fn set_hostname_if_exists(self, hostname: Option) -> Self { + match hostname { + Some(v) => self.set_hostname(v), + None => self + } + } + + pub fn add_ssh_keys(mut self, ssh_keys: Vec) -> Result { + for key in ssh_keys { + let key = PublicKey::parse(&key)?; + self.metadata.ssh_keys.push(key); + } + Ok(self) + } + + pub fn add_publickeys(mut self, mut ssh_keys: Vec) -> Self { + self.metadata.ssh_keys.append(&mut ssh_keys); + self + } + + pub fn add_network_interface(mut self, interface: network::Interface) -> Self { + self.metadata.network.push(interface); + self + } + + pub fn add_network_device(mut self, device: network::Device) -> Self { + self.metadata.net_dev.push(device); + self + } + + pub fn build(self) -> Metadata { + self.metadata + } +} + +impl Metadata { + pub fn builder() -> MetadataBuilder { + MetadataBuilder::new() + } + + pub fn new() -> Self { + Metadata { + attributes: HashMap::new(), + hostname: None, + ssh_keys: vec![], + network: vec![], + net_dev: vec![], + } + } + + pub fn write_attributes(&self, attributes_file_path: String) -> Result<()> { + let mut attributes_file = create_file(&attributes_file_path)?; + for (k,v) in &self.attributes { + write!(&mut attributes_file, "COREOS_{}={}\n", k, v) + .chain_err(|| format!("failed to write attributes to file {:?}", attributes_file))?; + } + Ok(()) + } + pub fn write_ssh_keys(&self, ssh_keys_user: String) -> Result<()> { + // find the ssh keys user and open their ssh authorized keys directory + let user = users::get_user_by_name(&ssh_keys_user) + .ok_or_else(|| format!("could not find user with username {:?}", ssh_keys_user))?; + let mut authorized_keys_dir = AuthorizedKeys::open(user, true, None) + .chain_err(|| format!("failed to open authorzied keys directory for user '{}'", ssh_keys_user))?; + + // add the ssh keys to the directory + authorized_keys_dir.add_keys("coreos-metadata", self.ssh_keys.clone(), true, true)?; + + // write the changes and sync the directory + authorized_keys_dir.write() + .chain_err(|| "failed to update authorized keys directory")?; + authorized_keys_dir.sync() + .chain_err(|| "failed to update authorized keys") + } + pub fn write_hostname(&self, hostname_file_path: String) -> Result<()> { + match self.hostname { + Some(ref hostname) => { + let mut hostname_file = create_file(&hostname_file_path)?; + write!(&mut hostname_file, "{}\n", hostname) + .chain_err(|| format!("failed to write hostname {:?} to file {:?}", self.hostname, hostname_file)) + } + None => Ok(()) + } + } + pub fn write_network_units(&self, network_units_dir: String) -> Result<()> { + let dir_path = Path::new(&network_units_dir); + fs::create_dir_all(&dir_path) + .chain_err(|| format!("failed to create directory {:?}", dir_path))?; + for interface in &self.network { + let file_path = dir_path.join(interface.unit_name()); + let mut unit_file = File::create(&file_path) + .chain_err(|| format!("failed to create file {:?}", file_path))?; + write!(&mut unit_file, "{}", interface.config()) + .chain_err(|| format!("failed to write network interface unit file {:?}", unit_file))?; + } + for device in &self.net_dev { + let file_path = dir_path.join(device.unit_name()); + let mut unit_file = File::create(&file_path) + .chain_err(|| format!("failed to create file {:?}", file_path))?; + write!(&mut unit_file, "{}", device.config()) + .chain_err(|| format!("failed to write network device unit file {:?}", unit_file))?; + } + Ok(()) + } +} diff --git a/src/network.rs b/src/network.rs new file mode 100644 index 00000000..45f440a8 --- /dev/null +++ b/src/network.rs @@ -0,0 +1,376 @@ +// Copyright 2017 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. + +//! network deals abstracts away the manipulation of network device and +//! interface unit files. All that is left is to write the resulting string to +//! the necessary unit. + +use std::net::IpAddr; +use pnet::util::MacAddr; +use std::string::String; +use std::string::ToString; + +use ipnetwork::IpNetwork; +use errors::*; + +pub const BONDING_MODE_BALANCE_RR: u32 = 0; +pub const BONDING_MODE_ACTIVE_BACKUP: u32 = 1; +pub const BONDING_MODE_BALANCE_XOR: u32 = 2; +pub const BONDING_MODE_BROADCAST: u32 = 3; +pub const BONDING_MODE_LACP: u32 = 4; +pub const BONDING_MODE_BALANCE_TLB: u32 = 5; +pub const BONDING_MODE_BALANCE_ALB: u32 = 6; + +const BONDING_MODES: [(u32,&str); 7] = [ + (BONDING_MODE_BALANCE_RR,"balance-rr"), + (BONDING_MODE_ACTIVE_BACKUP,"active-backup"), + (BONDING_MODE_BALANCE_XOR,"balance-xor"), + (BONDING_MODE_BROADCAST,"broadcast"), + (BONDING_MODE_LACP,"802.3ad"), + (BONDING_MODE_BALANCE_TLB,"balance-tlb"), + (BONDING_MODE_BALANCE_ALB,"balance-alb"), +]; + +pub fn bonding_mode_to_string(mode: &u32) -> Result { + for &(m,s) in &BONDING_MODES { + if m == *mode { + return Ok(s.to_owned()) + } + } + Err(format!("no such bonding mode: {}", mode).into()) +} + +#[derive(Clone, Copy, Debug)] +pub struct NetworkRoute { + pub destination: IpNetwork, + pub gateway: IpAddr, +} + +/// for naming purposes an interface needs either a name or an address. +/// it can have both. but it can't have neither. +/// there isn't really a way to express this in the type system +/// so we just panic! if it's not what we expected. +/// I guess that there aren't really type systems with inclusive disjunction +/// so it's not really that big of a deal. +#[derive(Clone, Debug)] +pub struct Interface { + pub name: Option, + pub mac_address: Option, + pub priority: Option, + pub nameservers: Vec, + pub ip_addresses: Vec, + pub routes: Vec, + pub bond: Option, +} + +#[derive(Clone, Debug)] +pub struct Section { + pub name: String, + pub attributes: Vec<(String, String)>, +} + +#[derive(Clone, Debug)] +pub struct Device { + pub name: String, + pub kind: String, + pub mac_address: MacAddr, + pub priority: Option, + pub sections: Vec
+} + +impl Interface { + pub fn unit_name(&self) -> String { + format!("{:02}-{}.network", + self.priority.unwrap_or(10), + self.name.clone().unwrap_or_else( + // needs to be a lambda or we panic immediately + // yay, manual thunking! + ||self.mac_address.unwrap_or_else( + ||panic!("interface needs either name or mac address (or both)") + ).to_string() + )) + } + pub fn config(&self) -> String { + let mut config = String::new(); + + // [Match] section + config.push_str("[Match]\n"); + self.name.clone().map(|name| config.push_str(&format!("Name={}\n", name))); + self.mac_address.map(|mac| config.push_str(&format!("MACAddress={}\n", mac))); + + // [Network] section + config.push_str("\n[Network]\n"); + for ns in &self.nameservers { + config.push_str(&format!("DNS={}\n", ns)) + } + self.bond.clone().map(|bond| config.push_str(&format!("Bond={}\n", bond))); + + // [Address] sections + for addr in &self.ip_addresses { + config.push_str(&format!("\n[Address]\nAddress={}\n", addr)); + } + + // [Route] sections + for route in &self.routes { + config.push_str(&format!("\n[Route]\nDestination={}\nGateway={}\n", route.destination, route.gateway)); + } + + config + } +} + +impl Device { + pub fn unit_name(&self) -> String { + format!("{:02}-{}.netdev", self.priority.unwrap_or(10), self.name) + } + pub fn config(&self) -> String { + let mut config = String::new(); + + // [NetDev] section + config.push_str("[NetDev]\n"); + config.push_str(&format!("Name={}\n", self.name)); + config.push_str(&format!("Kind={}\n", self.kind)); + config.push_str(&format!("MACAddress={}\n", self.mac_address)); + + // custom sections + for section in &self.sections { + config.push_str(&format!("\n[{}]\n", section.name)); + for attr in §ion.attributes { + config.push_str(&format!("{}={}\n", attr.0, attr.1)); + } + } + + config + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::net::{Ipv4Addr, Ipv6Addr}; + use ipnetwork::{Ipv4Network,Ipv6Network}; + + #[test] + fn mac_addr_display() { + let m = MacAddr(0xf4,0x00,0x34,0x09,0x73,0xee); + assert_eq!(m.to_string(), "f4:00:34:09:73:ee"); + } + + #[test] + fn interface_unit_name() { + let is = vec![ + (Interface { + name: Some(String::from("lo")), + mac_address: Some(MacAddr(0,0,0,0,0,0)), + priority: Some(20), + nameservers: vec![], + ip_addresses: vec![], + routes: vec![], + bond: None, + }, "20-lo.network"), + (Interface { + name: Some(String::from("lo")), + mac_address: Some(MacAddr(0,0,0,0,0,0)), + priority: None, + nameservers: vec![], + ip_addresses: vec![], + routes: vec![], + bond: None, + }, "10-lo.network"), + (Interface { + name: None, + mac_address: Some(MacAddr(0,0,0,0,0,0)), + priority: Some(20), + nameservers: vec![], + ip_addresses: vec![], + routes: vec![], + bond: None, + }, "20-00:00:00:00:00:00.network"), + (Interface { + name: Some(String::from("lo")), + mac_address: None, + priority: Some(20), + nameservers: vec![], + ip_addresses: vec![], + routes: vec![], + bond: None, + }, "20-lo.network"), + ]; + + for (i, s) in is { + assert_eq!(i.unit_name(), s); + } + } + + #[test] + #[should_panic] + fn interface_unit_name_no_name_no_mac() { + let i = Interface { + name: None, + mac_address: None, + priority: Some(20), + nameservers: vec![], + ip_addresses: vec![], + routes: vec![], + bond: None, + }; + let _name = i.unit_name(); + } + + #[test] + fn device_unit_name() { + let ds = vec![ + (Device { + name: String::from("vlan0"), + kind: String::from("vlan"), + mac_address: MacAddr(0,0,0,0,0,0), + priority: Some(20), + sections: vec![], + }, "20-vlan0.netdev"), + (Device { + name: String::from("vlan0"), + kind: String::from("vlan"), + mac_address: MacAddr(0,0,0,0,0,0), + priority: None, + sections: vec![], + }, "10-vlan0.netdev"), + ]; + + for (d, s) in ds { + assert_eq!(d.unit_name(), s); + } + } + + #[test] + fn interface_config() { + let is = vec![ + (Interface { + name: Some(String::from("lo")), + mac_address: Some(MacAddr(0,0,0,0,0,0)), + priority: Some(20), + nameservers: vec![ + IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)), + ], + ip_addresses: vec![ + IpNetwork::V4(Ipv4Network::new( + Ipv4Addr::new(127, 0, 0, 1), + 8 + ).unwrap()), + IpNetwork::V6(Ipv6Network::new( + Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1), + 128 + ).unwrap()), + ], + routes: vec![ + NetworkRoute { + destination: IpNetwork::V4(Ipv4Network::new( + Ipv4Addr::new(127, 0, 0, 1), + 8 + ).unwrap()), + gateway: IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + } + ], + bond: Some(String::from("james")), + }, "[Match] +Name=lo +MACAddress=00:00:00:00:00:00 + +[Network] +DNS=127.0.0.1 +DNS=::1 +Bond=james + +[Address] +Address=127.0.0.1/8 + +[Address] +Address=::1/128 + +[Route] +Destination=127.0.0.1/8 +Gateway=127.0.0.1 +"), + // this isn't really a valid interface object, but it's testing + // the minimum possible configuration for all peices at the same + // time, so I'll allow it. (sdemos) + (Interface { + name: None, + mac_address: None, + priority: None, + nameservers: vec![], + ip_addresses: vec![], + routes: vec![], + bond: None, + }, "[Match] + +[Network] +") + ]; + + for (i, s) in is { + assert_eq!(i.config(), s); + } + } + + #[test] + fn device_config() { + let ds = vec![ + (Device { + name: String::from("vlan0"), + kind: String::from("vlan"), + mac_address: MacAddr(0,0,0,0,0,0), + priority: Some(20), + sections: vec![ + Section { + name: String::from("Test"), + attributes: vec![ + (String::from("foo"), String::from("bar")), + (String::from("oingo"), String::from("boingo")), + ] + }, + Section { + name: String::from("Empty"), + attributes: vec![], + } + ], + }, "[NetDev] +Name=vlan0 +Kind=vlan +MACAddress=00:00:00:00:00:00 + +[Test] +foo=bar +oingo=boingo + +[Empty] +"), + (Device { + name: String::from("vlan0"), + kind: String::from("vlan"), + mac_address: MacAddr(0,0,0,0,0,0), + priority: Some(20), + sections: vec![], + }, "[NetDev] +Name=vlan0 +Kind=vlan +MACAddress=00:00:00:00:00:00 +") + ]; + + for (d, s) in ds { + assert_eq!(d.config(), s); + } + } +} diff --git a/src/providers/azure/crypto/mod.rs b/src/providers/azure/crypto/mod.rs new file mode 100644 index 00000000..90c5fc35 --- /dev/null +++ b/src/providers/azure/crypto/mod.rs @@ -0,0 +1,79 @@ +// Copyright 2017 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. + +//! crypto module takes care of cryptographic functions + +pub mod x509; + +use openssl::x509::X509; +use openssl::pkey::PKey; +use openssl::cms::CmsContentInfo; +use openssl::pkcs12::Pkcs12; + +use openssh_keys::PublicKey; + +use errors::*; + +pub fn mangle_pem(x509: &X509) -> Result { + // get the pem + let pem = x509.to_pem() + .chain_err(|| "failed to convert x509 cert to pem")?; + let pem = String::from_utf8(pem) + .chain_err(|| "failed to convert x509 pem file from utf8 to a string")?; + + // the pem needs to be mangled to send as a header + Ok(pem.lines() + .filter(|l| !l.contains("BEGIN CERTIFICATE") && !l.contains("END CERTIFICATE")) + .fold(String::new(), |mut s, l| {s.push_str(l); s})) +} + +pub fn decrypt_cms(smime: &[u8], pkey: &PKey, x509: &X509) -> Result> { + // now we need to read in that mime file + let cms = CmsContentInfo::smime_read_cms(smime) + .chain_err(|| "failed to read cms file")?; + + // and decrypt it's contents + let p12_der = cms.decrypt(pkey, x509) + .chain_err(|| "failed to decrypt cms file")?; + + Ok(p12_der) +} + +pub fn p12_to_ssh_pubkey(p12_der: &[u8]) -> Result { + // the contents of that encrypted cms blob we got are actually a different + // cryptographic structure. we read that in from the contents and parse it. + // PKCS12 has the ability to have a password, but we don't have one, hence + // empty string. + let p12 = Pkcs12::from_der(p12_der) + .chain_err(|| "failed to get pkcs12 blob from der")?; + let p12 = p12.parse("") + .chain_err(|| "failed to parse pkcs12 blob")?; + + // PKCS12 has three parts. A pkey, a main x509 cert, and a list of other + // x509 certs. The list of other x509 certs is called the chain. there is + // only one cert in this chain, and it is the ssh public key. + let ssh_pubkey_pem = p12.chain.get(0).unwrap(); + // .chain_err(|| "failed to get cert from pkcs12 chain")?; + let ssh_pubkey_pem = ssh_pubkey_pem.public_key() // get the public key from the x509 cert + .chain_err(|| "failed to get public key from cert")?; + let ssh_pubkey_pem = ssh_pubkey_pem.rsa() // get the rsa contents from the pkey struct + .chain_err(|| "failed to get rsa contents from pkey")?; + + // convert the openssl Rsa public key to an OpenSSH public key in string format + let e = ssh_pubkey_pem.e().unwrap().to_vec(); + let n = ssh_pubkey_pem.n().unwrap().to_vec(); + let ssh_pubkey = PublicKey::from_rsa(e, n); + + Ok(ssh_pubkey) +} diff --git a/src/providers/azure/crypto/x509.rs b/src/providers/azure/crypto/x509.rs new file mode 100644 index 00000000..f86ccc66 --- /dev/null +++ b/src/providers/azure/crypto/x509.rs @@ -0,0 +1,142 @@ +// Copyright 2017 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. + +//! Generate X509 certificate and associated RSA public/private keypair + +use openssl::x509::{X509, X509Name}; +use openssl::rsa::Rsa; +use openssl::pkey::PKey; +use openssl::hash::MessageDigest; +use openssl::asn1::Asn1Time; +use openssl::bn; +use openssl::conf::{Conf, ConfMethod}; +use openssl::x509::extension; + +use errors::*; + +pub struct Config { + rsa_bits: u32, + expire_in_days: u32, +} + +impl Config { + pub fn new(rsa_bits: u32, expire_in_days: u32) -> Self { + Config { + rsa_bits: rsa_bits, + expire_in_days: expire_in_days, + } + } +} + +pub fn generate_cert(config: &Config) -> Result<(X509, PKey)> { + // generate an rsa public/private keypair + let rsa = Rsa::generate(config.rsa_bits) + .chain_err(|| "failed to generate rsa keypair")?; + // put it into the pkey struct + let pkey = PKey::from_rsa(rsa) + .chain_err(|| "failed to create pkey struct from rsa keypair")?; + + // make a new x509 certificate with the pkey we generated + let mut x509builder = X509::builder() + .chain_err(|| "failed to make x509 builder")?; + x509builder.set_version(2) + .chain_err(|| "failed to set x509 version")?; + + // set the serial number to some big random positive integer + let mut serial = bn::BigNum::new() + .chain_err(|| "failed to make new bignum")?; + serial.rand(32, bn::MSB_ONE, false) + .chain_err(|| "failed to generate random bignum")?; + let serial = serial.to_asn1_integer() + .chain_err(|| "failed to get asn1 integer from bignum")?; + x509builder.set_serial_number(&serial) + .chain_err(|| "failed to set x509 serial number")?; + + // call fails without expiration dates + // I guess they are important anyway, but still + x509builder.set_not_before(&Asn1Time::days_from_now(0).unwrap()) + .chain_err(|| "failed to set x509 start date")?; + x509builder.set_not_after(&Asn1Time::days_from_now(config.expire_in_days).unwrap()) + .chain_err(|| "failed to set x509 expiration date")?; + + // add the issuer and subject name + // it's set to "/CN=LinuxTransport" + // if we want we can make that configurable later + let mut x509namebuilder = X509Name::builder() + .chain_err(|| "failed to get x509name builder")?; + x509namebuilder.append_entry_by_text("CN", "LinuxTransport") + .chain_err(|| "failed to append /CN=LinuxTransport to x509name builder")?; + let x509name = x509namebuilder.build(); + x509builder.set_issuer_name(&x509name) + .chain_err(|| "failed to set x509 issuer name")?; + x509builder.set_subject_name(&x509name) + .chain_err(|| "failed to set x509 subject name")?; + + // set the public key + x509builder.set_pubkey(&pkey) + .chain_err(|| "failed to set x509 pubkey")?; + + // it also needs several extensions + // in the openssl configuration file, these are set when generating certs + // basicConstraints=CA:true + // subjectKeyIdentifier=hash + // authorityKeyIdentifier=keyid:always,issuer + // that means these extensions get added to certs generated using the + // command line tool automatically. but since we are constructing it, we + // need to add them manually. + // we need to do them one at a time, and they need to be in this order + let conf = Conf::new(ConfMethod::default()) + .chain_err(|| "failed to make new conf struct")?; + // it seems like everything depends on the basic constraints, so let's do + // that first. + let bc = extension::BasicConstraints::new() + .ca() + .build() + .chain_err(|| "failed to build BasicConstraints extension")?; + x509builder.append_extension(bc) + .chain_err(|| "failed to append BasicConstraints extension")?; + + // the akid depends on the skid. I guess it copies the skid when the cert is + // self-signed or something, I'm not really sure. + let skid = { + // we need to wrap these in a block because the builder gets borrowed away + // from us + let ext_con = x509builder.x509v3_context(None, Some(&conf)); + extension::SubjectKeyIdentifier::new() + .build(&ext_con) + .chain_err(|| "failed to build SubjectKeyIdentifier extention")? + }; + x509builder.append_extension(skid) + .chain_err(|| "failed to append SubjectKeyIdentifier extention")?; + + // now that the skid is added we can add the akid + let akid = { + let ext_con = x509builder.x509v3_context(None, Some(&conf)); + extension::AuthorityKeyIdentifier::new() + .keyid(true) + .issuer(false) + .build(&ext_con) + .chain_err(|| "failed to build AuthorityKeyIdentifier extention")? + }; + x509builder.append_extension(akid) + .chain_err(|| "failed to append AuthorityKeyIdentifier extention")?; + + // self-sign the certificate + x509builder.sign(&pkey, MessageDigest::sha256()) + .chain_err(|| "failed to self-sign x509 cert")?; + + let x509 = x509builder.build(); + + Ok((x509, pkey)) +} diff --git a/src/providers/azure/mod.rs b/src/providers/azure/mod.rs new file mode 100644 index 00000000..49f7b979 --- /dev/null +++ b/src/providers/azure/mod.rs @@ -0,0 +1,293 @@ +// Copyright 2017 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. + +//! azure metadata fetcher + +mod crypto; + +use self::crypto::x509; +use errors::*; +use metadata::Metadata; +use retry; +use util; +use openssh_keys::PublicKey; + +use std::net::{IpAddr, SocketAddr}; + +header! {(MSAgentName, "x-ms-agent-name") => [String]} +header! {(MSVersion, "x-ms-version") => [String]} +header! {(MSCipherName, "x-ms-cipher-name") => [String]} +header! {(MSCert, "x-ms-guest-agent-public-x509-cert") => [String]} + +const OPTION_245: &str = "OPTION_245"; +const MS_AGENT_NAME: &str = "com.coreos.metadata"; +const MS_VERSION: &str = "2012-11-30"; +const SMIME_HEADER: &str = "\ +MIME-Version:1.0 +Content-Disposition: attachment; filename=/home/core/encrypted-ssh-cert.pem +Content-Type: application/x-pkcs7-mime; name=/home/core/encrypted-ssh-cert.pem +Content-Transfer-Encoding: base64 + +"; + +#[derive(Debug, Deserialize, Clone, Default)] +struct GoalState { + #[serde(rename = "Container")] + pub container: Container +} + +#[derive(Debug, Deserialize, Clone, Default)] +struct Container { + #[serde(rename = "RoleInstanceList")] + pub role_instance_list: RoleInstanceList +} + +#[derive(Debug, Deserialize, Clone, Default)] +struct RoleInstanceList { + #[serde(rename = "RoleInstance", default)] + pub role_instances: Vec +} + +#[derive(Debug, Deserialize, Clone)] +struct RoleInstance { + #[serde(rename = "Configuration")] + pub configuration: Configuration +} + +#[derive(Debug, Deserialize, Clone)] +struct Configuration { + #[serde(rename = "Certificates", default)] + pub certificates: String, + #[serde(rename = "SharedConfig", default)] + pub shared_config: String, +} + +#[derive(Debug, Deserialize, Clone)] +struct CertificatesFile { + #[serde(rename = "Data", default)] + pub data: String +} + +#[derive(Debug, Deserialize, Clone)] +struct Versions { + #[serde(rename = "Supported")] + pub supported: Supported +} + +#[derive(Debug, Deserialize, Clone)] +struct Supported { + #[serde(rename = "Version", default)] + pub versions: Vec +} + +#[derive(Debug, Deserialize, Clone)] +struct SharedConfig { + #[serde(rename = "Incarnation")] + pub incarnation: Incarnation, + #[serde(rename = "Instances")] + pub instances: Instances, +} + +#[derive(Debug, Deserialize, Clone)] +struct Incarnation { + pub instance: String, +} + +#[derive(Debug, Deserialize, Clone)] +struct Instances { + #[serde(rename = "Instance", default)] + pub instances: Vec, +} + +#[derive(Debug, Deserialize, Clone)] +struct Instance { + pub id: String, + pub address: String, + #[serde(rename = "InputEndpoints")] + pub input_endpoints: InputEndpoints, +} + +#[derive(Debug, Deserialize, Clone)] +struct InputEndpoints { + #[serde(rename = "Endpoint", default)] + pub endpoints: Vec, +} + +#[derive(Debug, Deserialize, Clone)] +struct Endpoint { + #[serde(rename = "loadBalancedPublicAddress", default)] + pub load_balanced_public_address: String, +} + +#[derive(Debug, Copy, Clone, Default)] +struct Attributes { + pub virtual_ipv4: Option, + pub dynamic_ipv4: Option, +} + +#[derive(Debug, Clone)] +struct Azure { + client: retry::Client, + endpoint: IpAddr, + goal_state: GoalState, +} + +impl Azure { + fn new() -> Result { + let addr = Azure::get_fabric_address() + .chain_err(|| "failed to get fabric address")?; + let client = retry::Client::new()? + .header(MSAgentName(MS_AGENT_NAME.to_owned())) + .header(MSVersion(MS_VERSION.to_owned())); + + let mut azure = Azure { + client: client, + endpoint: addr, + goal_state: GoalState::default(), + }; + + // make sure the metadata service is compatible with our version + azure.is_fabric_compatible(MS_VERSION) + .chain_err(|| "failed version compatibility check")?; + + // populate goalstate + azure.goal_state = azure.get_goal_state()?; + Ok(azure) + } + + fn get_goal_state(&self) -> Result { + self.client.get(retry::Xml, format!("http://{}/machine/?comp=goalstate", self.endpoint)).send() + .chain_err(|| "failed to get goal state")? + .ok_or_else(|| "failed to get goal state: not found response".into()) + } + + fn get_fabric_address() -> Result { + let v = util::dns_lease_key_lookup(OPTION_245)?; + // value is an 8 digit hex value. convert it to u32 and + // then parse that into an ip. Ipv4Addr::from(u32) + // performs conversion from big-endian + trace!("found fabric address in hex - {:?}", v); + let dec = u32::from_str_radix(&v, 16) + .chain_err(|| format!("failed to convert '{}' from hex", v))?; + Ok(IpAddr::V4(dec.into())) + } + + fn is_fabric_compatible(&self, version: &str) -> Result<()> { + let versions: Versions = self.client.get(retry::Xml, format!("http://{}/?comp=versions", self.endpoint)).send() + .chain_err(|| "failed to get versions")? + .ok_or_else(|| "failed to get versions: not found")?; + + if versions.supported.versions.iter().any(|v| v == version) { + Ok(()) + } else { + Err(format!("fabric version {} not compatible with fabric address {}", MS_VERSION, self.endpoint).into()) + } + } + + fn get_certs_endpoint(&self) -> Result { + // grab the certificates endpoint from the xml and return it + let cert_endpoint: &str = &self.goal_state.container.role_instance_list.role_instances[0].configuration.certificates; + Ok(String::from(cert_endpoint)) + } + + fn get_certs(&self, mangled_pem: String) -> Result { + // get the certificates + let endpoint = self.get_certs_endpoint() + .chain_err(|| "failed to get certs endpoint")?; + + let certs: CertificatesFile = self.client.get(retry::Xml, endpoint) + .header(MSCipherName("DES_EDE3_CBC".to_owned())) + .header(MSCert(mangled_pem)) + .send() + .chain_err(|| "failed to get certificates")? + .ok_or_else(|| "failed to get certificates: not found")?; + + // the cms decryption expects it to have MIME information on the top + // since cms is really for email attachments...don't tell the cops. + let mut smime = String::from(SMIME_HEADER); + smime.push_str(&certs.data); + + Ok(smime) + } + + // put it all together + fn get_ssh_pubkey(&self) -> Result { + // first we have to get the certificates endoint. + // we have to generate the rsa public/private keypair and the x509 cert + // that we use to make the request. this is equivalent to + // `openssl req -x509 -nodes -subj /CN=LinuxTransport -days 365 -newkey rsa:2048 -keyout private.pem -out cert.pem` + let (x509, pkey) = x509::generate_cert(&x509::Config::new(2048, 365)) + .chain_err(|| "failed to generate keys")?; + + // mangle the pem file for the request + let mangled_pem = crypto::mangle_pem(&x509) + .chain_err(|| "failed to mangle pem")?; + + // fetch the encrypted cms blob from the certs endpoint + let smime = self.get_certs(mangled_pem) + .chain_err(|| "failed to get certs")?; + + // decrypt the cms blob + let p12 = crypto::decrypt_cms(smime.as_bytes(), &pkey, &x509) + .chain_err(|| "failed to decrypt cms blob")?; + + // convert that to the OpenSSH public key format + let ssh_pubkey = crypto::p12_to_ssh_pubkey(&p12) + .chain_err(|| "failed to convert pkcs12 blob to ssh pubkey")?; + + Ok(ssh_pubkey) + } + + fn get_attributes(&self) -> Result { + let endpoint = &self.goal_state.container.role_instance_list.role_instances[0].configuration.shared_config; + + let shared_config: SharedConfig = self.client.get(retry::Xml, endpoint.to_string()).send() + .chain_err(|| "failed to get shared configuration")? + .ok_or_else(|| "failed to get shared configuration: not found")?; + + let mut attributes = Attributes::default(); + + for instance in shared_config.instances.instances { + if instance.id == shared_config.incarnation.instance { + attributes.dynamic_ipv4 = Some(instance.address.parse() + .chain_err(|| format!("failed to parse instance ip address: {}", instance.address))?); + for endpoint in instance.input_endpoints.endpoints { + attributes.virtual_ipv4 = match endpoint.load_balanced_public_address.parse::() { + Ok(lbpa) => Some(lbpa.ip()), + Err(_) => continue, + }; + } + } + } + + Ok(attributes) + } +} + +pub fn fetch_metadata() -> Result { + let provider = Azure::new() + .chain_err(|| "azure: failed create metadata client")?; + + let ssh_pubkey = provider.get_ssh_pubkey() + .chain_err(|| "azure: failed to get ssh pubkey")?; + + let attributes = provider.get_attributes() + .chain_err(|| "azure: failed to get attributes")?; + + Ok(Metadata::builder() + .add_publickeys(vec![ssh_pubkey]) + .add_attribute_if_exists("AZURE_IPV4_DYNAMIC".to_string(), attributes.dynamic_ipv4.map(|x| x.to_string())) + .add_attribute_if_exists("AZURE_IPV4_VIRTUAL".to_string(), attributes.virtual_ipv4.map(|x| x.to_string())) + .build()) +} diff --git a/src/providers/cloudstack/configdrive.rs b/src/providers/cloudstack/configdrive.rs new file mode 100644 index 00000000..31988285 --- /dev/null +++ b/src/providers/cloudstack/configdrive.rs @@ -0,0 +1,104 @@ +//! configdrive metadata fetcher for cloudstack + +use errors::*; +use metadata::Metadata; +use nix::mount; +use openssh_keys::PublicKey; +use std::path::{Path, PathBuf}; +use std::fs::File; +use std::io::Read; +use tempdir::TempDir; + +const CONFIG_DRIVE_LABEL_1: &'static str = "config-2"; +const CONFIG_DRIVE_LABEL_2: &'static str = "CONFIG-2"; + +#[derive(Debug)] +struct ConfigDrive { + temp_dir: Option, + target: PathBuf, + path: PathBuf, +} + +impl ConfigDrive { + fn new() -> Result { + // maybe its already mounted + let path = Path::new("/media/ConfigDrive/cloudstack/metadata/"); + if path.exists() { + return Ok(ConfigDrive { + temp_dir: None, + path: path.to_owned(), + target: path.to_owned(), + }) + } + + // if not try and mount with each of the labels + let target = TempDir::new("coreos-metadata") + .chain_err(|| "failed to create temporary directory")?; + mount_ro(&Path::new("/dev/disk/by-label/").join(CONFIG_DRIVE_LABEL_1), target.path(), "iso9660") + .or_else(|_| mount_ro(&Path::new("/dev/disk/by-label/").join(CONFIG_DRIVE_LABEL_2), target.path(), "iso9660"))?; + + Ok(ConfigDrive { + path: target.path().join("cloudstack").join("metadata"), + target: target.path().to_owned(), + temp_dir: Some(target), + }) + } + + fn fetch_value(&self, key: &str) -> Result> { + let filename = self.path.join(format!("{}.txt", key)); + + if !filename.exists() { + return Ok(None) + } + + let mut file = File::open(&filename) + .chain_err(|| format!("failed to open file '{:?}'", filename))?; + + let mut contents = String::new(); + file.read_to_string(&mut contents) + .chain_err(|| format!("failed to read from file '{:?}'", filename))?; + + Ok(Some(contents)) + } + + fn fetch_publickeys(&self) -> Result> { + let filename = self.path.join("public_keys.txt"); + let file = File::open(&filename) + .chain_err(|| format!("failed to open file '{:?}'", filename))?; + + PublicKey::read_keys(file) + .chain_err(|| "failed to read public keys from config drive file") + } +} + +impl ::std::ops::Drop for ConfigDrive { + fn drop(&mut self) { + if self.temp_dir.is_some() { + unmount(&self.path).unwrap(); + } + } +} + +fn mount_ro(source: &Path, target: &Path, fstype: &str) -> Result<()> { + mount::mount(Some(source), target, Some(fstype), mount::MS_RDONLY, None::<&str>) + .chain_err(|| format!("failed to read-only mount source '{:?}' to target '{:?}' with filetype '{}'", source, target, fstype)) +} + +fn unmount(target: &Path) -> Result<()> { + mount::umount(target) + .chain_err(|| format!("failed to unmount target '{:?}'", target)) +} + +pub fn fetch_metadata() -> Result { + let drive = ConfigDrive::new()?; + + Ok(Metadata::builder() + .add_publickeys(drive.fetch_publickeys()?) + .add_attribute_if_exists("CLOUDSTACK_AVAILABILITY_ZONE".into(), drive.fetch_value("availability_zone")?) + .add_attribute_if_exists("CLOUDSTACK_INSTANCE_ID".into(), drive.fetch_value("instance_id")?) + .add_attribute_if_exists("CLOUDSTACK_SERVICE_OFFERING".into(), drive.fetch_value("service_offering")?) + .add_attribute_if_exists("CLOUDSTACK_CLOUD_IDENTIFIER".into(), drive.fetch_value("cloud_identifier")?) + .add_attribute_if_exists("CLOUDSTACK_LOCAL_HOSTNAME".into(), drive.fetch_value("local_hostname")?) + .add_attribute_if_exists("CLOUDSTACK_VM_ID".into(), drive.fetch_value("vm_id")?) + .build()) +} diff --git a/src/providers/cloudstack/mod.rs b/src/providers/cloudstack/mod.rs new file mode 100644 index 00000000..4627493f --- /dev/null +++ b/src/providers/cloudstack/mod.rs @@ -0,0 +1,4 @@ +//! Metadata fetchers for the cloudstack provider + +pub mod network; +pub mod configdrive; diff --git a/src/providers/cloudstack/network.rs b/src/providers/cloudstack/network.rs new file mode 100644 index 00000000..905b5e8c --- /dev/null +++ b/src/providers/cloudstack/network.rs @@ -0,0 +1,50 @@ +//! network metadata fetcher for the cloudstack provider + +use errors::*; +use metadata::Metadata; +use std::net::IpAddr; +use std::time::Duration; +use retry; +use openssh_keys::PublicKey; +use util; + +const SERVER_ADDRESS: &'static str = "SERVER_ADDRESS"; + +pub fn fetch_metadata() -> Result { + // first, find the address of the metadata service + let server = get_dhcp_server_address()?; + let client = retry::Client::new()? + .initial_backoff(Duration::from_secs(1)) + .max_backoff(Duration::from_secs(5)) + .max_attempts(10); + let endpoint_for = |key| format!("http://{}/latest/meta-data/{}", server, key); + + // then get the ssh keys and parse them + let keys: Option = client.get(retry::Raw, endpoint_for("public-keys")).send() + .chain_err(|| "failed to get public keys")?; + + let mut builder = Metadata::builder(); + if let Some(k) = keys { + let keys = PublicKey::read_keys(k.as_bytes())?; + builder = builder.add_publickeys(keys); + } + + Ok(builder + .add_attribute_if_exists("CLOUDSTACK_INSTANCE_ID".into(), client.get(retry::Raw, endpoint_for("instance-id")).send()?) + .add_attribute_if_exists("CLOUDSTACK_LOCAL_HOSTNAME".into(), client.get(retry::Raw, endpoint_for("local-hostname")).send()?) + .add_attribute_if_exists("CLOUDSTACK_PUBLIC_HOSTNAME".into(), client.get(retry::Raw, endpoint_for("public-hostname")).send()?) + .add_attribute_if_exists("CLOUDSTACK_AVAILABILITY_ZONE".into(), client.get(retry::Raw, endpoint_for("availability-zone")).send()?) + .add_attribute_if_exists("CLOUDSTACK_IPV4_PUBLIC".into(), client.get(retry::Raw, endpoint_for("public-ipv4")).send()?) + .add_attribute_if_exists("CLOUDSTACK_IPV4_LOCAL".into(), client.get(retry::Raw, endpoint_for("local-ipv4")).send()?) + .add_attribute_if_exists("CLOUDSTACK_SERVICE_OFFERING".into(), client.get(retry::Raw, endpoint_for("service-offering")).send()?) + .add_attribute_if_exists("CLOUDSTACK_CLOUD_IDENTIFIER".into(), client.get(retry::Raw, endpoint_for("cloud-identifier")).send()?) + .add_attribute_if_exists("CLOUDSTACK_VM_ID".into(), client.get(retry::Raw, endpoint_for("vm-id")).send()?) + .build()) +} + +fn get_dhcp_server_address() -> Result { + let server = util::dns_lease_key_lookup(SERVER_ADDRESS)?; + let ip = server.parse() + .chain_err(|| format!("failed to parse server ip address: {}", server))?; + Ok(IpAddr::V4(ip)) +} diff --git a/src/providers/digitalocean/mod.rs b/src/providers/digitalocean/mod.rs new file mode 100644 index 00000000..0b66e4cb --- /dev/null +++ b/src/providers/digitalocean/mod.rs @@ -0,0 +1,249 @@ +// Copyright 2017 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. + +//! digital ocean metadata fetcher + +use retry; +use metadata; +use network; +use ipnetwork; +use ipnetwork::{IpNetwork,Ipv4Network,Ipv6Network}; +use pnet::util::MacAddr; + +use std::str::FromStr; +use std::net::{IpAddr,Ipv4Addr,Ipv6Addr}; +use std::collections::HashMap; + +use errors::*; + +#[derive(Clone,Deserialize)] +struct Address { + ip_address: IpAddr, + netmask: Option, + cidr: Option, + gateway: IpAddr, +} + +#[derive(Clone,Deserialize)] +struct Interface { + ipv4: Option
, + ipv6: Option
, + anchor_ipv4: Option
, + mac: String, + #[serde(rename = "type")] + type_name: String, +} + +#[derive(Clone,Deserialize)] +struct Interfaces { + public: Option>, + private: Option>, +} + +#[derive(Clone,Deserialize)] +struct DNS { + nameservers: Vec +} + +#[derive(Clone,Deserialize)] +struct Metadata { + hostname: String, + interfaces: Interfaces, + public_keys: Vec, + region: String, + dns: DNS, +} + +pub fn fetch_metadata() -> Result { + let client = retry::Client::new()?; + let data: Metadata = client.get(retry::Json, "http://169.254.169.254/metadata/v1.json".to_owned()).send()? + .ok_or("not found")?; + + let attrs = parse_attrs(&data)?; + let interfaces = parse_network(&data)?; + + let mut m = metadata::Metadata::builder() + .add_ssh_keys(data.public_keys)? + .set_hostname(data.hostname); + + for (key,val) in attrs { + m = m.add_attribute(key,val); + } + for iface in interfaces { + m = m.add_network_interface(iface); + } + Ok(m.build()) +} + +fn parse_attrs(data: &Metadata) -> Result> { + let mut attrs = Vec::new(); + + attrs.push(("DIGITALOCEAN_HOSTNAME".to_owned(), data.hostname.clone())); + attrs.push(("DIGITALOCEAN_REGION".to_owned(), data.region.clone())); + + let mut public_counter = 0; + let mut private_counter = 0; + if let Some(ifaces) = data.interfaces.public.clone() { + for a in ifaces { + if a.ipv4.is_some() { + attrs.push(( + format!("DIGITALOCEAN_IPV4_PUBLIC_{}", public_counter), + format!("{}", a.ipv4.unwrap().ip_address) + )); + } + if a.ipv6.is_some() { + attrs.push(( + format!("DIGITALOCEAN_IPV6_PUBLIC_{}", public_counter), + format!("{}", a.ipv6.unwrap().ip_address) + )); + } + if a.anchor_ipv4.is_some() { + attrs.push(( + format!("DIGITALOCEAN_IPV4_ANCHOR_{}", public_counter), + format!("{}", a.anchor_ipv4.unwrap().ip_address) + )); + } + public_counter += 1; + } + } + if let Some(ifaces) = data.interfaces.private.clone() { + for a in ifaces { + if a.ipv4.is_some() { + attrs.push(( + format!("DIGITALOCEAN_IPV4_PRIVATE_{}", public_counter), + format!("{}", a.ipv4.unwrap().ip_address) + )); + } + if a.ipv6.is_some() { + attrs.push(( + format!("DIGITALOCEAN_IPV6_PRIVATE_{}", private_counter), + format!("{}", a.ipv6.unwrap().ip_address) + )); + } + private_counter += 1; + } + } + Ok(attrs) +} + +fn parse_network(data: &Metadata) -> Result> { + let mut interfaces = Vec::new(); + if let Some(ifaces) = data.interfaces.public.clone() { + interfaces.extend(parse_interfaces(data, ifaces)?); + } + if let Some(ifaces) = data.interfaces.private.clone() { + interfaces.extend(parse_interfaces(data, ifaces)?); + } + Ok(interfaces) +} + +fn parse_interfaces(data: &Metadata, interfaces: Vec) -> Result> { + let mut iface_config_map: HashMap = HashMap::new(); + for iface in interfaces { + let mac = MacAddr::from_str(&iface.mac) + .map_err(|e| Error::from(format!("{:?}", e))) + .chain_err(|| "failed to parse mac address")?; + let (mut addrs, mut routes) = parse_interface(&iface)?; + + if let Some(existing_iface) = iface_config_map.get_mut(&mac) { + addrs.extend(existing_iface.ip_addresses.clone()); + routes.extend(existing_iface.routes.clone()); + } + iface_config_map.insert(mac, network::Interface{ + mac_address: Some(mac), + nameservers: data.dns.nameservers.clone(), + ip_addresses: addrs, + routes: routes, + bond: None, + name: None, + priority: None, + }); + } + let mut iface_configs = Vec::new(); + for i in iface_config_map.values() { + iface_configs.push(i.clone()); + } + Ok(iface_configs) +} + +fn parse_interface(interface: &Interface) -> Result<(Vec,Vec)> { + let mut addrs = Vec::new(); + let mut routes = Vec::new(); + + if interface.ipv4.is_some() { + let netmask = interface.clone().ipv4.unwrap().netmask + .ok_or("missing netmask for ipv4 address")?; + let prefix = ipnetwork::ip_mask_to_prefix(netmask) + .chain_err(|| "invalid network mask")?; + let a = match interface.clone().ipv4.unwrap().ip_address { + IpAddr::V4(a) => Some(a), + IpAddr::V6(_) => None, + }.ok_or("ipv6 address in ipv4 field")?; + let net = IpNetwork::V4(Ipv4Network::new(a, prefix) + .chain_err(|| "invalid ip address or prefix")?); + addrs.push(net); + routes.push(network::NetworkRoute{ + destination: net, + gateway: interface.clone().ipv4.unwrap().gateway, + }); + + if interface.type_name == "public" { + routes.push(network::NetworkRoute{ + destination: IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(0,0,0,0),0) + .chain_err(|| "invalid ip address or prefix")?), + gateway: interface.clone().ipv4.unwrap().gateway, + }); + } + } + if interface.ipv6.is_some() { + let cidr = interface.clone().ipv6.unwrap().cidr + .ok_or("missing cidr for ipv6 address")?; + let a = match interface.clone().ipv6.unwrap().ip_address { + IpAddr::V4(_) => None, + IpAddr::V6(a) => Some(a), + }.ok_or("ipv4 address in ipv6 field")?; + let net = IpNetwork::V6(Ipv6Network::new(a, cidr) + .chain_err(|| "invalid ip address or prefix")?); + addrs.push(net); + routes.push(network::NetworkRoute{ + destination: net, + gateway: interface.clone().ipv6.unwrap().gateway, + }); + if interface.type_name == "public" { + routes.push(network::NetworkRoute{ + destination: IpNetwork::V6(Ipv6Network::new(Ipv6Addr::new(0,0,0,0,0,0,0,0),0) + .chain_err(|| "invalid ip address or prefix")?), + gateway: interface.clone().ipv6.unwrap().gateway, + }); + } + } + if interface.anchor_ipv4.is_some() { + let netmask = interface.clone().anchor_ipv4.unwrap().netmask + .ok_or("missing netmask for anchor ipv4 address")?; + let prefix = ipnetwork::ip_mask_to_prefix(netmask) + .chain_err(|| "invalid network mask")?; + let a = match interface.clone().anchor_ipv4.unwrap().ip_address { + IpAddr::V4(a) => Some(a), + IpAddr::V6(_) => None, + }.ok_or("ipv6 address in ipv4 field")?; + let net = IpNetwork::V4(Ipv4Network::new(a, prefix) + .chain_err(|| "invalid ip address or prefix")?); + addrs.push(net); + routes.push(network::NetworkRoute{ + destination: net, + gateway: interface.clone().anchor_ipv4.unwrap().gateway, + }); + } + Ok((addrs,routes)) +} diff --git a/src/providers/ec2/mock_tests.rs b/src/providers/ec2/mock_tests.rs new file mode 100644 index 00000000..cea3d72d --- /dev/null +++ b/src/providers/ec2/mock_tests.rs @@ -0,0 +1,34 @@ +use mockito; +use errors::*; +use providers::ec2; + +pub(crate) const URL: &'static str = ::mockito::SERVER_URL; + +#[test] +fn test_ec2_basic() { + let ep = "/meta-data/public-keys"; + let client = ::retry::Client::new() + .chain_err(|| "failed to create http client") + .unwrap() + .max_attempts(1) + .return_on_404(true); + + ec2::fetch_ssh_keys(&client).unwrap_err(); + + let _m = mockito::mock("GET", ep) + .with_status(503) + .create(); + ec2::fetch_ssh_keys(&client).unwrap_err(); + + let _m = mockito::mock("GET", ep) + .with_status(200) + .create(); + let v = ec2::fetch_ssh_keys(&client).unwrap(); + assert_eq!(v.len(), 0); + + let _m = mockito::mock("GET", ep) + .with_status(404) + .create(); + let v = ec2::fetch_ssh_keys(&client).unwrap(); + assert_eq!(v.len(), 0); +} diff --git a/src/providers/ec2/mod.rs b/src/providers/ec2/mod.rs new file mode 100644 index 00000000..3748bcde --- /dev/null +++ b/src/providers/ec2/mod.rs @@ -0,0 +1,81 @@ +// Copyright 2017 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. + +//! aws ec2 metadata fetcher +//! +use errors::*; +use metadata::Metadata; +use retry; + +#[cfg(test)] +mod mock_tests; +#[cfg(test)] +use self::mock_tests::URL; + +#[cfg(not(test))] +const URL: &'static str = "http://169.254.169.254/2009-04-04"; + +fn url_for_key(key: &str) -> String { + format!("{}/{}", URL, key) +} + +#[allow(non_snake_case)] +#[derive(Debug, Deserialize)] +struct InstanceIdDoc { + region: String, +} + +pub fn fetch_metadata() -> Result { + let client = retry::Client::new() + .chain_err(|| "ec2: failed to create http client")? + .return_on_404(true); + + let instance_id: Option = client.get(retry::Raw, url_for_key("meta-data/instance-id")).send()?; + let public: Option = client.get(retry::Raw, url_for_key("meta-data/public-ipv4")).send()?; + let local: Option = client.get(retry::Raw, url_for_key("meta-data/local-ipv4")).send()?; + let hostname: Option = client.get(retry::Raw, url_for_key("meta-data/hostname")).send()?; + let availability_zone: Option = client.get(retry::Raw, url_for_key("meta-data/placement/availability-zone")).send()?; + let region: Option = client.get(retry::Json, url_for_key("dynamic/instance-identity/document")).send()? + .map(|instance_id_doc: InstanceIdDoc| instance_id_doc.region); + + let ssh_keys: Vec = fetch_ssh_keys(&client)?; + + Ok(Metadata::builder() + .add_attribute_if_exists("EC2_REGION".to_owned(), region) + .add_attribute_if_exists("EC2_INSTANCE_ID".to_owned(), instance_id) + .add_attribute_if_exists("EC2_IPV4_PUBLIC".to_owned(), public) + .add_attribute_if_exists("EC2_IPV4_LOCAL".to_owned(), local) + .add_attribute_if_exists("EC2_HOSTNAME".to_owned(), hostname.clone()) + .add_attribute_if_exists("EC2_AVAILABILITY_ZONE".to_owned(), availability_zone) + .set_hostname_if_exists(hostname) + .add_ssh_keys(ssh_keys)? + .build()) +} + +fn fetch_ssh_keys(client: &retry::Client) -> Result> { + let keydata: Option = client.get(retry::Raw, url_for_key("meta-data/public-keys")).send()?; + let mut keys = Vec::new(); + if let Some(keys_list) = keydata { + for l in keys_list.lines() { + let tokens: Vec<&str> = l.split('=').collect(); + if tokens.len() != 2 { + return Err("error parsing keyID".into()); + } + let key: String = client.get(retry::Raw, url_for_key(&format!("meta-data/public-keys/{}/openssh-key", tokens[0]))).send()? + .ok_or("missing ssh key")?; + keys.push(key) + } + } + Ok(keys) +} diff --git a/src/providers/gce/mod.rs b/src/providers/gce/mod.rs new file mode 100644 index 00000000..6134449e --- /dev/null +++ b/src/providers/gce/mod.rs @@ -0,0 +1,95 @@ +// Copyright 2017 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. + +//! google compute engine metadata fetcher + +use metadata::Metadata; + +use errors::*; + +use retry; + +header! {(MetadataFlavor, "Metadata-Flavor") => [String]} +const GOOGLE: &str = "Google"; + +fn url_for_key(key: &str) -> String { + format!("http://metadata.google.internal/computeMetadata/v1/{}", key) +} + +// Google's metadata service returns a 200 success even if there is no resource. If an empty body +// was returned, it means there was no result +fn empty_to_none(s: Option) -> Option { + match s { + Some(s) => if &s == "" { None } else { Some(s) }, + x => x, + } +} + +pub fn fetch_metadata() -> Result { + let client = retry::Client::new()? + .header(MetadataFlavor(GOOGLE.to_owned())) + .return_on_404(true); + let public: Option = client.get(retry::Raw, url_for_key("instance/network-interfaces/0/access-configs/0/external-ip")).send()?; + let local: Option = client.get(retry::Raw, url_for_key("instance/network-interfaces/0/ip")).send()?; + let hostname: Option = client.get(retry::Raw, url_for_key("instance/hostname")).send()?; + + let ssh_keys = fetch_all_ssh_keys(&client)?; + + Ok(Metadata::builder() + .add_attribute_if_exists("GCE_IP_LOCAL_0".to_owned(), empty_to_none(local)) + .add_attribute_if_exists("GCE_IP_EXTERNAL_0".to_owned(), empty_to_none(public)) + .add_attribute_if_exists("GCE_HOSTNAME".to_owned(), empty_to_none(hostname.clone())) + .set_hostname_if_exists(hostname) + .add_ssh_keys(ssh_keys)? + .build()) +} + +fn fetch_all_ssh_keys(client: &retry::Client) -> Result> { + let keys = fetch_ssh_keys(client, "instance/attributes/sshKeys")?; + if !keys.is_empty() { + return Ok(keys); + } + let mut keys = fetch_ssh_keys(client, "instance/attributes/ssh-keys")?; + + let block_project_keys: Option = client.clone().get(retry::Raw, url_for_key("instance/attributes/block-project-ssh-keys")).send()?; + + if block_project_keys == Some("true".to_owned()) { + return Ok(keys); + } + + keys.append(&mut fetch_ssh_keys(client, "project/attributes/sshKeys")?); + + Ok(keys) +} + +fn fetch_ssh_keys(client: &retry::Client, key: &str) -> Result> { + let key_data: Option = client.get(retry::Raw, url_for_key(key)).send()?; + if let Some(key_data) = key_data { + let mut keys = Vec::new(); + for l in key_data.lines() { + if l.is_empty() { + continue + } + let mut l = l.to_owned(); + let index = l.find(':') + .ok_or("character ':' not found in line in key data")?; + keys.push(l.split_off(index+1)); + } + Ok(keys) + } else { + // The user must have not provided any keys + Ok(Vec::new()) + } + +} diff --git a/src/providers/mod.rs b/src/providers/mod.rs new file mode 100644 index 00000000..fcc0bad6 --- /dev/null +++ b/src/providers/mod.rs @@ -0,0 +1,34 @@ +// Copyright 2017 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. + +//! Providers +//! +//! These are the providers which coreos-metadata knows how to retrieve metadata +//! from. Internally, they handle the ins and outs of each providers metadata +//! services, and externally, they provide a function to fetch that metadata in +//! a regular format. +//! +//! To add a provider, put a `pub mod provider;` line in this file, export a +//! function to fetch the metadata, and then add a match line in the top-level +//! `fetch_metadata()` function in metadata.rs. + +pub mod azure; +pub mod digitalocean; +pub mod cloudstack; +pub mod ec2; +pub mod gce; +pub mod openstack; +pub mod oracle; +pub mod packet; +pub mod vagrant_virtualbox; diff --git a/internal/vendor/github.com/coreos/update-ssh-keys/src/main_test.go b/src/providers/openstack/mod.rs similarity index 84% rename from internal/vendor/github.com/coreos/update-ssh-keys/src/main_test.go rename to src/providers/openstack/mod.rs index 572da155..3af9e984 100644 --- a/internal/vendor/github.com/coreos/update-ssh-keys/src/main_test.go +++ b/src/providers/openstack/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2015 CoreOS, Inc. +// Copyright 2017 CoreOS, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,12 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -package main +//! openstack metadata fetcher -import ( - "testing" -) - -func Test(t *testing.T) { - // TODO -} +pub mod network; diff --git a/src/providers/openstack/network.rs b/src/providers/openstack/network.rs new file mode 100644 index 00000000..25ff6b1e --- /dev/null +++ b/src/providers/openstack/network.rs @@ -0,0 +1,44 @@ +//! openstack metadata fetcher + +use errors::*; +use metadata::Metadata; +use retry; + +const URL: &'static str = "http://169.254.169.254/latest/meta-data"; + +fn url_for_key(key: &str) -> String { + format!("{}/{}", URL, key) +} + +pub fn fetch_metadata() -> Result { + let client = retry::Client::new() + .chain_err(|| "openstack: failed to create http client")?; + + let hostname: Option = client.get(retry::Raw, url_for_key("hostname")).send()?; + + Ok(Metadata::builder() + .add_attribute_if_exists("OPENSTACK_INSTANCE_ID".to_owned(), client.get(retry::Raw, url_for_key("instance-id")).send()?) + .add_attribute_if_exists("OPENSTACK_IPV4_LOCAL".to_owned(), client.get(retry::Raw, url_for_key("local-ipv4")).send()?) + .add_attribute_if_exists("OPENSTACK_IPV4_PUBLIC".to_owned(), client.get(retry::Raw, url_for_key("public-ipv4")).send()?) + .add_attribute_if_exists("OPENSTACK_HOSTNAME".to_owned(), hostname.clone()) + .set_hostname_if_exists(hostname) + .add_ssh_keys(fetch_keys(&client)?)? + .build()) +} + +fn fetch_keys(client: &retry::Client) -> Result> { + let keys_list: Option = client.get(retry::Raw, url_for_key("public-keys")).send()?; + let mut keys = Vec::new(); + if let Some(keys_list) = keys_list { + for l in keys_list.lines() { + let tokens: Vec<&str> = l.split('=').collect(); + if tokens.len() != 2 { + return Err("error parsing keyID".into()); + } + let key: String = client.get(retry::Raw, url_for_key(&format!("public-keys/{}/openssh-key", tokens[0]))).send()? + .ok_or("missing ssh key")?; + keys.push(key); + } + } + Ok(keys) +} diff --git a/src/providers/oracle/mod.rs b/src/providers/oracle/mod.rs new file mode 100644 index 00000000..7770858a --- /dev/null +++ b/src/providers/oracle/mod.rs @@ -0,0 +1,47 @@ +//! oracle metadata fetcher + +use retry; +use metadata; +use errors::*; + +use openssh_keys::PublicKey; + +#[derive(Debug, Deserialize, Clone)] +struct InstanceData { + #[serde(rename = "availabilityDomain")] + availability_domain: String, + #[serde(rename = "compartmentId")] + compartment_id: String, + #[serde(rename = "displayName")] + display_name: String, + id: String, + image: String, + region: String, + shape: String, + #[serde(rename = "timeCreated")] + time_created: u64, + metadata: Metadata, +} + +#[derive(Debug, Deserialize, Clone)] +struct Metadata { + ssh_authorized_keys: String, +} + +pub fn fetch_metadata() -> Result { + let client = retry::Client::new() + .chain_err(|| "oracle: failed to create http client")?; + + let data: InstanceData = client.get(retry::Json, "http://169.254.169.254/opc/v1/instance/".into()).send() + .chain_err(|| "oracle: failed to get instance metadata from metadata service")? + .ok_or_else(|| "oracle: failed to get instance metadata from metadata service: no response")?; + + let ssh_keys = PublicKey::read_keys(data.metadata.ssh_authorized_keys.as_bytes())?; + + Ok(metadata::Metadata::builder() + .add_attribute("ORACLE_OCI_DISPLAY_NAME".into(), data.display_name) + .add_attribute("ORACLE_OCI_INSTANCE_ID".into(), data.id) + .add_attribute("ORACLE_OCI_REGION".into(), data.region) + .add_publickeys(ssh_keys) + .build()) +} diff --git a/src/providers/packet/mod.rs b/src/providers/packet/mod.rs new file mode 100644 index 00000000..078fdc70 --- /dev/null +++ b/src/providers/packet/mod.rs @@ -0,0 +1,230 @@ +// Copyright 2017 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. + +//! packet metadata fetcher + +use metadata::Metadata; + +use errors::*; + +use retry; +use std::fs::File; +use std::str::FromStr; + +use util; + +use network; +use network::{Interface,Device,Section,NetworkRoute}; +use pnet::util::MacAddr; +use std::net::{IpAddr,Ipv4Addr,Ipv6Addr}; +use ipnetwork; +use ipnetwork::{IpNetwork,Ipv4Network,Ipv6Network}; + +#[derive(Clone,Deserialize)] +struct PacketData { + id: String, + hostname: String, + iqn: String, + plan: String, + facility: String, + tags: Vec, + ssh_keys: Vec, + network: PacketNetworkInfo, + + error: Option, + phone_home_url: String, +} + +#[derive(Clone,Deserialize)] +struct PacketNetworkInfo { + interfaces: Vec, + addresses: Vec, + bonding: PacketBondingMode, +} + +#[derive(Clone,Deserialize)] +struct PacketBondingMode { + mode: u32, +} + +#[derive(Clone,Deserialize)] +struct PacketInterfaceInfo { + name: String, + mac: String, +} + +#[derive(Clone,Deserialize)] +struct PacketAddressInfo { + id: String, + address_family: i32, + public: bool, + management: bool, + address: IpAddr, + netmask: IpAddr, + gateway: IpAddr, +} + +pub fn fetch_metadata() -> Result { + let client = retry::Client::new()?; + let data: PacketData = client.get(retry::Json, "http://metadata.packet.net/metadata".to_owned()).send()? + .ok_or("not found")?; + + let (interfaces,network_devices) = parse_network(&data.network)?; + + let attrs = get_attrs(&data)?; + + let mut m = Metadata::builder() + .add_ssh_keys(data.ssh_keys)? + .set_hostname(data.hostname); + + for (key,val) in attrs { + m = m.add_attribute(key,val); + } + for iface in interfaces { + m = m.add_network_interface(iface); + } + for netdev in network_devices { + m = m.add_network_device(netdev); + } + Ok(m.build()) +} + +fn get_dns_servers() -> Result> { + let f = File::open("/run/systemd/netif/state")?; + let ip_strings = util::key_lookup_reader('=', "DNS", f)? + .ok_or("DNS not found in netif state file")?; + let mut addrs = Vec::new(); + for ip_string in ip_strings.split(' ') { + addrs.push(IpAddr::from_str(ip_string) + .chain_err(|| "failed to parse IP address")?); + } + if addrs.is_empty() { + return Err("no DNS servers in /run/systemd/netif/state".into()); + } + Ok(addrs) +} + +fn parse_network(netinfo: &PacketNetworkInfo) -> Result<(Vec,Vec)> { + let mut interfaces = Vec::new(); + for i in netinfo.interfaces.clone() { + let mac = MacAddr::from_str(&i.mac) + .map_err(|err| Error::from(format!("{:?}", err))) + .chain_err(|| format!("failed to parse mac address: '{}'", i.mac))?; + interfaces.push(Interface { + mac_address: Some(mac), + bond: Some("bond0".to_owned()), + name: None, + priority: None, + nameservers: Vec::new(), + ip_addresses: Vec::new(), + routes: Vec::new(), + }); + } + let mut iface = Interface{ + name: Some("bond0".to_owned()), + priority: Some(5), + nameservers: get_dns_servers()?, + mac_address: None, + bond: None, + ip_addresses: Vec::new(), + routes: Vec::new(), + }; + for a in netinfo.addresses.clone() { + let prefix = ipnetwork::ip_mask_to_prefix(a.netmask) + .chain_err(|| "invalid network mask")?; + iface.ip_addresses.push( + match a.address { + IpAddr::V4(addrv4) => IpNetwork::V4(Ipv4Network::new(addrv4, prefix) + .chain_err(|| "invalid IP address or prefix")?), + IpAddr::V6(addrv6) => IpNetwork::V6(Ipv6Network::new(addrv6, prefix) + .chain_err(|| "invalid IP address or prefix")?), + } + ); + let dest = match (a.public,a.address) { + (false,IpAddr::V4(_)) => + IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(10,0,0,0),8).unwrap()), + (true,IpAddr::V4(_)) => + IpNetwork::V4(Ipv4Network::new(Ipv4Addr::new(0,0,0,0),0).unwrap()), + (_,IpAddr::V6(_)) => + IpNetwork::V6(Ipv6Network::new(Ipv6Addr::new(0,0,0,0,0,0,0,0),0).unwrap()), + }; + iface.routes.push( + NetworkRoute { + destination: dest, + gateway: a.gateway, + } + + ); + } + interfaces.push(iface); + + let mut attrs = vec![ + ("TransmitHashPolicy".to_owned(), "layer3+4".to_owned()), + ("MIIMonitorSec".to_owned(), ".1".to_owned()), + ("UpDelaySec".to_owned(), ".2".to_owned()), + ("DownDelaySec".to_owned(), ".2".to_owned()), + ("Mode".to_owned(), network::bonding_mode_to_string(&netinfo.bonding.mode)?), + ]; + if netinfo.bonding.mode == network::BONDING_MODE_LACP { + attrs.push(("LACPTransmitRate".to_owned(), "fast".to_owned())); + } + let network_devices = vec![ + Device{ + name: "bond0".to_owned(), + kind: "bond".to_owned(), + mac_address: interfaces[0].mac_address + .ok_or("first interface doesn't have a mac address, should be impossible")?, + priority: Some(5), + sections: vec![ + Section{ + name: "Bond".to_owned(), + attributes: attrs, + } + ], + }, + ]; + + Ok((interfaces,network_devices)) +} + +fn get_attrs(data: &PacketData) -> Result> { + let mut attrs = Vec::new(); + let mut v4_public_counter = 0; + let mut v4_private_counter = 0; + let mut v6_public_counter = 0; + let mut v6_private_counter = 0; + for a in data.network.addresses.clone() { + match (a.address,a.public) { + (IpAddr::V4(a),true) => { + attrs.push((format!("PACKET_IPV4_PUBLIC_{}", v4_public_counter), format!("{}", a))); + v4_public_counter += 1; + } + (IpAddr::V4(a),false) => { + attrs.push((format!("PACKET_IPV4_PRIVATE_{}", v4_private_counter), format!("{}", a))); + v4_private_counter += 1; + } + (IpAddr::V6(a),true) => { + attrs.push((format!("PACKET_IPV6_PUBLIC_{}", v6_public_counter), format!("{}", a))); + v6_public_counter += 1; + } + (IpAddr::V6(a),false) => { + attrs.push((format!("PACKET_IPV6_PRIVATE_{}", v6_private_counter), format!("{}", a))); + v6_private_counter += 1; + } + } + } + attrs.push(("PACKET_HOSTNAME".to_owned(), data.hostname.clone())); + attrs.push(("PACKET_PHONE_HOME_URL".to_owned(), data.phone_home_url.clone())); + Ok(attrs) +} diff --git a/src/providers/vagrant_virtualbox/mod.rs b/src/providers/vagrant_virtualbox/mod.rs new file mode 100644 index 00000000..48e6e5e5 --- /dev/null +++ b/src/providers/vagrant_virtualbox/mod.rs @@ -0,0 +1,64 @@ +// Copyright 2017 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. + +//! vagrant/virtualbox metadata fetcher + +use metadata::Metadata; + +use errors::*; + +use std::net::IpAddr; +use std::time::Duration; +use std::thread; + +use hostname; +use pnet; + +pub fn fetch_metadata() -> Result { + let h = hostname::get_hostname().ok_or("unable to get hostname")?; + let ip = get_ip()?; + + Ok(Metadata::builder() + .add_attribute("VAGRANT_VIRTUALBOX_PRIVATE_IPV4".to_owned(), ip) + .add_attribute("VAGRANT_VIRTUALBOX_HOSTNAME".to_owned(), h.clone()) + .set_hostname(h) + .build()) +} + +fn get_ip() -> Result { + let max_attempts = 30; + for _ in 0..max_attempts { + let iface = find_eth1(); + if let Some(iface) = iface { + for a in iface.ips { + if let IpAddr::V4(a) = a.ip() { + return Ok(format!("{}", a)); + } + } + } + info!("eth1 not found or is lacking an ipv4 address; waiting 2 seconds"); + thread::sleep(Duration::from_secs(2)); + } + Err("eth1 was not found!".into()) +} + +fn find_eth1() -> Option { + let mut ifaces = pnet::datalink::interfaces(); + ifaces.retain(|i| i.name == "eth1"); + if !ifaces.is_empty() { + Some(ifaces[0].clone()) + } else { + None + } +} diff --git a/src/retry/client.rs b/src/retry/client.rs new file mode 100644 index 00000000..ba8fa46f --- /dev/null +++ b/src/retry/client.rs @@ -0,0 +1,224 @@ +// Copyright 2017 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. + +//! retry +//! +//! this is an abstraction over the regular http get request. it allows you to +//! have a request retry until it succeeds, with a configurable number of +//! of attempts and a backoff strategy. It also takes care of automatically +//! deserializing responses and handles headers in a sane way. + +use std::io::Read; +use std::time::Duration; + +use reqwest; +use reqwest::header; +use reqwest::header::ContentType; +use reqwest::{Method,Request}; + +use serde; +use serde_xml_rs; +use serde_json; + +use retry::Retry; +use errors::*; + +use retry::raw_deserializer; + +pub trait Deserializer { + fn deserialize(&self, R) -> Result + where T: for<'de> serde::Deserialize<'de>, R: Read; + fn content_type(&self) -> ContentType; +} + +#[derive(Debug, Clone, Copy)] +pub struct Xml; + +impl Deserializer for Xml { + fn deserialize(&self, r: R) -> Result + where T: for<'de> serde::Deserialize<'de>, R: Read + { + serde_xml_rs::deserialize(r) + .chain_err(|| "failed xml deserialization") + } + fn content_type(&self) -> ContentType { + ContentType("text/xml; charset=utf-8".parse().unwrap()) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Json; + +impl Deserializer for Json { + fn deserialize(&self, r: R) -> Result + where T: serde::de::DeserializeOwned, R: Read + { + serde_json::from_reader(r) + .chain_err(|| "failed json deserialization") + } + fn content_type(&self) -> ContentType { + ContentType("text/json; charset=utf-8".parse().unwrap()) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Raw; + +impl Deserializer for Raw { + fn deserialize(&self, r: R) -> Result + where T: for<'de> serde::Deserialize<'de>, R: Read + { + raw_deserializer::from_reader(r) + .chain_err(|| "failed raw deserialization") + } + fn content_type(&self) -> ContentType { + ContentType("text/plain; charset=utf-8".parse().unwrap()) + } +} + +#[derive(Debug, Clone)] +pub struct Client { + client: reqwest::Client, + headers: header::Headers, + retry: Retry, + return_on_404: bool, +} + +impl Client { + pub fn new() -> Result { + let client = reqwest::Client::new() + .chain_err(|| "failed to initialize client")?; + Ok(Client{ + client, + headers: header::Headers::new(), + retry: Retry::new(), + return_on_404: false, + }) + } + + pub fn header(mut self, h: H) -> Self + where H: header::Header + { + self.headers.set(h); + self + } + + pub fn initial_backoff(mut self, initial_backoff: Duration) -> Self { + self.retry = self.retry.initial_backoff(initial_backoff); + self + } + + pub fn max_backoff(mut self, max_backoff: Duration) -> Self { + self.retry = self.retry.max_backoff(max_backoff); + self + } + + /// max_attempts will panic if the argument is greater than 500 + pub fn max_attempts(mut self, max_attempts: u32) -> Self { + self.retry = self.retry.max_attempts(max_attempts); + self + } + + pub fn return_on_404(mut self, return_on_404: bool) -> Self { + self.return_on_404 = return_on_404; + self + } + + pub fn get(&self, d: D, url: String) -> RequestBuilder + where D: Deserializer + { + RequestBuilder{ + url, + d, + client: self.client.clone(), + headers: self.headers.clone(), + retry: self.retry.clone(), + return_on_404: self.return_on_404, + } + } +} + +pub struct RequestBuilder + where D: Deserializer +{ + url: String, + d: D, + client: reqwest::Client, + headers: header::Headers, + retry: Retry, + return_on_404: bool, +} + +impl RequestBuilder + where D: Deserializer +{ + + pub fn header(mut self, h: H) -> Self + where H: header::Header + { + self.headers.set(h); + self + } + + pub fn send(self) -> Result> + where T: for<'de> serde::Deserialize<'de> + { + let url = reqwest::Url::parse(self.url.as_str()) + .chain_err(|| "failed to parse uri")?; + let mut req = Request::new(Method::Get, url); + req.headers_mut().extend(self.headers.iter()); + req.headers_mut().set(self.d.content_type()); + + self.retry.clone().retry(|attempt| { + info!("Fetching {}: Attempt #{}", req.url(), attempt + 1); + self.dispatch_request(&req) + }) + } + + fn dispatch_request(&self, req: &Request) -> Result> + where T: for<'de> serde::Deserialize<'de> + { + match self.client.execute(clone_request(req)) { + Ok(resp) => { + match (resp.status(), self.return_on_404) { + (reqwest::StatusCode::Ok,_) => { + info!("Fetch successful"); + self.d.deserialize(resp) + .map(Some) + .chain_err(|| "failed to deserialize data") + } + (reqwest::StatusCode::NotFound,true) => { + info!("Fetch failed with 404: resource not found"); + Ok(None) + } + (s,_) => { + info!("Failed to fetch: {}", s); + Err(format!("failed to fetch: {}", s).into()) + } + } + } + Err(e) => { + info!("Failed to fetch: {}", e); + Err(Error::with_chain(e, "failed to fetch")) + } + } + } +} + +/// Reqwests Request struct doesn't implement copy, so we have to do it here +fn clone_request(req: &Request) -> Request { + let mut newreq = Request::new(req.method().clone(), req.url().clone()); + newreq.headers_mut().extend(req.headers().iter()); + newreq +} diff --git a/src/retry/mod.rs b/src/retry/mod.rs new file mode 100644 index 00000000..17a7c69a --- /dev/null +++ b/src/retry/mod.rs @@ -0,0 +1,91 @@ +// Copyright 2017 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. + +//! retry is a generic function that retrys functions until they succeed. + +use errors::*; +use std::time::Duration; +use std::thread; + +pub mod raw_deserializer; +mod client; +pub use self::client::*; + +#[derive(Clone, Debug)] +pub struct Retry { + initial_backoff: Duration, + max_backoff: Duration, + max_attempts: u32, +} + +impl ::std::default::Default for Retry { + fn default() -> Self { + Retry { + initial_backoff: Duration::new(1,0), + max_backoff: Duration::new(5,0), + max_attempts: 10, + } + } +} + +impl Retry { + pub fn new() -> Self { + Retry::default() + } + + pub fn initial_backoff(mut self, initial_backoff: Duration) -> Self { + self.initial_backoff = initial_backoff; + self + } + + pub fn max_backoff(mut self, max_backoff: Duration) -> Self { + self.max_backoff = max_backoff; + self + } + + pub fn max_attempts(mut self, max_attempts: u32) -> Self { + self.max_attempts = max_attempts; + self + } + + pub fn retry(self, try: F) -> Result + where F: Fn(u32) -> Result + { + let mut delay = self.initial_backoff; + let mut attempts = 0; + + loop { + let res = try(attempts); + + // if the result is ok, we don't need to try again + if res.is_ok() { + break res; + } + + // otherwise, perform the retry-backoff logic + attempts += 1; + if attempts == self.max_attempts { + break res.map_err(|e| Error::with_chain(e, "timed out")); + } + + thread::sleep(delay); + + delay = if self.max_backoff != Duration::new(0,0) && delay * 2 > self.max_backoff { + self.max_backoff + } else { + delay * 2 + }; + } + } +} diff --git a/src/retry/raw_deserializer.rs b/src/retry/raw_deserializer.rs new file mode 100644 index 00000000..c00764b4 --- /dev/null +++ b/src/retry/raw_deserializer.rs @@ -0,0 +1,233 @@ +// Copyright 2017 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. + +use std::io::Read; + +use std::result; + +use serde::de::{self,Visitor,DeserializeOwned}; + +use errors::*; + +pub struct RawDeserializer +{ + s: String, +} + +impl RawDeserializer +{ + pub fn from_reader(mut r: R) -> Result + where R: Read + { + let mut s = String::new(); + r.read_to_string(&mut s) + .chain_err(|| "error reading")?; + Ok(RawDeserializer { s }) + } +} + +pub fn from_reader(r: R) -> Result + where T: DeserializeOwned, R: Read +{ + let mut deserializer = RawDeserializer::from_reader(r)?; + Ok(T::deserialize(&mut deserializer) + .chain_err(|| "error deserializing")?) +} + +impl<'de, 'a> de::Deserializer<'de> for &'a mut RawDeserializer { + type Error = de::value::Error; + + fn deserialize_any(self, visitor: V) -> result::Result + where V: Visitor<'de> + { + self.deserialize_string(visitor) + } + + fn deserialize_bool(self, _: V) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_i8(self, _: V) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_i16(self, _: V) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_i32(self, _: V) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_i64(self, _: V) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_u8(self, _: V) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_u16(self, _: V) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_u32(self, _: V) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_u64(self, _: V) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_f32(self, _: V) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_f64(self, _: V) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_char(self, _: V) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_str(self, _: V) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_string(self, visitor: V) -> result::Result + where V: Visitor<'de> + { + visitor.visit_string(self.s.clone()) + } + fn deserialize_bytes(self, _: V) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_byte_buf( + self, + _: V + ) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_option(self, _: V) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_unit(self, _: V) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_unit_struct( + self, + _: &'static str, + _: V + ) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_newtype_struct( + self, + _: &'static str, + _: V + ) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_seq(self, _: V) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_tuple( + self, + _: usize, + _: V + ) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_tuple_struct( + self, + _: &'static str, + _: usize, + _: V + ) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_map(self, _: V) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_struct( + self, + _: &'static str, + _: &'static [&'static str], + _: V + ) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_enum( + self, + _: &'static str, + _: &'static [&'static str], + _: V + ) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_identifier( + self, + _: V + ) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } + fn deserialize_ignored_any( + self, + _: V + ) -> result::Result + where V: Visitor<'de> + { + unimplemented!() + } +} diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 00000000..0655130a --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1,107 @@ +// Copyright 2017 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. + +//! utility functions + +use pnet; +use std::io::{Read, BufRead, BufReader}; +use std::fs::File; +use std::path::Path; +use std::time::Duration; +use errors::*; +use retry; + +fn key_lookup_line(delim: char, key: &str, line: &str) -> Option { + match line.find(delim) { + Some(index) => { + let (k, val) = line.split_at(index+1); + if k != format!("{}{}", key, delim) { + None + } else { + Some(val.to_owned()) + } + } + None => None, + } +} + +pub fn key_lookup_reader(delim: char, key: &str, reader: R) -> Result> { + let contents = BufReader::new(reader); + + for l in contents.lines() { + let l = l?; + if let Some(v) = key_lookup_line(delim, key, &l) { + return Ok(Some(v)); + } + } + Ok(None) +} + +pub fn key_lookup(delim: char, key: &str, contents: &str) -> Option { + for l in contents.lines() { + if let Some(v) = key_lookup_line(delim, key, l) { + return Some(v) + } + } + None +} + +pub fn dns_lease_key_lookup(key: &str) -> Result { + let interfaces = pnet::datalink::interfaces(); + trace!("interfaces - {:?}", interfaces); + + retry::Retry::new() + .initial_backoff(Duration::from_millis(50)) + .max_backoff(Duration::from_millis(500)) + .max_attempts(60) + .retry(|_| { + for interface in interfaces.clone() { + trace!("looking at interface {:?}", interface); + let lease_path = format!("/run/systemd/netif/leases/{}", interface.index); + let lease_path = Path::new(&lease_path); + if lease_path.exists() { + debug!("found lease file - {:?}", lease_path); + let lease = File::open(&lease_path) + .chain_err(|| format!("failed to open lease file ({:?})", lease_path))?; + + if let Some(v) = key_lookup_reader('=', key, lease)? { + return Ok(v); + } + + debug!("failed to get value from existing lease file '{:?}'", lease_path); + } + } + Err("failed to retrieve fabric address".into()) + }) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn key_lookup_test() { + let tests = vec![ + ('=', "DNS", "foo=bar\nbaz=bax\nDNS=8.8.8.8\n", Some("8.8.8.8".to_owned())), + (':', "foo", "foo:bar", Some("bar".to_owned())), + (' ', "foo", "", None), + (':', "bar", "foo:bar\nbaz:bar", None), + (' ', "baz", "foo foo\nbaz bar", Some("bar".to_owned())), + (' ', "foo", "\n\n\n\n\n\n\n \n", None), + ]; + for (delim, key, contents, expected_val) in tests { + let val = key_lookup(delim, key, contents); + assert_eq!(val, expected_val); + } + } +} diff --git a/test b/test deleted file mode 100755 index 324f6dfd..00000000 --- a/test +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash - -set -eu - -source ./build - -SRC=$(find . -name '*.go' \ - -not -path "./internal/vendor/*") - -PKG=$(cd gopath/src/${REPO_PATH}; go list ./... | \ - grep --invert-match vendor) - -echo "Checking gofix..." -go tool fix -diff $SRC - -echo "Checking gofmt..." -res=$(gofmt -d -e -s $SRC) -echo "${res}" -if [ -n "${res}" ]; then - exit 1 -fi - -echo "Checking govet..." -go vet $PKG - -echo "Running tests..." -go test -timeout 60s -cover $@ ${PKG} --race - -echo "Success"