Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

providers: add support for hetzner cloud #125

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -41,6 +42,7 @@ pub fn fetch_metadata(provider: &str) -> errors::Result<Box<providers::MetadataP
"digitalocean" => 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()),
Expand Down
108 changes: 108 additions & 0 deletions src/providers/hcloud/mod.rs
Original file line number Diff line number Diff line change
@@ -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";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a public reference where this endpoint JSON response is documented?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LKaemmerling Could you answer this from the Hetzner team? It seems there is indeed no public documentation of this endpoint (i.e. on https://docs.hetzner.cloud/)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This endpoint is not public documented.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the endpoint stable? (will it continue to behave as the current implementation?)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it is not public documented, we can not guarantee that it will behave the same way as the current implementation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thcyron thanks for the feedback! Do you have some spec documentation URL to reference here, or should we just follow whatever the CSI driver is doing?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately our metadata service is not documented. You can use all endpoints except local-ipv4 (since we don’t have private networking yet) as listed in this comment.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we remove the local-ipv4 or can we keep it for now? For the moment it is not used anywhere but then the metadata service in coreos is already prepared for the moment Hetzner would add private networking (which would be awesome ;))?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can keep it. It’s no secret that we’ll offer private networking sooner or later and I’m pretty sure we’ll support the local-ipv4 endpoint then.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to use the official endpoint http://169.254.169.254/hetzner/v1/metadata instead of the EC2-compat path


#[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<HetznerCloudProvider> {
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<HashMap<String, String>> {
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<Option<String>> {
self.client.get(retry::Raw, HetznerCloudProvider::endpoint_for("meta-data/hostname")).send()
}

fn ssh_keys(&self) -> Result<Vec<AuthorizedKeyEntry>> {
let raw_keys: Option<String> = self.client
.get(retry::Raw, HetznerCloudProvider::endpoint_for("meta-data/public-keys"))
.send()?;

if let Some(raw_keys) = raw_keys {
let keys: Vec<String> = 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<Vec<network::Interface>> {
Ok(vec![])
}

fn network_devices(&self) -> Result<Vec<network::Device>> {
Ok(vec![])
}
}
1 change: 1 addition & 0 deletions src/providers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down