diff --git a/README.md b/README.md index f0322b25..8a293ac3 100644 --- a/README.md +++ b/README.md @@ -34,12 +34,14 @@ On CoreOS Container Linux, the supported providers and metadata are [somewhat di - AFTERBURN_AWS_IPV4_PUBLIC - AFTERBURN_AWS_AVAILABILITY_ZONE - AFTERBURN_AWS_INSTANCE_ID + - AFTERBURN_AWS_INSTANCE_TYPE - AFTERBURN_AWS_REGION - azure - SSH Keys - Attributes - AFTERBURN_AZURE_IPV4_DYNAMIC - AFTERBURN_AZURE_IPV4_VIRTUAL + - AFTERBURN_AZURE_VMSIZE - cloudstack-configdrive - SSH Keys - Attributes @@ -79,6 +81,7 @@ On CoreOS Container Linux, the supported providers and metadata are [somewhat di - AFTERBURN_GCP_HOSTNAME - AFTERBURN_GCP_IP_EXTERNAL_0 - AFTERBURN_GCP_IP_LOCAL_0 + - AFTERBURN_GCP_MACHINE_TYPE - openstack-metadata - SSH Keys - Attributes @@ -86,11 +89,13 @@ On CoreOS Container Linux, the supported providers and metadata are [somewhat di - AFTERBURN_OPENSTACK_IPV4_LOCAL - AFTERBURN_OPENSTACK_IPV4_PUBLIC - AFTERBURN_OPENSTACK_INSTANCE_ID + - AFTERBURN_OPENSTACK_INSTANCE_TYPE - packet - SSH Keys - Network Configs - Attributes - AFTERBURN_PACKET_HOSTNAME + - AFTERBURN_PACKET_PLAN - AFTERBURN_PACKET_IPV4_PUBLIC_0 - AFTERBURN_PACKET_IPV4_PRIVATE_0 - AFTERBURN_PACKET_IPV6_PUBLIC_0 diff --git a/docs/container-linux-legacy.md b/docs/container-linux-legacy.md index 09638948..c86ccad0 100644 --- a/docs/container-linux-legacy.md +++ b/docs/container-linux-legacy.md @@ -17,6 +17,7 @@ On Container Linux, the supported cloud providers and their respective metadata - Attributes - COREOS_AZURE_IPV4_DYNAMIC - COREOS_AZURE_IPV4_VIRTUAL + - COREOS_AZURE_VMSIZE - cloudstack-configdrive - SSH Keys - Attributes @@ -59,6 +60,7 @@ On Container Linux, the supported cloud providers and their respective metadata - COREOS_EC2_IPV4_PUBLIC - COREOS_EC2_AVAILABILITY_ZONE - COREOS_EC2_INSTANCE_ID + - COREOS_EC2_INSTANCE_TYPE - COREOS_EC2_REGION - gce - SSH Keys @@ -66,6 +68,7 @@ On Container Linux, the supported cloud providers and their respective metadata - COREOS_GCE_HOSTNAME - COREOS_GCE_IP_EXTERNAL_0 - COREOS_GCE_IP_LOCAL_0 + - COREOS_GCE_MACHINE_TYPE - openstack-metadata - SSH Keys - Attributes @@ -73,11 +76,13 @@ On Container Linux, the supported cloud providers and their respective metadata - COREOS_OPENSTACK_IPV4_LOCAL - COREOS_OPENSTACK_IPV4_PUBLIC - COREOS_OPENSTACK_INSTANCE_ID + - COREOS_OPENSTACK_INSTANCE_TYPE - packet - SSH Keys - Network Configs - Attributes - COREOS_PACKET_HOSTNAME + - COREOS_PACKET_PLAN - COREOS_PACKET_IPV4_PUBLIC_0 - COREOS_PACKET_IPV4_PRIVATE_0 - COREOS_PACKET_IPV6_PUBLIC_0 diff --git a/src/providers/aws/mock_tests.rs b/src/providers/aws/mock_tests.rs index 6e694f9a..d09837c6 100644 --- a/src/providers/aws/mock_tests.rs +++ b/src/providers/aws/mock_tests.rs @@ -1,6 +1,7 @@ use crate::errors::*; use crate::providers::aws; use mockito; +use crate::providers::MetadataProvider; #[test] fn test_aws_basic() { @@ -24,4 +25,64 @@ fn test_aws_basic() { let _m = mockito::mock("GET", ep).with_status(404).create(); let v = provider.fetch_ssh_keys().unwrap(); assert_eq!(v.len(), 0); + + mockito::reset(); + provider.fetch_ssh_keys().unwrap_err(); } + +#[test] +fn test_aws_attributes() { + let instance_id = "test-instance-id"; + let instance_type = "test-instance-type"; + let ipv4_local = "test-ipv4-local"; + let ipv4_public = "test-ipv4-public"; + let availability_zone = "test-availability-zone"; + let hostname = "test-hostname"; + let public_hostname = "test-public-hostname"; + let instance_id_doc = r#"{"region": "test-region"}"#; + let region = "test-region"; + + let endpoints = maplit::btreemap! { + "/meta-data/instance-id" => instance_id, + "/meta-data/instance-type" => instance_type, + "/meta-data/local-ipv4" => ipv4_local, + "/meta-data/public-ipv4" => ipv4_public, + "/meta-data/placement/availability-zone" => availability_zone, + "/meta-data/hostname" => hostname, + "/meta-data/public-hostname" => public_hostname, + "/dynamic/instance-identity/document" => instance_id_doc, + }; + + let mut mocks = Vec::with_capacity(endpoints.len()); + for (endpoint, body) in endpoints { + let m = mockito::mock("GET", endpoint) + .with_status(200) + .with_body(body) + .create(); + mocks.push(m); + } + + let attributes = maplit::hashmap! { + format!("{}_INSTANCE_ID", aws::ENV_PREFIX) => instance_id.to_string(), + format!("{}_INSTANCE_TYPE", aws::ENV_PREFIX) => instance_type.to_string(), + format!("{}_IPV4_LOCAL", aws::ENV_PREFIX) => ipv4_local.to_string(), + format!("{}_IPV4_PUBLIC", aws::ENV_PREFIX) => ipv4_public.to_string(), + format!("{}_AVAILABILITY_ZONE", aws::ENV_PREFIX) => availability_zone.to_string(), + format!("{}_HOSTNAME", aws::ENV_PREFIX) => hostname.to_string(), + format!("{}_PUBLIC_HOSTNAME", aws::ENV_PREFIX) => public_hostname.to_string(), + format!("{}_REGION", aws::ENV_PREFIX) => region.to_string(), + }; + + let client = crate::retry::Client::try_new() + .chain_err(|| "failed to create http client") + .unwrap() + .max_attempts(1) + .return_on_404(true); + let provider = aws::AwsProvider { client }; + + let v = provider.attributes().unwrap(); + assert_eq!(v, attributes); + + mockito::reset(); + provider.attributes().unwrap_err(); +} \ No newline at end of file diff --git a/src/providers/aws/mod.rs b/src/providers/aws/mod.rs index d070bee6..95dbe8fe 100644 --- a/src/providers/aws/mod.rs +++ b/src/providers/aws/mod.rs @@ -120,6 +120,11 @@ impl MetadataProvider for AwsProvider { &format!("{}_INSTANCE_ID", ENV_PREFIX), "meta-data/instance-id", )?; + add_value( + &mut out, + &format!("{}_INSTANCE_TYPE", ENV_PREFIX), + "meta-data/instance-type", + )?; add_value( &mut out, &format!("{}_IPV4_LOCAL", ENV_PREFIX), diff --git a/src/providers/azure/mock_tests.rs b/src/providers/azure/mock_tests.rs index da332788..b3c3f153 100644 --- a/src/providers/azure/mock_tests.rs +++ b/src/providers/azure/mock_tests.rs @@ -88,6 +88,9 @@ fn test_boot_checkin() { m_goalstate.assert(); m_health.assert(); r.unwrap(); + + mockito::reset(); + azure::Azure::try_new().unwrap_err(); } #[test] @@ -112,4 +115,35 @@ fn test_hostname() { m_hostname.assert(); let hostname = r.unwrap(); assert_eq!(hostname, testname); + + mockito::reset(); + azure::Azure::try_new().unwrap_err(); +} + +#[test] +fn test_vmsize() { + let m_version = mock_fab_version(); + let m_goalstate = mock_goalstate(); + + let testvmsize = "testvmsize"; + let endpoint = "/metadata/instance/compute/vmSize?api-version=2017-08-01&format=text"; + let m_vmsize = mockito::mock("GET", endpoint) + .match_header("Metadata", "true") + .with_body(testvmsize) + .with_status(200) + .create(); + + let provider = azure::Azure::try_new(); + let attributes = provider.unwrap().attributes().unwrap(); + let r = attributes.get("AZURE_VMSIZE"); + + m_version.assert(); + m_goalstate.assert(); + + m_vmsize.assert(); + let vmsize = r.unwrap(); + assert_eq!(vmsize, testvmsize); + + mockito::reset(); + azure::Azure::try_new().unwrap_err(); } diff --git a/src/providers/azure/mod.rs b/src/providers/azure/mod.rs index f45f43b3..0c70e691 100644 --- a/src/providers/azure/mod.rs +++ b/src/providers/azure/mod.rs @@ -17,7 +17,7 @@ mod crypto; use std::collections::HashMap; -use std::net::{IpAddr, SocketAddr}; +use std::net::IpAddr; use openssh_keys::PublicKey; use reqwest::header::{HeaderName, HeaderValue}; @@ -355,7 +355,18 @@ impl Azure { Ok(ssh_pubkey) } + #[cfg(test)] + fn get_attributes(&self) -> Result { + Ok(Attributes{ + virtual_ipv4: Some(Azure::get_fabric_address()), + dynamic_ipv4: Some(Azure::get_fabric_address()), + }) + } + + #[cfg(not(test))] fn get_attributes(&self) -> Result { + use std::net::SocketAddr; + let endpoint = &self.goal_state.container.role_instance_list.role_instances[0] .configuration .shared_config; @@ -418,12 +429,28 @@ impl Azure { .chain_err(|| "failed to get hostname")?; Ok(name) } + + fn fetch_vmsize (&self) -> Result { + const VMSIZE_URL: &str = "metadata/instance/compute/vmSize?api-version=2017-08-01&format=text"; + let url = format!("{}/{}", Self::metadata_endpoint(), VMSIZE_URL); + + let vmsize = retry::Client::try_new()? + .header( + HeaderName::from_static("metadata"), + HeaderValue::from_static("true"), + ) + .get(retry::Raw, url) + .send()? + .chain_err(|| "failed to get vmsize")?; + Ok(vmsize) + } } impl MetadataProvider for Azure { fn attributes(&self) -> Result> { let attributes = self.get_attributes()?; - let mut out = HashMap::with_capacity(2); + let vmsize = self.fetch_vmsize()?; + let mut out = HashMap::with_capacity(3); if let Some(virtual_ipv4) = attributes.virtual_ipv4 { out.insert("AZURE_IPV4_VIRTUAL".to_string(), virtual_ipv4.to_string()); @@ -433,6 +460,8 @@ impl MetadataProvider for Azure { out.insert("AZURE_IPV4_DYNAMIC".to_string(), dynamic_ipv4.to_string()); } + out.insert("AZURE_VMSIZE".to_string(), vmsize.to_string()); + Ok(out) } diff --git a/src/providers/gcp/mock_tests.rs b/src/providers/gcp/mock_tests.rs index 21e10ef4..7c3b5f27 100644 --- a/src/providers/gcp/mock_tests.rs +++ b/src/providers/gcp/mock_tests.rs @@ -27,3 +27,45 @@ fn basic_hostname() { mockito::reset(); provider.hostname().unwrap_err(); } + +#[test] +fn basic_attributes() { + let hostname = "test-hostname"; + let ip_external = "test-ip-external"; + let ip_local = "test-ip-local"; + let machine_type = "test-machine-type"; + + let endpoints = maplit::btreemap! { + "/instance/hostname" => hostname, + "/instance/network-interfaces/0/access-configs/0/external-ip" => ip_external, + "/instance/network-interfaces/0/ip" => ip_local, + "/instance/machine-type" => machine_type, + }; + let mut mocks = Vec::with_capacity(endpoints.len()); + for (endpoint, body) in endpoints { + let m = mockito::mock("GET", endpoint) + .with_status(200) + .with_body(body) + .create(); + mocks.push(m); + } + + let attributes = maplit::hashmap! { + format!("{}_HOSTNAME", gcp::ENV_PREFIX) => hostname.to_string(), + format!("{}_IP_EXTERNAL_0", gcp::ENV_PREFIX) => ip_external.to_string(), + format!("{}_IP_LOCAL_0", gcp::ENV_PREFIX) => ip_local.to_string(), + format!("{}_MACHINE_TYPE", gcp::ENV_PREFIX) => machine_type.to_string(), + }; + + let client = crate::retry::Client::try_new() + .unwrap() + .max_attempts(1) + .return_on_404(true); + let provider = gcp::GcpProvider { client }; + + let v = provider.attributes().unwrap(); + assert_eq!(v, attributes); + + mockito::reset(); + provider.attributes().unwrap_err(); +} diff --git a/src/providers/gcp/mod.rs b/src/providers/gcp/mod.rs index 1970a95e..3ee985d0 100644 --- a/src/providers/gcp/mod.rs +++ b/src/providers/gcp/mod.rs @@ -131,7 +131,7 @@ impl GcpProvider { impl MetadataProvider for GcpProvider { fn attributes(&self) -> Result> { - let mut out = HashMap::with_capacity(3); + let mut out = HashMap::with_capacity(4); let add_value = |map: &mut HashMap<_, _>, key: &str, name| -> Result<()> { let value: Option = self @@ -163,6 +163,11 @@ impl MetadataProvider for GcpProvider { &format!("{}_IP_LOCAL_0", ENV_PREFIX), "instance/network-interfaces/0/ip", )?; + add_value( + &mut out, + &format!("{}_MACHINE_TYPE", ENV_PREFIX), + "instance/machine-type", + )?; Ok(out) } diff --git a/src/providers/openstack/network.rs b/src/providers/openstack/network.rs index 223c4c1d..9797147a 100644 --- a/src/providers/openstack/network.rs +++ b/src/providers/openstack/network.rs @@ -73,6 +73,7 @@ impl MetadataProvider for OpenstackProvider { add_value(&mut out, "OPENSTACK_HOSTNAME", "hostname")?; add_value(&mut out, "OPENSTACK_INSTANCE_ID", "instance-id")?; + add_value(&mut out, "OPENSTACK_INSTANCE_TYPE", "instance-type")?; add_value(&mut out, "OPENSTACK_IPV4_LOCAL", "local-ipv4")?; add_value(&mut out, "OPENSTACK_IPV4_PUBLIC", "public-ipv4")?; diff --git a/src/providers/packet/mock_tests.rs b/src/providers/packet/mock_tests.rs index 5a07ef20..eeb76d92 100644 --- a/src/providers/packet/mock_tests.rs +++ b/src/providers/packet/mock_tests.rs @@ -33,4 +33,45 @@ fn test_boot_checkin() { let r = provider.boot_checkin(); mock.assert(); r.unwrap(); + + mockito::reset(); + provider.boot_checkin().unwrap_err(); +} + +#[test] +fn test_packet_attributes() { + let metadata = r#"{ + "id": "test-id", + "hostname": "test-hostname", + "iqn": "test-iqn", + "plan": "test-plan", + "facility": "test-facility", + "tags": [], + "ssh_keys": [], + "network": { + "interfaces": [], + "addresses": [], + "bonding": { "mode": 0 } + }, + "phone_home_url": "test-url" + }"#; + + let attributes = maplit::hashmap! { + "PACKET_HOSTNAME".to_string() => "test-hostname".to_string(), + "PACKET_PHONE_HOME_URL".to_string() => "test-url".to_string(), + "PACKET_PLAN".to_string() => "test-plan".to_string(), + }; + + let _m = mockito::mock("GET", "/metadata") + .with_status(200) + .with_body(metadata) + .create(); + + let provider = packet::PacketProvider::try_new().unwrap(); + let v = provider.attributes().unwrap(); + + assert_eq!(v, attributes); + + mockito::reset(); + packet::PacketProvider::try_new().unwrap_err(); } diff --git a/src/providers/packet/mod.rs b/src/providers/packet/mod.rs index 3a42520e..0341e9ac 100644 --- a/src/providers/packet/mod.rs +++ b/src/providers/packet/mod.rs @@ -87,6 +87,23 @@ pub struct PacketProvider { } impl PacketProvider { + #[cfg(test)] + pub fn try_new() -> Result { + let client = retry::Client::try_new()?; + let url = mockito::server_url(); + + let data: PacketData = client + .get( + retry::Json, + format!("{}/metadata", url), + ) + .send()? + .ok_or("not found")?; + + Ok(PacketProvider { data }) + } + + #[cfg(not(test))] pub fn try_new() -> Result { let client = retry::Client::try_new()?; @@ -144,6 +161,7 @@ impl PacketProvider { "PACKET_PHONE_HOME_URL".to_owned(), self.data.phone_home_url.clone(), )); + attrs.push(("PACKET_PLAN".to_owned(), self.data.plan.clone())); Ok(attrs) }