Skip to content

Commit

Permalink
support providing a configuration file for ip binding
Browse files Browse the repository at this point in the history
  • Loading branch information
ajones committed Sep 22, 2021
1 parent 6e7bc29 commit be5c3d0
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 10 deletions.
1 change: 1 addition & 0 deletions cmd/kubefwd/kubefwd.go
Expand Up @@ -48,6 +48,7 @@ func newRootCmd() *cobra.Command {
" kubefwd svc -n default -l \"app in (ws, api)\"\n" +
" kubefwd svc -n default -n the-project\n" +
" kubefwd svc -n the-project -m 80:8080 -m 443:1443\n" +
" kubefwd svc -n the-project -s path/to/conf.yml\n" +
" kubefwd svc --all-namespaces",

Long: globalUsage,
Expand Down
4 changes: 4 additions & 0 deletions cmd/kubefwd/services/services.go
Expand Up @@ -56,6 +56,7 @@ var verbose bool
var domain string
var mappings []string
var isAllNs bool
var svcConfigurationPath string

func init() {
// override error output from k8s.io/apimachinery/pkg/util/runtime
Expand All @@ -73,6 +74,7 @@ func init() {
Cmd.Flags().StringVarP(&domain, "domain", "d", "", "Append a pseudo domain name to generated host names.")
Cmd.Flags().StringSliceVarP(&mappings, "mapping", "m", []string{}, "Specify a port mapping. Specify multiple mapping by duplicating this argument.")
Cmd.Flags().BoolVarP(&isAllNs, "all-namespaces", "A", false, "Enable --all-namespaces option like kubectl.")
Cmd.Flags().StringVarP(&svcConfigurationPath, "fwd-conf", "z", "", "Define a forward configuration map")

}

Expand All @@ -88,6 +90,7 @@ var Cmd = &cobra.Command{
" kubefwd svc -n default -d internal.example.com\n" +
" kubefwd svc -n the-project -x prod-cluster\n" +
" kubefwd svc -n the-project -m 80:8080 -m 443:1443\n" +
" kubefwd svc -n the-project -z path/to/conf.yml\n" +
" kubefwd svc --all-namespaces",
Run: runCmd,
}
Expand Down Expand Up @@ -439,6 +442,7 @@ func (opts *NamespaceOpts) AddServiceHandler(obj interface{}) {
SyncDebouncer: debounce.New(5 * time.Second),
DoneChannel: make(chan struct{}),
PortMap: opts.ParsePortMap(mappings),
ServiceConfigPath: svcConfigurationPath,
}

// Add the service to the catalog of services being forwarded
Expand Down
6 changes: 6 additions & 0 deletions example.fwdconf.yml
@@ -0,0 +1,6 @@
baseIP: 127.1.27.1
serviceConfigurations:
- serviceName: service-name
finalOctet: 11 # 127.1.27.11
- serviceName: other-service-name
finalOctet: 99 # 127.1.27.99
1 change: 1 addition & 0 deletions go.mod
Expand Up @@ -12,6 +12,7 @@ require (
github.com/spf13/cobra v1.1.1
github.com/txn2/txeh v1.2.1
golang.org/x/sys v0.0.0-20201112073958-5cba982894dd
gopkg.in/yaml.v2 v2.2.8 // indirect
k8s.io/api v0.20.4
k8s.io/apimachinery v0.20.4
k8s.io/cli-runtime v0.20.4
Expand Down
104 changes: 101 additions & 3 deletions pkg/fwdIp/fwdIp.go
Expand Up @@ -3,7 +3,13 @@ package fwdIp
import (
"fmt"
"net"
"os"
"strconv"
"strings"
"sync"

log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
)

// Registry is a structure to create and hold all of the
Expand All @@ -14,7 +20,20 @@ type Registry struct {
reg map[string]net.IP
}

type ForwardConfiguration struct {
BaseIP string `yaml:"baseIP"`
ServiceConfigurations []ServiceConfiguration `yaml:"serviceConfigurations"`
}

type ServiceConfiguration struct {
ServiceName string `yaml:"serviceName"`
FinalOctet int `yaml:"finalOctet"`
FailOnOverlap bool `yaml:"failOnOverlap"`
}

var ipRegistry *Registry
var forwardConfiguration *ForwardConfiguration
var defaultConfiguration = &ForwardConfiguration{BaseIP: "127.1.27.1"}

// Init
func init() {
Expand All @@ -26,7 +45,7 @@ func init() {
}
}

func GetIp(svcName string, podName string, clusterN int, NamespaceN int) (net.IP, error) {
func GetIp(svcName string, podName string, clusterN int, NamespaceN int, forwardConfigurationPath string) (net.IP, error) {
ipRegistry.mutex.Lock()
defer ipRegistry.mutex.Unlock()

Expand All @@ -36,18 +55,97 @@ func GetIp(svcName string, podName string, clusterN int, NamespaceN int) (net.IP
return ip, nil
}

return determineIP(regKey, svcName, podName, clusterN, NamespaceN, forwardConfigurationPath), nil
}

func determineIP(regKey string, svcName string, podName string, clusterN int, NamespaceN int, forwardConfigurationPath string) net.IP {
baseIP := getBaseIP(forwardConfigurationPath)

// if a configuration exists use it
svcConf := getConfigurationForService(svcName, forwardConfigurationPath)
if svcConf != nil {
ip := net.IP{baseIP[0], baseIP[1], baseIP[2], byte(svcConf.FinalOctet)}.To4()
ipRegistry.reg[regKey] = ip
return ip
}

// fall back to previous implementation
if ipRegistry.inc[clusterN] == nil {
ipRegistry.inc[clusterN] = map[int]int{0: 0}
}

// @TODO check ranges
ip := net.IP{127, 1, 27, 1}.To4()
ip := net.IP{baseIP[0], baseIP[1], baseIP[2], baseIP[3]}.To4()
ip[1] += byte(clusterN)
ip[2] += byte(NamespaceN)
ip[3] += byte(ipRegistry.inc[clusterN][NamespaceN])

ipRegistry.inc[clusterN][NamespaceN]++
ipRegistry.reg[regKey] = ip

return ip, nil
return ip
}

func getBaseIP(forwardConfigurationPath string) []byte {
fwdCfg := getForwardConfiguration(forwardConfigurationPath)
ipParts := strings.Split(fwdCfg.BaseIP, ".")

octet0, err := strconv.Atoi(ipParts[0])
if err != nil {
panic("Unable to parse BaseIP octet 0")
}
octet1, err := strconv.Atoi(ipParts[1])
if err != nil {
panic("Unable to parse BaseIP octet 1")
}
octet2, err := strconv.Atoi(ipParts[2])
if err != nil {
panic("Unable to parse BaseIP octet 2")
}
octet3, err := strconv.Atoi(ipParts[3])
if err != nil {
panic("Unable to parse BaseIP octet 3")
}
return []byte{byte(octet0), byte(octet1), byte(octet2), byte(octet3)}
}

func getConfigurationForService(serviceName string, forwardConfigurationPath string) *ServiceConfiguration {
fwdCfg := getForwardConfiguration(forwardConfigurationPath)
for _, c := range fwdCfg.ServiceConfigurations {
if c.ServiceName == serviceName {
return &c
}
}
return nil
}

func getForwardConfiguration(forwardConfigurationPath string) *ForwardConfiguration {
if forwardConfiguration != nil {
return forwardConfiguration
}

if forwardConfigurationPath == "" {
forwardConfiguration = defaultConfiguration
return forwardConfiguration
}

dat, err := os.ReadFile(forwardConfigurationPath)
if err != nil {
// fall back to existing kubefwd base
log.Error(fmt.Sprintf("ForwardConfiguration read error %s", err))
forwardConfiguration = defaultConfiguration
return forwardConfiguration
}

conf := &ForwardConfiguration{}
err = yaml.Unmarshal(dat, conf)
if err != nil {
// fall back to existing kubefwd base
log.Error(fmt.Sprintf("ForwardConfiguration parse error %s", err))
forwardConfiguration = defaultConfiguration
return forwardConfiguration
}

forwardConfiguration = conf
return forwardConfiguration
}
4 changes: 2 additions & 2 deletions pkg/fwdnet/fwdnet.go
Expand Up @@ -13,9 +13,9 @@ import (

// ReadyInterface prepares a local IP address on
// the loopback interface.
func ReadyInterface(svcName string, podName string, clusterN int, namespaceN int, port string) (net.IP, error) {
func ReadyInterface(svcName string, podName string, clusterN int, namespaceN int, port string, forwardConfigurationPath string) (net.IP, error) {

ip, _ := fwdIp.GetIp(svcName, podName, clusterN, namespaceN)
ip, _ := fwdIp.GetIp(svcName, podName, clusterN, namespaceN, forwardConfigurationPath)

// lo means we are probably on linux and not mac
_, err := net.InterfaceByName("lo")
Expand Down
12 changes: 7 additions & 5 deletions pkg/fwdservice/fwdservice.go
@@ -1,11 +1,11 @@
package fwdservice

import (
"context"
"fmt"
"strconv"
"sync"
"time"
"context"

log "github.com/sirupsen/logrus"
"github.com/txn2/kubefwd/pkg/fwdnet"
Expand Down Expand Up @@ -70,6 +70,8 @@ type ServiceFWD struct {
// key = podName
PortForwards map[string]*fwdport.PortForwardOpts
DoneChannel chan struct{} // After shutdown is complete, this channel will be closed

ServiceConfigPath string
}

/**
Expand Down Expand Up @@ -232,7 +234,7 @@ func (svcFwd *ServiceFWD) LoopPodsToForward(pods []v1.Pod, includePodNameInHost
svcName = pod.Name + "." + svcFwd.Svc.Name
}

localIp, err := fwdnet.ReadyInterface(svcName, pod.Name, svcFwd.ClusterN, svcFwd.NamespaceN, podPort)
localIp, err := fwdnet.ReadyInterface(svcName, pod.Name, svcFwd.ClusterN, svcFwd.NamespaceN, podPort, svcFwd.ServiceConfigPath)
if err != nil {
log.Warnf("WARNING: error readying interface: %s\n", err)
}
Expand Down Expand Up @@ -282,10 +284,10 @@ func (svcFwd *ServiceFWD) LoopPodsToForward(pods []v1.Pod, includePodNameInHost
svcName,
)

log.Printf("Port-Forward: %s %s:%d to pod %s:%s\n",
// 30 chars is a pretty long service name
log.Printf("Port-Forward: %-30s %s to pod %s:%s\n",
fmt.Sprintf("%s:%d", serviceHostName, port.Port),
localIp.String(),
serviceHostName,
port.Port,
pod.Name,
podPort,
)
Expand Down

0 comments on commit be5c3d0

Please sign in to comment.