diff --git a/README.md b/README.md index e63569f0..3af3935f 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,13 @@ The supported cloud providers and their respective metadata are as follows: - COREOS_GCE_HOSTNAME - COREOS_GCE_IP_EXTERNAL_0 - COREOS_GCE_IP_LOCAL_0 + - hcloud + - SSH Keys + - Attributes + - COREOS_HCLOUD_HOSTNAME + - COREOS_HCLOUD_IPV4_LOCAL + - COREOS_HCLOUD_IPV4_PUBLIC + - COREOS_HCLOUD_INSTANCE_ID - openstack-metadata - SSH Keys - Attributes diff --git a/src/metadata.rs b/src/metadata.rs index 5d28b95e..18ce3323 100644 --- a/src/metadata.rs +++ b/src/metadata.rs @@ -20,6 +20,7 @@ use providers::cloudstack::network::CloudstackNetwork; use providers::digitalocean::DigitalOceanProvider; use providers::ec2::Ec2Provider; use providers::gce::GceProvider; +use providers::hcloud::HetznerCloudProvider; use providers::openstack::network::OpenstackProvider; use providers::packet::PacketProvider; use providers::vagrant_virtualbox::VagrantVirtualboxProvider; @@ -41,6 +42,7 @@ pub fn fetch_metadata(provider: &str) -> errors::Result box_result!(DigitalOceanProvider::try_new()?), "ec2" => box_result!(Ec2Provider::try_new()?), "gce" => box_result!(GceProvider::try_new()?), + "hcloud" => box_result!(HetznerCloudProvider::try_new()?), "openstack-metadata" => box_result!(OpenstackProvider::try_new()?), "packet" => box_result!(PacketProvider::try_new()?), "vagrant-virtualbox" => box_result!(VagrantVirtualboxProvider::new()), diff --git a/src/providers/hcloud/mod.rs b/src/providers/hcloud/mod.rs new file mode 100644 index 00000000..82858274 --- /dev/null +++ b/src/providers/hcloud/mod.rs @@ -0,0 +1,108 @@ +// Copyright 2018 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. + +//! Hetzner Cloud metadata fetcher +//! + +use std::collections::HashMap; + +use openssh_keys::PublicKey; +use update_ssh_keys::AuthorizedKeyEntry; + +use errors::*; +use network; +use providers::MetadataProvider; +use retry; +use serde_json; + +const URL: &str = "http://169.254.169.254/2009-04-04"; + +#[allow(non_snake_case)] +#[derive(Debug, Deserialize)] +struct InstanceIdDoc { + region: String, +} + +#[derive(Clone, Debug)] +pub struct HetznerCloudProvider { + client: retry::Client, +} + +impl HetznerCloudProvider { + pub fn try_new() -> Result { + let client = retry::Client::try_new()? + .return_on_404(true); + + Ok(HetznerCloudProvider { client }) + } + + fn endpoint_for(key: &str) -> String { + format!("{}/{}", URL, key) + } +} + +impl MetadataProvider for HetznerCloudProvider { + fn attributes(&self) -> Result> { + let mut out = HashMap::with_capacity(4); + + let add_value = |map: &mut HashMap<_, _>, key: &str, name| -> Result<()> { + let value = self.client.get(retry::Raw, HetznerCloudProvider::endpoint_for(name)).send()?; + + if let Some(value) = value { + map.insert(key.to_string(), value); + } + + Ok(()) + }; + + add_value(&mut out, "HCLOUD_INSTANCE_ID", "meta-data/instance-id")?; + add_value(&mut out, "HCLOUD_IPV4_LOCAL", "meta-data/local-ipv4")?; + add_value(&mut out, "HCLOUD_IPV4_PUBLIC", "meta-data/public-ipv4")?; + add_value(&mut out, "HCLOUD_HOSTNAME", "meta-data/hostname")?; + + Ok(out) + } + + fn hostname(&self) -> Result> { + self.client.get(retry::Raw, HetznerCloudProvider::endpoint_for("meta-data/hostname")).send() + } + + fn ssh_keys(&self) -> Result> { + let raw_keys: Option = self.client + .get(retry::Raw, HetznerCloudProvider::endpoint_for("meta-data/public-keys")) + .send()?; + + if let Some(raw_keys) = raw_keys { + let keys: Vec = serde_json::from_str(&raw_keys).unwrap(); + let mut out = Vec::new(); + + for key in keys { + let key = PublicKey::parse(&key)?; + out.push(AuthorizedKeyEntry::Valid{key}); + } + + Ok(out) + } else { + Ok(vec![]) + } + } + + fn networks(&self) -> Result> { + Ok(vec![]) + } + + fn network_devices(&self) -> Result> { + Ok(vec![]) + } +} diff --git a/src/providers/mod.rs b/src/providers/mod.rs index ff50e14f..f6217ab0 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -28,6 +28,7 @@ pub mod digitalocean; pub mod cloudstack; pub mod ec2; pub mod gce; +pub mod hcloud; pub mod openstack; pub mod packet; pub mod vagrant_virtualbox;