Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement support for custom internal IP mapping via HCLOUD_INSTANCES… #696

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion hcloud/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func (c *cloud) Instances() (cloudprovider.Instances, bool) {
}

func (c *cloud) InstancesV2() (cloudprovider.InstancesV2, bool) {
return newInstances(c.client, c.robotClient, c.recorder, c.cfg.Instance.AddressFamily, c.networkID), true
return newInstances(c.client, c.robotClient, c.recorder, c.cfg.Instance.AddressFamily, c.networkID, c.cfg.Instance.InternalIpMap), true
}

func (c *cloud) Zones() (cloudprovider.Zones, bool) {
Expand Down
38 changes: 28 additions & 10 deletions hcloud/instances.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,16 @@
recorder record.EventRecorder
addressFamily config.AddressFamily
networkID int64
internalIpMap map[string]string

Check failure on line 46 in hcloud/instances.go

View workflow job for this annotation

GitHub Actions / Lint

var-naming: struct field internalIpMap should be internalIPMap (revive)
}

var (
errServerNotFound = errors.New("server not found")
errMissingRobotClient = errors.New("no robot client configured, make sure to enable Robot support in the configuration")
)

func newInstances(client *hcloud.Client, robotClient robot.Client, recorder record.EventRecorder, addressFamily config.AddressFamily, networkID int64) *instances {
return &instances{client, robotClient, recorder, addressFamily, networkID}
func newInstances(client *hcloud.Client, robotClient robot.Client, recorder record.EventRecorder, addressFamily config.AddressFamily, networkID int64, internalIpMap map[string]string) *instances {

Check failure on line 54 in hcloud/instances.go

View workflow job for this annotation

GitHub Actions / Lint

var-naming: func parameter internalIpMap should be internalIPMap (revive)
return &instances{client, robotClient, recorder, addressFamily, networkID, internalIpMap}
}

// lookupServer attempts to locate the corresponding [*hcloud.Server] or [*hrobotmodels.Server] for a given [*corev1.Node].
Expand Down Expand Up @@ -178,15 +179,15 @@
op, node.Name, errServerNotFound)
}

metadata, err := server.Metadata(i.addressFamily, i.networkID)
metadata, err := server.Metadata(i.addressFamily, i.networkID, i.internalIpMap)
if err != nil {
return nil, fmt.Errorf("%s: %w", op, err)
}

return metadata, nil
}

func hcloudNodeAddresses(addressFamily config.AddressFamily, networkID int64, server *hcloud.Server) []corev1.NodeAddress {
func hcloudNodeAddresses(addressFamily config.AddressFamily, networkID int64, server *hcloud.Server, internalIpMap map[string]string) []corev1.NodeAddress {

Check failure on line 190 in hcloud/instances.go

View workflow job for this annotation

GitHub Actions / Lint

var-naming: func parameter internalIpMap should be internalIPMap (revive)
var addresses []corev1.NodeAddress
addresses = append(
addresses,
Expand Down Expand Up @@ -226,10 +227,19 @@
}
}
}

// Add internal IP from the map if a mapping exists
if internalIP, ok := internalIpMap[server.Name]; ok {
addresses = append(
addresses,
corev1.NodeAddress{Type: corev1.NodeInternalIP, Address: internalIP},
)
}

return addresses
}

func robotNodeAddresses(addressFamily config.AddressFamily, server *hrobotmodels.Server) []corev1.NodeAddress {
func robotNodeAddresses(addressFamily config.AddressFamily, server *hrobotmodels.Server, internalIpMap map[string]string) []corev1.NodeAddress {

Check failure on line 242 in hcloud/instances.go

View workflow job for this annotation

GitHub Actions / Lint

var-naming: func parameter internalIpMap should be internalIPMap (revive)
var addresses []corev1.NodeAddress
addresses = append(
addresses,
Expand All @@ -254,12 +264,20 @@
)
}

// Add internal IP from the map if a mapping exists
if internalIP, ok := internalIpMap[server.Name]; ok {
addresses = append(
addresses,
corev1.NodeAddress{Type: corev1.NodeInternalIP, Address: internalIP},
)
}

return addresses
}

