Skip to content

Commit 5c7c765

Browse files
authored
Merge pull request #1841 from nesv/provider.akamai
2 parents 44e3886 + 9b833b2 commit 5c7c765

File tree

4 files changed

+131
-0
lines changed

4 files changed

+131
-0
lines changed

docs/release-notes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ nav_order: 9
1010

1111
### Features
1212

13+
- Support Akamai Connected Cloud (Linode)
14+
1315
### Changes
1416

1517
### Bug fixes

docs/supported-platforms.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ nav_order: 8
66

77
Ignition is currently only supported for the following platforms:
88

9+
* [Akamai Connected Cloud] (`akamai`) - Ignition will read its configuration from the instance userdata. Cloud SSH keys and network configuration are handled separately.
910
* [Alibaba Cloud] (`aliyun`) - Ignition will read its configuration from the instance userdata. Cloud SSH keys are handled separately.
1011
* [Apple Hypervisor] (`applehv`) - Ignition will read its configuration using an HTTP GET over a vsock connection with its host on port 1024.
1112
* [Amazon Web Services] (`aws`) - Ignition will read its configuration from the instance userdata. Cloud SSH keys are handled separately.
@@ -36,6 +37,7 @@ Ignition is under active development, so this list may grow over time.
3637

3738
For most cloud providers, cloud SSH keys and custom network configuration are handled by [Afterburn].
3839

40+
[Akamai Connected Cloud]: https://www.linode.com
3941
[Alibaba Cloud]: https://www.alibabacloud.com/product/ecs
4042
[Apple Hypervisor]: https://developer.apple.com/documentation/hypervisor
4143
[Amazon Web Services]: https://aws.amazon.com/ec2/
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
// Copyright 2024 CoreOS, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Package akamai provides platform support for Akamai Connected Cloud
16+
// (previously known as Linode).
17+
package akamai
18+
19+
import (
20+
"bytes"
21+
"encoding/base64"
22+
"errors"
23+
"fmt"
24+
"net/http"
25+
"net/url"
26+
27+
"github.com/coreos/ignition/v2/config/v3_5_experimental/types"
28+
"github.com/coreos/ignition/v2/internal/platform"
29+
"github.com/coreos/ignition/v2/internal/providers/util"
30+
"github.com/coreos/ignition/v2/internal/resource"
31+
32+
"github.com/coreos/vcontext/report"
33+
)
34+
35+
func init() {
36+
platform.Register(platform.Provider{
37+
Name: "akamai",
38+
Fetch: fetchConfig,
39+
})
40+
}
41+
42+
// HTTP headers.
43+
const (
44+
// tokenTTLHeader is the name of the HTTP request header that must be
45+
// set when making requests to [tokenURL] or [tokenURL6].
46+
tokenTTLHeader = "Metadata-Token-Expiry-Seconds"
47+
48+
// tokenHeader is the name of the HTTP request header that callers must
49+
// set when making requests to [userdataURL] or [userdataURL6].
50+
tokenHeader = "Metadata-Token"
51+
)
52+
53+
var (
54+
// IPv4 URLs.
55+
tokenURL = url.URL{Scheme: "http", Host: "169.254.169.254", Path: "/v1/token"}
56+
userdataURL = url.URL{Scheme: "http", Host: "169.254.169.254", Path: "/v1/user-data"}
57+
58+
// IPv6 URLs (for reference).
59+
// tokenURL6 = url.URL{Scheme: "http", Host: "[fd00:a9fe:a9fe::1]", Path: "/v1/token"}
60+
// userdataURL6 = url.URL{Scheme: "http", Host: "[fd00:a9fe:a9fe::1]", Path: "/v1/user-data"}
61+
)
62+
63+
func fetchConfig(f *resource.Fetcher) (types.Config, report.Report, error) {
64+
if f.Offline {
65+
return types.Config{}, report.Report{}, resource.ErrNeedNet
66+
}
67+
68+
token, err := getToken(f)
69+
if err != nil {
70+
return types.Config{}, report.Report{}, fmt.Errorf("get token: %w", err)
71+
}
72+
73+
// NOTE: If we do not explicitly set the "Accept" header, it will be
74+
// set by FetchToBuffer to a value that the Linode Metadata Service
75+
// does not accept.
76+
encoded, err := f.FetchToBuffer(userdataURL, resource.FetchOptions{
77+
Headers: http.Header{
78+
"Accept": []string{"*/*"},
79+
tokenHeader: []string{string(token)},
80+
},
81+
})
82+
if err != nil {
83+
return types.Config{}, report.Report{}, fmt.Errorf("fetch userdata: %w", err)
84+
}
85+
86+
// The Linode Metadata Service requires userdata to be base64-encoded
87+
// when it is uploaded, so we will have to decode the response.
88+
data := make([]byte, base64.StdEncoding.DecodedLen(len(encoded)))
89+
if _, err := base64.StdEncoding.Decode(data, encoded); err != nil {
90+
return types.Config{}, report.Report{}, fmt.Errorf("decode base64: %w", err)
91+
}
92+
93+
return util.ParseConfig(f.Logger, data)
94+
}
95+
96+
// defaultTokenTTL is the time-to-live (TTL; in seconds) for an authorization
97+
// token retrieved from the Metadata Service API's "PUT /v1/token" endpoint.
98+
const defaultTokenTTL = "300"
99+
100+
// getToken retrieves an authorization token to use for subsequent requests to
101+
// Linode's Metadata Service.
102+
// The returned token must be provided in the [tokenHeader] request header.
103+
func getToken(f *resource.Fetcher) (token string, err error) {
104+
// NOTE: This is using "text/plain" for content negotiation, just to
105+
// skip the need to decode a JSON response.
106+
// In the future, the accepted content type should probably become
107+
// "application/vnd.coreos.ignition+json", but that will require
108+
// support from Linode's Metadata Service API.
109+
p, err := f.FetchToBuffer(tokenURL, resource.FetchOptions{
110+
HTTPVerb: http.MethodPut,
111+
Headers: http.Header{
112+
"Accept": []string{"text/plain"},
113+
tokenTTLHeader: []string{defaultTokenTTL},
114+
},
115+
})
116+
if err != nil {
117+
return "", fmt.Errorf("fetch to buffer: %w", err)
118+
}
119+
120+
p = bytes.TrimSpace(p)
121+
if len(p) == 0 {
122+
return "", errors.New("received an empty token")
123+
}
124+
125+
return string(p), nil
126+
}

internal/register/providers.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package register
1616

1717
import (
18+
_ "github.com/coreos/ignition/v2/internal/providers/akamai"
1819
_ "github.com/coreos/ignition/v2/internal/providers/aliyun"
1920
_ "github.com/coreos/ignition/v2/internal/providers/applehv"
2021
_ "github.com/coreos/ignition/v2/internal/providers/aws"

0 commit comments

Comments
 (0)