Skip to content

Commit

Permalink
feat: add akamai platform support
Browse files Browse the repository at this point in the history
Add support for the Akamai(Linode) platform

Signed-off-by: Evan Johnson <ejohnson@akamai.com>
Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
  • Loading branch information
eljohnson92 authored and smira committed Apr 3, 2024
1 parent 5c0f74b commit e8ae5ef
Show file tree
Hide file tree
Showing 15 changed files with 530 additions and 3 deletions.
2 changes: 2 additions & 0 deletions .drone.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -1054,6 +1054,8 @@ local release = {
draft: true,
note: '_out/RELEASE_NOTES.md',
files: [
'_out/akamai-amd64.raw.gz',
'_out/akamai-arm64.raw.gz',
'_out/aws-amd64.raw.xz',
'_out/aws-arm64.raw.xz',
'_out/azure-amd64.vhd.xz',
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ image-%: ## Builds the specified image. Valid options are aws, azure, digital-oc

images-essential: image-aws image-azure image-gcp image-metal secureboot-installer ## Builds only essential images used in the CI (AWS, GCP, and Metal).

images: image-aws image-azure image-digital-ocean image-exoscale image-gcp image-hcloud image-iso image-metal image-nocloud image-opennebula image-openstack image-oracle image-scaleway image-upcloud image-vmware image-vultr ## Builds all known images (AWS, Azure, DigitalOcean, Exoscale, GCP, HCloud, Metal, NoCloud, OpenNebula, Openstack, Oracle, Scaleway, UpCloud, Vultr and VMware).
images: image-akamai image-aws image-azure image-digital-ocean image-exoscale image-gcp image-hcloud image-iso image-metal image-nocloud image-opennebula image-openstack image-oracle image-scaleway image-upcloud image-vmware image-vultr ## Builds all known images (AWS, Azure, DigitalOcean, Exoscale, GCP, HCloud, Metal, NoCloud, OpenNebula, Openstack, Oracle, Scaleway, UpCloud, Vultr and VMware).

.PHONY: iso
iso: image-iso ## Builds the ISO and outputs it to the artifact directory.
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ require (
github.com/jeromer/syslogparser v1.1.0
github.com/jsimonetti/rtnetlink v1.4.1
github.com/jxskiss/base62 v1.1.0
github.com/linode/go-metadata v0.2.0
github.com/klauspost/cpuid/v2 v2.2.7
github.com/martinlindhe/base36 v1.1.1
github.com/mattn/go-isatty v0.0.20
Expand Down Expand Up @@ -242,6 +243,7 @@ require (
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/swag v0.22.3 // indirect
github.com/go-resty/resty/v2 v2.9.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang-jwt/jwt/v5 v5.2.0 // indirect
Expand Down
13 changes: 13 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,8 @@ github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2Kv
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-resty/resty/v2 v2.9.1 h1:PIgGx4VrHvag0juCJ4dDv3MiFRlDmP0vicBucwf+gLM=
github.com/go-resty/resty/v2 v2.9.1/go.mod h1:4/GYJVjh9nhkhGR6AUNW3XhpDYNUr+Uvy9gV/VGZIy4=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
Expand Down Expand Up @@ -475,6 +477,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/linode/go-metadata v0.2.0 h1:hlWzkYLa80ikA0NmFX2hcwhcnWFol8F3UIvJnOgdKw4=
github.com/linode/go-metadata v0.2.0/go.mod h1:XraDbSwms0+CtA7/Qh7agkSvGDc6H0s782kpX9MdMu0=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
Expand Down Expand Up @@ -848,6 +852,7 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
Expand Down Expand Up @@ -932,6 +937,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
Expand Down Expand Up @@ -1023,6 +1030,7 @@ golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
Expand All @@ -1033,6 +1041,8 @@ golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuX
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
Expand All @@ -1048,11 +1058,14 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down
6 changes: 6 additions & 0 deletions hack/release.toml
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ machine:
extraTags:
server: s03-rack07
```
"""

[notes.platforms]
title = "Platforms"
description = """\
Talos Linux now supports [Akamai Connected Cloud](https://www.linode.com/) provider (platform `akamai`).
"""

[make_deps]
Expand Down
206 changes: 206 additions & 0 deletions internal/app/machined/pkg/runtime/v1alpha1/platform/akamai/akamai.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

// Package akamai contains the Akamai implementation of the [platform.Platform].
package akamai

import (
"context"
"fmt"
"net/netip"
"strconv"
"strings"

"github.com/cosi-project/runtime/pkg/state"
akametadata "github.com/linode/go-metadata"
"github.com/siderolabs/go-procfs/procfs"

"github.com/siderolabs/talos/internal/app/machined/pkg/runtime"
"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/internal/netutils"
"github.com/siderolabs/talos/pkg/machinery/constants"
"github.com/siderolabs/talos/pkg/machinery/nethelpers"
"github.com/siderolabs/talos/pkg/machinery/resources/network"
runtimeres "github.com/siderolabs/talos/pkg/machinery/resources/runtime"
)

// Akamai is the concrete type that implements the platform.Platform interface.
type Akamai struct{}

// Name implements the platform.Platform interface.
func (a *Akamai) Name() string {
return "akamai"
}

// ParseMetadata converts Akamai platform metadata into platform network config.
func (a *Akamai) ParseMetadata(metadata *akametadata.InstanceData, interfaceAddresses *akametadata.NetworkData) (*runtime.PlatformNetworkConfig, error) {
networkConfig := &runtime.PlatformNetworkConfig{}

if metadata.Label != "" {
hostnameSpec := network.HostnameSpecSpec{
ConfigLayer: network.ConfigPlatform,
}

if err := hostnameSpec.ParseFQDN(metadata.Label); err != nil {
return nil, err
}

networkConfig.Hostnames = append(networkConfig.Hostnames, hostnameSpec)
}

publicIPs := make([]string, 0, len(interfaceAddresses.IPv4.Public)+len(interfaceAddresses.IPv6.Ranges))

// external IP
for _, iface := range interfaceAddresses.IPv4.Public {
publicIPs = append(publicIPs, iface.Addr().String())
networkConfig.Addresses = append(networkConfig.Addresses,
network.AddressSpecSpec{
ConfigLayer: network.ConfigPlatform,
LinkName: "eth0",
Address: iface,
Scope: nethelpers.ScopeGlobal,
Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent),
Family: nethelpers.FamilyInet4,
},
)
}

for _, iface := range interfaceAddresses.IPv4.Private {
networkConfig.Addresses = append(networkConfig.Addresses,
network.AddressSpecSpec{
ConfigLayer: network.ConfigPlatform,
LinkName: "eth0",
Address: iface,
Scope: nethelpers.ScopeGlobal,
Flags: nethelpers.AddressFlags(nethelpers.AddressPermanent),
Family: nethelpers.FamilyInet4,
},
)
}

for _, iface := range interfaceAddresses.IPv6.Ranges {
publicIPs = append(publicIPs, iface.Addr().String())

networkConfig.Addresses = append(networkConfig.Addresses,
network.AddressSpecSpec{
ConfigLayer: network.ConfigPlatform,
LinkName: "eth0",
Address: iface,
Scope: nethelpers.ScopeGlobal,
Flags: nethelpers.AddressFlags(nethelpers.AddressManagementTemp),
Family: nethelpers.FamilyInet6,
},
)
}

networkConfig.Addresses = append(networkConfig.Addresses,
network.AddressSpecSpec{
ConfigLayer: network.ConfigPlatform,
LinkName: "eth0",
Address: interfaceAddresses.IPv6.LinkLocal,
Scope: nethelpers.ScopeLink,
Family: nethelpers.FamilyInet6,
},
)

ipv6gw, err := netip.ParseAddr(strings.Split(interfaceAddresses.IPv6.LinkLocal.String(), ":")[0] + "::1")
if err != nil {
return nil, err
}

route := network.RouteSpecSpec{
ConfigLayer: network.ConfigPlatform,
Gateway: ipv6gw,
OutLinkName: "eth0",
Destination: interfaceAddresses.IPv6.LinkLocal,
Table: nethelpers.TableMain,
Protocol: nethelpers.ProtocolStatic,
Type: nethelpers.TypeUnicast,
Family: nethelpers.FamilyInet6,
Priority: 1024,
}

route.Normalize()

networkConfig.Routes = append(networkConfig.Routes, route)

for _, ipStr := range publicIPs {
if ip, err := netip.ParseAddr(ipStr); err == nil {
networkConfig.ExternalIPs = append(networkConfig.ExternalIPs, ip)
}
}

networkConfig.Metadata = &runtimeres.PlatformMetadataSpec{
Platform: a.Name(),
Hostname: metadata.Label,
Region: metadata.Region,
InstanceType: metadata.Type,
InstanceID: strconv.Itoa(metadata.ID),
ProviderID: fmt.Sprintf("linode://%d", metadata.ID),
}

return networkConfig, nil
}

// Configuration implements the platform.Platform interface.
func (a *Akamai) Configuration(ctx context.Context, r state.State) ([]byte, error) {
if err := netutils.Wait(ctx, r); err != nil {
return nil, err
}

metadataClient, err := akametadata.NewClient(ctx)
if err != nil {
return nil, fmt.Errorf("new metadata client: %w", err)
}

userData, err := metadataClient.GetUserData(ctx)
if err != nil {
return nil, fmt.Errorf("get user data: %w", err)
}

return []byte(userData), err
}

// Mode implements the platform.Platform interface.
func (a *Akamai) Mode() runtime.Mode {
return runtime.ModeCloud
}

// KernelArgs implements the runtime.Platform interface.
func (a *Akamai) KernelArgs(string) procfs.Parameters {
return []*procfs.Parameter{
procfs.NewParameter("console").Append("ttyS0").Append("tty0").Append("tty1"),
procfs.NewParameter(constants.KernelParamNetIfnames).Append("0"),
}
}

// NetworkConfiguration implements the runtime.Platform interface.
func (a *Akamai) NetworkConfiguration(ctx context.Context, _ state.State, ch chan<- *runtime.PlatformNetworkConfig) error {
metadataClient, err := akametadata.NewClient(ctx)
if err != nil {
return fmt.Errorf("new metadata client: %w", err)
}

metadata, err := metadataClient.GetInstance(ctx)
if err != nil {
return fmt.Errorf("get instance data: %w", err)
}

metadataNetworkConfig, err := metadataClient.GetNetwork(ctx)
if err != nil {
return fmt.Errorf("get network data: %w", err)
}

networkConfig, err := a.ParseMetadata(metadata, metadataNetworkConfig)
if err != nil {
return fmt.Errorf("parse metadata: %w", err)
}

select {
case ch <- networkConfig:
case <-ctx.Done():
return ctx.Err()
}

return nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package akamai_test

import (
_ "embed"
"encoding/json"
"testing"

akametadata "github.com/linode/go-metadata"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"

"github.com/siderolabs/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/akamai"
)

//go:embed testdata/instance.json
var rawMetadata []byte

//go:embed testdata/network.json

var rawNetwork []byte

//go:embed testdata/expected.yaml
var expectedNetworkConfig string

func TestParseMetadata(t *testing.T) {
p := &akamai.Akamai{}

var metadata akametadata.InstanceData

var interfaceConfig akametadata.NetworkData

require.NoError(t, json.Unmarshal(rawMetadata, &metadata))

require.NoError(t, json.Unmarshal(rawNetwork, &interfaceConfig))

networkConfig, err := p.ParseMetadata(&metadata, &interfaceConfig)
require.NoError(t, err)

marshaled, err := yaml.Marshal(networkConfig)
require.NoError(t, err)

assert.Equal(t, expectedNetworkConfig, string(marshaled))
}
Loading

0 comments on commit e8ae5ef

Please sign in to comment.