Skip to content

Commit

Permalink
feat: add upcloud.com cloud support
Browse files Browse the repository at this point in the history
* cloud-init for upcloud.com
* ipv4/v6 support

Signed-off-by: Serge Logvinov <serge.logvinov@sinextra.dev>
  • Loading branch information
sergelogvinov authored and smira committed Sep 9, 2021
1 parent c3b2429 commit f156ab1
Show file tree
Hide file tree
Showing 6 changed files with 368 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .drone.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,8 @@ local release = {
'_out/talosctl-linux-arm64',
'_out/talosctl-linux-armv7',
'_out/talosctl-windows-amd64.exe',
'_out/upcloud-amd64.raw.xz',
'_out/upcloud-arm64.raw.xz',
'_out/vmware-amd64.ova',
'_out/vmware-arm64.ova',
'_out/vmlinuz-amd64',
Expand Down
15 changes: 14 additions & 1 deletion cmd/installer/cmd/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func runImageCmd() (err error) {

if options.ConfigSource == "" {
switch p.Name() {
case "aws", "azure", "digital-ocean", "gcp", "hcloud":
case "aws", "azure", "digital-ocean", "gcp", "hcloud", "upcloud":
options.ConfigSource = constants.ConfigNone
case "vmware":
options.ConfigSource = constants.ConfigGuestInfo
Expand Down Expand Up @@ -156,6 +156,19 @@ func finalize(platform runtime.Platform, img, arch string) (err error) {
if err = tar(fmt.Sprintf("openstack-%s.tar.gz", arch), file, dir); err != nil {
return err
}
case "upcloud":
file = filepath.Join(outputArg, fmt.Sprintf("upcloud-%s.raw", arch))

err = os.Rename(img, file)
if err != nil {
return err
}

log.Println("compressing image")

if err = xz(file); err != nil {
return err
}
case "vmware":
if err = ova.CreateOVAFromRAW(name, img, outputArg, arch); err != nil {
return err
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/metal"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/openstack"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/packet"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/upcloud"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/vmware"
"github.com/talos-systems/talos/pkg/machinery/constants"
)
Expand Down Expand Up @@ -70,6 +71,8 @@ func newPlatform(platform string) (p runtime.Platform, err error) {
p = &openstack.Openstack{}
case "packet":
p = &packet.Packet{}
case "upcloud":
p = &upcloud.UpCloud{}
case "vmware":
p = &vmware.VMware{}
default:
Expand Down
204 changes: 204 additions & 0 deletions internal/app/machined/pkg/runtime/v1alpha1/platform/upcloud/upcloud.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
// 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 upcloud

import (
"context"
"encoding/json"
"fmt"
"log"
"net"

"github.com/talos-systems/go-procfs/procfs"

"github.com/talos-systems/talos/internal/app/machined/pkg/runtime"
"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/errors"
"github.com/talos-systems/talos/pkg/download"
"github.com/talos-systems/talos/pkg/machinery/config"
"github.com/talos-systems/talos/pkg/machinery/config/configloader"
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
)

const (
// UpCloudMetadataEndpoint is the local UpCloud endpoint.
UpCloudMetadataEndpoint = "http://169.254.169.254/metadata/v1.json"

// UpCloudExternalIPEndpoint is the local UpCloud endpoint for the external IP.
UpCloudExternalIPEndpoint = "http://169.254.169.254/metadata/v1/network/interfaces/1/ip_addresses/1/address"

// UpCloudHostnameEndpoint is the local UpCloud endpoint for the hostname.
UpCloudHostnameEndpoint = "http://169.254.169.254/metadata/v1/hostname"

// UpCloudUserDataEndpoint is the local UpCloud endpoint for the config.
UpCloudUserDataEndpoint = "http://169.254.169.254/metadata/v1/user_data"
)

// MetaData represents a metadata Upcloud interface.
type MetaData struct {
Hostname string `json:"hostname,omitempty"`
InstanceID string `json:"instance_id,omitempty"`
PublicKeys []string `json:"public_keys,omitempty"`
Region string `json:"region,omitempty"`

Network struct {
Interfaces []struct {
Index int `json:"index,omitempty"`
IPAddresses []struct {
Address string `json:"address,omitempty"`
DHCP bool `json:"dhcp,omitempty"`
DNS []string `json:"dns,omitempty"`
Family string `json:"family,omitempty"`
Floating bool `json:"floating,omitempty"`
Gateway string `json:"gateway,omitempty"`
Network string `json:"network,omitempty"`
} `json:"ip_addresses,omitempty"`
MAC string `json:"mac,omitempty"`
NetworkType string `json:"type,omitempty"`
NetworkID string `json:"network_id,omitempty"`
} `json:"interfaces,omitempty"`
DNS []string `json:"dns,omitempty"`
} `json:"network,omitempty"`
}

// UpCloud is the concrete type that implements the runtime.Platform interface.
type UpCloud struct{}

// Name implements the runtime.Platform interface.
func (u *UpCloud) Name() string {
return "upcloud"
}

// ConfigurationNetwork implements the network configuration interface.
//nolint:gocyclo
func (u *UpCloud) ConfigurationNetwork(metadataConfig []byte, confProvider config.Provider) (config.Provider, error) {
var machineConfig *v1alpha1.Config

machineConfig, ok := confProvider.(*v1alpha1.Config)
if !ok {
return nil, fmt.Errorf("unable to determine machine config type")
}

meta := &MetaData{}
if err := json.Unmarshal(metadataConfig, meta); err != nil {
return nil, err
}

if machineConfig.MachineConfig == nil {
machineConfig.MachineConfig = &v1alpha1.MachineConfig{}
}

if machineConfig.MachineConfig.MachineNetwork == nil {
machineConfig.MachineConfig.MachineNetwork = &v1alpha1.NetworkConfig{}
}

if machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces == nil {
for _, addr := range meta.Network.Interfaces {
if addr.Index <= 0 { // protect from negative interface name
continue
}

iface := &v1alpha1.Device{
DeviceInterface: fmt.Sprintf("eth%d", addr.Index-1),
}

for _, ip := range addr.IPAddresses {
if ip.DHCP && ip.Family == "IPv4" {
iface.DeviceDHCP = true
}

if !ip.DHCP {
if ip.Floating {
iface.DeviceAddresses = append(iface.DeviceAddresses, ip.Network)
} else {
iface.DeviceAddresses = append(iface.DeviceAddresses, ip.Address)

if ip.Gateway != "" {
iface.DeviceRoutes = append(iface.DeviceRoutes, &v1alpha1.Route{
RouteNetwork: ip.Network,
RouteGateway: ip.Gateway,
RouteMetric: 1024,
})
}
}
}
}

machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces = append(machineConfig.MachineConfig.MachineNetwork.NetworkInterfaces, iface)
}
}

return confProvider, nil
}

// Configuration implements the runtime.Platform interface.
func (u *UpCloud) Configuration(ctx context.Context) ([]byte, error) {
log.Printf("fetching UpCloud instance config from: %q ", UpCloudMetadataEndpoint)

metaConfigDl, err := download.Download(ctx, UpCloudMetadataEndpoint)
if err != nil {
return nil, fmt.Errorf("failed to fetch network config from metadata service")
}

log.Printf("fetching machine config from: %q", UpCloudUserDataEndpoint)

machineConfigDl, err := download.Download(ctx, UpCloudUserDataEndpoint,
download.WithErrorOnNotFound(errors.ErrNoConfigSource),
download.WithErrorOnEmptyResponse(errors.ErrNoConfigSource))
if err != nil {
return nil, err
}

confProvider, err := configloader.NewFromBytes(machineConfigDl)
if err != nil {
return nil, err
}

confProvider, err = u.ConfigurationNetwork(metaConfigDl, confProvider)
if err != nil {
return nil, err
}

return confProvider.Bytes()
}

// Mode implements the runtime.Platform interface.
func (u *UpCloud) Mode() runtime.Mode {
return runtime.ModeCloud
}

// Hostname implements the runtime.Platform interface.
func (u *UpCloud) Hostname(ctx context.Context) (hostname []byte, err error) {
log.Printf("fetching hostname from: %q", UpCloudHostnameEndpoint)

host, err := download.Download(ctx, UpCloudHostnameEndpoint,
download.WithErrorOnNotFound(errors.ErrNoHostname),
download.WithErrorOnEmptyResponse(errors.ErrNoHostname))
if err != nil {
return nil, err
}

return host, nil
}

// ExternalIPs implements the runtime.Platform interface.
func (u *UpCloud) ExternalIPs(ctx context.Context) (addrs []net.IP, err error) {
log.Printf("fetching external IP from: %q", UpCloudExternalIPEndpoint)

exIP, err := download.Download(ctx, UpCloudExternalIPEndpoint,
download.WithErrorOnNotFound(errors.ErrNoExternalIPs),
download.WithErrorOnEmptyResponse(errors.ErrNoExternalIPs))
if err != nil {
return addrs, err
}

addrs = append(addrs, net.ParseIP(string(exIP)))

return addrs, err
}

// KernelArgs implements the runtime.Platform interface.
func (u *UpCloud) KernelArgs() procfs.Parameters {
return []*procfs.Parameter{}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// 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 upcloud_test

import (
"testing"

"github.com/stretchr/testify/suite"

"github.com/talos-systems/talos/internal/app/machined/pkg/runtime/v1alpha1/platform/upcloud"
"github.com/talos-systems/talos/pkg/machinery/config/types/v1alpha1"
)

type ConfigSuite struct {
suite.Suite
}

func (suite *ConfigSuite) TestNetworkConfig() {
cfg := []byte(`{
"cloud_name": "upcloud",
"instance_id": "00123456-1111-2222-3333-123456789012",
"hostname": "talos",
"network": {
"interfaces": [
{
"index": 1,
"ip_addresses": [
{
"address": "185.70.197.2",
"dhcp": true,
"dns": [
"94.237.127.9",
"94.237.40.9"
],
"family": "IPv4",
"floating": false,
"gateway": "185.70.196.1",
"network": "185.70.196.0/22"
},
{
"address": "185.70.197.3",
"dhcp": false,
"dns": null,
"family": "IPv4",
"floating": true,
"gateway": "",
"network": "185.70.197.3/32"
}
],
"mac": "5e:bf:5e:02:28:07",
"network_id": "035ef879-1111-2222-3333-123456789012",
"type": "public"
},
{
"index": 2,
"ip_addresses": [
{
"address": "10.11.0.2",
"dhcp": true,
"dns": null,
"family": "IPv4",
"floating": false,
"gateway": "10.11.0.1",
"network": "10.11.0.0/22"
}
],
"mac": "5e:bf:5e:02:cd:e0",
"network_id": "031c9f9c-1111-2222-3333-123456789012",
"type": "utility"
},
{
"index": 3,
"ip_addresses": [
{
"address": "2a04:3544:8000:1000:0000:1111:2222:3333",
"dhcp": true,
"dns": [
"2a04:3540:53::1",
"2a04:3544:53::1"
],
"family": "IPv6",
"floating": false,
"gateway": "2a04:3544:8000:1000::1",
"network": "2a04:3544:8000:1000::/64"
}
],
"mac": "5e:bf:5e:02:78:a4",
"network_id": "03b326a2-1111-2222-3333-123456789012",
"type": "public"
}
],
"dns": [
"94.237.127.9",
"94.237.40.9"
]
},
"storage": {},
"tags": [],
"user_data": "",
"vendor_data": ""
}`)

p := &upcloud.UpCloud{}

defaultMachineConfig := &v1alpha1.Config{}

machineConfig := &v1alpha1.Config{
MachineConfig: &v1alpha1.MachineConfig{
MachineNetwork: &v1alpha1.NetworkConfig{
NetworkInterfaces: []*v1alpha1.Device{
{
DeviceInterface: "eth0",
DeviceAddresses: []string{"185.70.197.3/32"},
DeviceDHCP: true,
},
{
DeviceInterface: "eth1",
DeviceDHCP: true,
},
{
DeviceInterface: "eth2",
DeviceDHCP: false,
},
},
},
},
}

result, err := p.ConfigurationNetwork(cfg, defaultMachineConfig)

suite.Require().NoError(err)
suite.Assert().Equal(machineConfig, result)
}

func TestConfigSuite(t *testing.T) {
suite.Run(t, new(ConfigSuite))
}

0 comments on commit f156ab1

Please sign in to comment.