Skip to content

Commit

Permalink
providers: Add "akamai" provider
Browse files Browse the repository at this point in the history
The "akamai" provider adds support for retrieving configuration from
Akamai Connected Cloud's (a.k.a. Linode) [Metadata
Service][metadata-service].

References: flatcar/Flatcar#1404
References: coreos/fedora-coreos-tracker#1701
References: coreos/ignition#1841

[metadata-service]: https://www.linode.com/docs/products/compute/compute-instances/guides/metadata/
  • Loading branch information
nesv committed Apr 10, 2024
1 parent ff6b1c5 commit 21961f5
Show file tree
Hide file tree
Showing 8 changed files with 412 additions and 0 deletions.
3 changes: 3 additions & 0 deletions docs/platforms.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ By default Afterburn uses the Ignition platform ID to detect the environment whe

The following platforms are supported, with a different set of features available on each:

* akamai
- Attributes
- SSH Keys
* aliyun
- Attributes
- SSH Keys
Expand Down
2 changes: 2 additions & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ nav_order: 8

Major changes:

- Add support for Akamai Connected Cloud (Linode)

Minor changes:

Packaging changes:
Expand Down
14 changes: 14 additions & 0 deletions docs/usage/attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@ which wants to make use of Afterburn metadata must explicitly pull it in using e

Cloud providers with supported metadata endpoints and their respective attributes are listed below.

* akamai
- AFTERBURN_AKAMAI_INSTANCE_HOST_UUID
- AFTERBURN_AKAMAI_INSTANCE_ID
- AFTERBURN_AKAMAI_INSTANCE_LABEL
- AFTERBURN_AKAMAI_INSTANCE_REGION
- AFTERBURN_AKAMAI_INSTANCE_TAGS
- AFTERBURN_AKAMAI_INSTANCE_TYPE
- AFTERBURN_AKAMAI_IPV6_LINK_LOCAL
- AFTERBURN_AKAMAI_IPV6_RANGE_0
- AFTERBURN_AKAMAI_IPV6_SHARED_RANGE_0
- AFTERBURN_AKAMAI_IPV6_SLAAC
- AFTERBURN_AKAMAI_PRIVATE_IPV4_0
- AFTERBURN_AKAMAI_PUBLIC_IPV4_0
- AFTERBURN_AKAMAI_SHARED_IPV4_0
* aliyun
- AFTERBURN_ALIYUN_EIPV4
- AFTERBURN_ALIYUN_HOSTNAME
Expand Down
2 changes: 2 additions & 0 deletions src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use anyhow::{bail, Result};

use crate::providers;
use crate::providers::akamai::AkamaiProvider;
use crate::providers::aliyun::AliyunProvider;
use crate::providers::aws::AwsProvider;
use crate::providers::cloudstack::configdrive::ConfigDrive;
Expand Down Expand Up @@ -49,6 +50,7 @@ macro_rules! box_result {
/// to the provider-specific fetch logic.
pub fn fetch_metadata(provider: &str) -> Result<Box<dyn providers::MetadataProvider>> {
match provider {
"akamai" => box_result!(AkamaiProvider::try_new()?),
"aliyun" => box_result!(AliyunProvider::try_new()?),
"aws" => box_result!(AwsProvider::try_new()?),
"azure" => box_result!(Azure::try_new()?),
Expand Down
106 changes: 106 additions & 0 deletions src/providers/akamai/mock_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use super::{HDR_TOKEN, HDR_TOKEN_EXPIRY, TOKEN_TTL};
use crate::providers::{akamai::AkamaiProvider, MetadataProvider};
use mockito::{self};

#[test]
fn test_attributes() {
let mut server = mockito::Server::new();
let token = "deadbeefcafebabe";

// Mock the PUT /v1/token endpoint.
let put_v1_token = server
.mock("PUT", "/v1/token")
.match_header(HDR_TOKEN_EXPIRY, TOKEN_TTL)
.with_body(token)
.expect_at_least(1)
.create();

// Mock the GET /v1/instance endpoint.
let instance_metadata = r#"{
"id": 12345678,
"label": "my-linode",
"region": "us-ord",
"type": "g6-nanode-1",
"specs": {
"vcpus": 1,
"memory": 1024,
"gpus": 0,
"transfer": 1000,
"disk": 25600
},
"backups": {
"enabled": false,
"status": null
},
"host_uuid": "a631b16d14534d84e2830da16d1b28e1d08d24df",
"tags": ["foo", "bar", "baz"]
}"#;

let get_v1_instance = server
.mock("GET", "/v1/instance")
.match_header("Accept", "application/json")
.match_header(HDR_TOKEN, token)
.with_body(instance_metadata)
.create();

// Mock the /v1/network endpoint.
let network_metadata = r#"{
"interfaces": [
{
"id": 12345678,
"purpose": "public",
"label": null,
"ipam_address": null
}
],
"ipv4": {
"public": [
"1.2.3.4/32"
],
"private": [
"192.168.1.1/32"
],
"shared": []
},
"ipv6": {
"slaac": "2600:3c06::f03c:94ff:fecb:c10b/128",
"ranges": [],
"link_local": "fe80::f03c:94ff:fecb:c10b/128",
"shared_ranges": []
}
}"#;

let get_v1_network = server
.mock("GET", "/v1/network")
.match_header("Accept", "application/json")
.match_header(HDR_TOKEN, token)
.with_body(network_metadata)
.create();

let mut provider = AkamaiProvider::try_new().unwrap();
provider.client = provider.client.max_retries(0).mock_base_url(server.url());

let attrs = provider.attributes();

// Assert that our endpoints were called.
put_v1_token.assert();
get_v1_instance.assert();
get_v1_network.assert();

let actual = attrs.unwrap();
let expected = maplit::hashmap! {
"AKAMAI_INSTANCE_ID".to_string() => "12345678".to_string(),
"AKAMAI_INSTANCE_HOST_UUID".to_string() => "a631b16d14534d84e2830da16d1b28e1d08d24df".to_string(),
"AKAMAI_INSTANCE_LABEL".to_string() => "my-linode".to_string(),
"AKAMAI_INSTANCE_REGION".to_string() => "us-ord".to_string(),
"AKAMAI_INSTANCE_TYPE".to_string() => "g6-nanode-1".to_string(),
"AKAMAI_INSTANCE_TAGS".to_string() => "foo:bar:baz".to_string(),
"AKAMAI_PUBLIC_IPV4_0".to_string() => "1.2.3.4/32".to_string(),
"AKAMAI_PRIVATE_IPV4_0".to_string() => "192.168.1.1/32".to_string(),
"AKAMAI_IPV6_SLAAC".to_string() => "2600:3c06::f03c:94ff:fecb:c10b/128".to_string(),
"AKAMAI_IPV6_LINK_LOCAL".to_string() => "fe80::f03c:94ff:fecb:c10b/128".to_string(),
};
assert_eq!(expected, actual);

server.reset();
}

0 comments on commit 21961f5

Please sign in to comment.