type genericServer interface {
IsShutdown() (bool, error)
Metadata(addressFamily config.AddressFamily, networkID int64) (*cloudprovider.InstanceMetadata, error)
Metadata(addressFamily config.AddressFamily, networkID int64, internalIpMap map[string]string) (*cloudprovider.InstanceMetadata, error)

Check failure on line 280 in hcloud/instances.go

View workflow job for this annotation

GitHub Actions / Lint

var-naming: interface method parameter internalIpMap should be internalIPMap (revive)
}

type hcloudServer struct {
Expand All @@ -270,11 +288,11 @@
return s.Status == hcloud.ServerStatusOff, nil
}

func (s hcloudServer) Metadata(addressFamily config.AddressFamily, networkID int64) (*cloudprovider.InstanceMetadata, error) {
func (s hcloudServer) Metadata(addressFamily config.AddressFamily, networkID int64, internalIpMap map[string]string) (*cloudprovider.InstanceMetadata, error) {

Check failure on line 291 in hcloud/instances.go

View workflow job for this annotation

GitHub Actions / Lint

var-naming: method parameter internalIpMap should be internalIPMap (revive)
return &cloudprovider.InstanceMetadata{
ProviderID: providerid.FromCloudServerID(s.ID),
InstanceType: s.ServerType.Name,
NodeAddresses: hcloudNodeAddresses(addressFamily, networkID, s.Server),
NodeAddresses: hcloudNodeAddresses(addressFamily, networkID, s.Server, internalIpMap),
Zone: s.Datacenter.Name,
Region: s.Datacenter.Location.Name,
AdditionalLabels: map[string]string{
Expand All @@ -299,11 +317,11 @@
return resetStatus.OperatingStatus == "shut off", nil
}

func (s robotServer) Metadata(addressFamily config.AddressFamily, _ int64) (*cloudprovider.InstanceMetadata, error) {
func (s robotServer) Metadata(addressFamily config.AddressFamily, _ int64, internalIpMap map[string]string) (*cloudprovider.InstanceMetadata, error) {

Check failure on line 320 in hcloud/instances.go

View workflow job for this annotation

GitHub Actions / Lint

var-naming: method parameter internalIpMap should be internalIPMap (revive)
return &cloudprovider.InstanceMetadata{
ProviderID: providerid.FromRobotServerNumber(s.ServerNumber),
InstanceType: getInstanceTypeOfRobotServer(s.Server),
NodeAddresses: robotNodeAddresses(addressFamily, s.Server),
NodeAddresses: robotNodeAddresses(addressFamily, s.Server, internalIpMap),
Zone: getZoneOfRobotServer(s.Server),
Region: getRegionOfRobotServer(s.Server),
AdditionalLabels: map[string]string{
Expand Down
12 changes: 6 additions & 6 deletions hcloud/instances_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func TestInstances_InstanceExists(t *testing.T) {
})
})

instances := newInstances(env.Client, env.RobotClient, env.Recorder, config.AddressFamilyIPv4, 0)
instances := newInstances(env.Client, env.RobotClient, env.Recorder, config.AddressFamilyIPv4, 0, map[string]string{})

tests := []struct {
name string
Expand Down Expand Up @@ -209,7 +209,7 @@ func TestInstances_InstanceShutdown(t *testing.T) {
})
})

instances := newInstances(env.Client, env.RobotClient, env.Recorder, config.AddressFamilyIPv4, 0)
instances := newInstances(env.Client, env.RobotClient, env.Recorder, config.AddressFamilyIPv4, 0, map[string]string{})
env.Mux.HandleFunc("/robot/server/3", func(w http.ResponseWriter, _ *http.Request) {
json.NewEncoder(w).Encode(hrobotmodels.ServerResponse{
Server: hrobotmodels.Server{
Expand Down Expand Up @@ -343,7 +343,7 @@ func TestInstances_InstanceMetadata(t *testing.T) {
})
})

instances := newInstances(env.Client, env.RobotClient, env.Recorder, config.AddressFamilyIPv4, 0)
instances := newInstances(env.Client, env.RobotClient, env.Recorder, config.AddressFamilyIPv4, 0, map[string]string{})

metadata, err := instances.InstanceMetadata(context.TODO(), &corev1.Node{
Spec: corev1.NodeSpec{ProviderID: "hcloud://1"},
Expand Down Expand Up @@ -387,7 +387,7 @@ func TestInstances_InstanceMetadataRobotServer(t *testing.T) {
})
})

instances := newInstances(env.Client, env.RobotClient, env.Recorder, config.AddressFamilyIPv4, 0)
instances := newInstances(env.Client, env.RobotClient, env.Recorder, config.AddressFamilyIPv4, 0, map[string]string{})

metadata, err := instances.InstanceMetadata(context.TODO(), &corev1.Node{
ObjectMeta: metav1.ObjectMeta{
Expand Down Expand Up @@ -581,7 +581,7 @@ func TestNodeAddresses(t *testing.T) {

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
addresses := hcloudNodeAddresses(test.addressFamily, test.privateNetwork, test.server)
addresses := hcloudNodeAddresses(test.addressFamily, test.privateNetwork, test.server, map[string]string{})

if !reflect.DeepEqual(addresses, test.expected) {
t.Fatalf("Expected addresses %+v but got %+v", test.expected, addresses)
Expand Down Expand Up @@ -642,7 +642,7 @@ func TestNodeAddressesRobotServer(t *testing.T) {

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
addresses := robotNodeAddresses(test.addressFamily, test.server)
addresses := robotNodeAddresses(test.addressFamily, test.server, map[string]string{})

if !reflect.DeepEqual(addresses, test.expected) {
t.Fatalf("%s: expected addresses %+v but got %+v", test.name, test.expected, addresses)
Expand Down
27 changes: 27 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"fmt"
"os"
"strconv"
"strings"
"time"

"github.com/hetznercloud/hcloud-go/v2/hcloud/exp/kit/envutil"
Expand All @@ -23,6 +24,7 @@
robotRateLimitWaitTime = "ROBOT_RATE_LIMIT_WAIT_TIME"

hcloudInstancesAddressFamily = "HCLOUD_INSTANCES_ADDRESS_FAMILY"
hcloudInstancesInternalIpMap = "HCLOUD_INSTANCES_INTERNAL_IP_MAP"

Check failure on line 27 in internal/config/config.go

View workflow job for this annotation

GitHub Actions / Lint

var-naming: const hcloudInstancesInternalIpMap should be hcloudInstancesInternalIPMap (revive)

// Disable the "master/server is attached to the network" check against the metadata service.
hcloudNetworkDisableAttachedCheck = "HCLOUD_NETWORK_DISABLE_ATTACHED_CHECK"
Expand Down Expand Up @@ -68,6 +70,7 @@

type InstanceConfiguration struct {
AddressFamily AddressFamily
InternalIpMap map[string]string

Check failure on line 73 in internal/config/config.go

View workflow job for this annotation

GitHub Actions / Lint

var-naming: struct field InternalIpMap should be InternalIPMap (revive)
}

type LoadBalancerConfiguration struct {
Expand Down Expand Up @@ -154,6 +157,11 @@
cfg.Instance.AddressFamily = AddressFamilyIPv4
}

cfg.Instance.InternalIpMap, err = parseInternalIpMap(os.Getenv(hcloudInstancesInternalIpMap))
if err != nil {
errs = append(errs, err)
}

cfg.LoadBalancer.Enabled, err = getEnvBool(hcloudLoadBalancersEnabled, true)
if err != nil {
errs = append(errs, err)
Expand Down Expand Up @@ -263,3 +271,22 @@

return b, nil
}

// parseInternalIpMap parses the HCLOUD_INSTANCES_INTERNAL_IP_MAP environment variable into a map.
func parseInternalIpMap(value string) (map[string]string, error) {

Check failure on line 276 in internal/config/config.go

View workflow job for this annotation

GitHub Actions / Lint

var-naming: func parseInternalIpMap should be parseInternalIPMap (revive)
ipMap := make(map[string]string)
if value == "" {
return ipMap, nil
}

pairs := strings.Split(value, ",")
for _, pair := range pairs {
kv := strings.Split(pair, "=")
if len(kv) != 2 {
return nil, fmt.Errorf("invalid format for %s: %s", hcloudInstancesInternalIpMap, pair)
}
ipMap[kv[0]] = kv[1]
}

return ipMap, nil
}
32 changes: 16 additions & 16 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func TestRead(t *testing.T) {
want: HCCMConfiguration{
Robot: RobotConfiguration{CacheTimeout: 5 * time.Minute},
Metrics: MetricsConfiguration{Enabled: true, Address: ":8233"},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4, InternalIpMap: map[string]string{}},
LoadBalancer: LoadBalancerConfiguration{Enabled: true},
},
wantErr: nil,
Expand All @@ -40,7 +40,7 @@ func TestRead(t *testing.T) {
HCloudClient: HCloudClientConfiguration{Token: "jr5g7ZHpPptyhJzZyHw2Pqu4g9gTqDvEceYpngPf79jN_NOT_VALID_dzhepnahq"},
Robot: RobotConfiguration{CacheTimeout: 5 * time.Minute},
Metrics: MetricsConfiguration{Enabled: true, Address: ":8233"},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4, InternalIpMap: map[string]string{}},
Network: NetworkConfiguration{
NameOrID: "foobar",
},
Expand Down Expand Up @@ -112,7 +112,7 @@ failed to read ROBOT_PASSWORD_FILE: open /tmp/hetzner-password: no such file or
},
Robot: RobotConfiguration{CacheTimeout: 5 * time.Minute},
Metrics: MetricsConfiguration{Enabled: true, Address: ":8233"},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4, InternalIpMap: map[string]string{}},
LoadBalancer: LoadBalancerConfiguration{Enabled: true},
},
wantErr: nil,
Expand All @@ -135,7 +135,7 @@ failed to read ROBOT_PASSWORD_FILE: open /tmp/hetzner-password: no such file or
RateLimitWaitTime: 5 * time.Minute,
},
Metrics: MetricsConfiguration{Enabled: true, Address: ":8233"},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4, InternalIpMap: map[string]string{}},
LoadBalancer: LoadBalancerConfiguration{Enabled: true},
},
wantErr: nil,
Expand All @@ -148,7 +148,7 @@ failed to read ROBOT_PASSWORD_FILE: open /tmp/hetzner-password: no such file or
want: HCCMConfiguration{
Robot: RobotConfiguration{CacheTimeout: 5 * time.Minute},
Metrics: MetricsConfiguration{Enabled: true, Address: ":8233"},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv6},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv6, InternalIpMap: map[string]string{}},
LoadBalancer: LoadBalancerConfiguration{Enabled: true},
},
wantErr: nil,
Expand All @@ -162,7 +162,7 @@ failed to read ROBOT_PASSWORD_FILE: open /tmp/hetzner-password: no such file or
want: HCCMConfiguration{
Robot: RobotConfiguration{CacheTimeout: 5 * time.Minute},
Metrics: MetricsConfiguration{Enabled: true, Address: ":8233"},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4, InternalIpMap: map[string]string{}},
LoadBalancer: LoadBalancerConfiguration{Enabled: true},
Network: NetworkConfiguration{
NameOrID: "foobar",
Expand All @@ -181,7 +181,7 @@ failed to read ROBOT_PASSWORD_FILE: open /tmp/hetzner-password: no such file or
want: HCCMConfiguration{
Robot: RobotConfiguration{CacheTimeout: 5 * time.Minute},
Metrics: MetricsConfiguration{Enabled: true, Address: ":8233"},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4, InternalIpMap: map[string]string{}},
LoadBalancer: LoadBalancerConfiguration{Enabled: true},
Network: NetworkConfiguration{
NameOrID: "foobar",
Expand All @@ -203,7 +203,7 @@ failed to read ROBOT_PASSWORD_FILE: open /tmp/hetzner-password: no such file or
want: HCCMConfiguration{
Robot: RobotConfiguration{CacheTimeout: 5 * time.Minute},
Metrics: MetricsConfiguration{Enabled: true, Address: ":8233"},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4, InternalIpMap: map[string]string{}},
LoadBalancer: LoadBalancerConfiguration{
Enabled: false,
Location: "nbg1",
Expand Down Expand Up @@ -289,7 +289,7 @@ func TestHCCMConfiguration_Validate(t *testing.T) {
name: "minimal",
fields: fields{
HCloudClient: HCloudClientConfiguration{Token: "jr5g7ZHpPptyhJzZyHw2Pqu4g9gTqDvEceYpngPf79jN_NOT_VALID_dzhepnahq"},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4, InternalIpMap: map[string]string{}},
},
wantErr: nil,
},
Expand All @@ -299,7 +299,7 @@ func TestHCCMConfiguration_Validate(t *testing.T) {
fields: fields{
HCloudClient: HCloudClientConfiguration{Token: "jr5g7ZHpPptyhJzZyHw2Pqu4g9gTqDvEceYpngPf79jN_NOT_VALID_dzhepnahq"},
Metrics: MetricsConfiguration{Enabled: true, Address: ":8233"},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4, InternalIpMap: map[string]string{}},
Network: NetworkConfiguration{
NameOrID: "foobar",
},
Expand All @@ -313,31 +313,31 @@ func TestHCCMConfiguration_Validate(t *testing.T) {
name: "token missing",
fields: fields{
HCloudClient: HCloudClientConfiguration{Token: ""},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4, InternalIpMap: map[string]string{}},
},
wantErr: errors.New("environment variable \"HCLOUD_TOKEN\" is required"),
},
{
name: "token invalid length",
fields: fields{
HCloudClient: HCloudClientConfiguration{Token: "abc"},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4, InternalIpMap: map[string]string{}},
},
wantErr: errors.New("entered token is invalid (must be exactly 64 characters long)"),
},
{
name: "address family invalid",
fields: fields{
HCloudClient: HCloudClientConfiguration{Token: "jr5g7ZHpPptyhJzZyHw2Pqu4g9gTqDvEceYpngPf79jN_NOT_VALID_dzhepnahq"},
Instance: InstanceConfiguration{AddressFamily: AddressFamily("foobar")},
Instance: InstanceConfiguration{AddressFamily: AddressFamily("foobar"), InternalIpMap: map[string]string{}},
},
wantErr: errors.New("invalid value for \"HCLOUD_INSTANCES_ADDRESS_FAMILY\", expect one of: ipv4,ipv6,dualstack"),
},
{
name: "LB location and network zone set",
fields: fields{
HCloudClient: HCloudClientConfiguration{Token: "jr5g7ZHpPptyhJzZyHw2Pqu4g9gTqDvEceYpngPf79jN_NOT_VALID_dzhepnahq"},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4, InternalIpMap: map[string]string{}},
LoadBalancer: LoadBalancerConfiguration{
Location: "nbg1",
NetworkZone: "eu-central",
Expand All @@ -349,7 +349,7 @@ func TestHCCMConfiguration_Validate(t *testing.T) {
name: "robot enabled but missing credentials",
fields: fields{
HCloudClient: HCloudClientConfiguration{Token: "jr5g7ZHpPptyhJzZyHw2Pqu4g9gTqDvEceYpngPf79jN_NOT_VALID_dzhepnahq"},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4, InternalIpMap: map[string]string{}},

Robot: RobotConfiguration{
Enabled: true,
Expand All @@ -363,7 +363,7 @@ environment variable "ROBOT_PASSWORD" is required if Robot support is enabled`),
fields: fields{

HCloudClient: HCloudClientConfiguration{Token: "jr5g7ZHpPptyhJzZyHw2Pqu4g9gTqDvEceYpngPf79jN_NOT_VALID_dzhepnahq"},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4},
Instance: InstanceConfiguration{AddressFamily: AddressFamilyIPv4, InternalIpMap: map[string]string{}},
Route: RouteConfiguration{Enabled: true},
Robot: RobotConfiguration{
Enabled: true,
Expand Down
Loading