mirror of
https://github.com/kubenetworks/kubevpn.git
synced 2025-12-24 11:51:13 +08:00
first commit
This commit is contained in:
314
pkg/cfg.go
Normal file
314
pkg/cfg.go
Normal file
@@ -0,0 +1,314 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/ginuerzh/gost"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type baseConfig struct {
|
||||
route
|
||||
Routes []route
|
||||
Debug bool
|
||||
}
|
||||
|
||||
var (
|
||||
defaultCertFile = "cert.pem"
|
||||
defaultKeyFile = "key.pem"
|
||||
)
|
||||
|
||||
// Load the certificate from cert & key files and optional client CA file,
|
||||
// will use the default certificate if the provided info are invalid.
|
||||
func tlsConfig(certFile, keyFile, caFile string) (*tls.Config, error) {
|
||||
if certFile == "" || keyFile == "" {
|
||||
certFile, keyFile = defaultCertFile, defaultKeyFile
|
||||
}
|
||||
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := &tls.Config{Certificates: []tls.Certificate{cert}}
|
||||
|
||||
if pool, _ := loadCA(caFile); pool != nil {
|
||||
cfg.ClientCAs = pool
|
||||
cfg.ClientAuth = tls.RequireAndVerifyClientCert
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func loadCA(caFile string) (cp *x509.CertPool, err error) {
|
||||
if caFile == "" {
|
||||
return
|
||||
}
|
||||
cp = x509.NewCertPool()
|
||||
data, err := ioutil.ReadFile(caFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !cp.AppendCertsFromPEM(data) {
|
||||
return nil, errors.New("AppendCertsFromPEM failed")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseKCPConfig(configFile string) (*gost.KCPConfig, error) {
|
||||
if configFile == "" {
|
||||
return nil, nil
|
||||
}
|
||||
file, err := os.Open(configFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
config := &gost.KCPConfig{}
|
||||
if err = json.NewDecoder(file).Decode(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func parseUsers(authFile string) (users []*url.Userinfo, err error) {
|
||||
if authFile == "" {
|
||||
return
|
||||
}
|
||||
|
||||
file, err := os.Open(authFile)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
s := strings.SplitN(line, " ", 2)
|
||||
if len(s) == 1 {
|
||||
users = append(users, url.User(strings.TrimSpace(s[0])))
|
||||
} else if len(s) == 2 {
|
||||
users = append(users, url.UserPassword(strings.TrimSpace(s[0]), strings.TrimSpace(s[1])))
|
||||
}
|
||||
}
|
||||
|
||||
err = scanner.Err()
|
||||
return
|
||||
}
|
||||
|
||||
func parseAuthenticator(s string) (gost.Authenticator, error) {
|
||||
if s == "" {
|
||||
return nil, nil
|
||||
}
|
||||
f, err := os.Open(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
au := gost.NewLocalAuthenticator(nil)
|
||||
au.Reload(f)
|
||||
|
||||
go gost.PeriodReload(au, s)
|
||||
|
||||
return au, nil
|
||||
}
|
||||
|
||||
func parseIP(s string, port string) (ips []string) {
|
||||
if s == "" {
|
||||
return
|
||||
}
|
||||
if port == "" {
|
||||
port = "8080" // default port
|
||||
}
|
||||
|
||||
file, err := os.Open(s)
|
||||
if err != nil {
|
||||
ss := strings.Split(s, ",")
|
||||
for _, s := range ss {
|
||||
s = strings.TrimSpace(s)
|
||||
if s != "" {
|
||||
// TODO: support IPv6
|
||||
if !strings.Contains(s, ":") {
|
||||
s = s + ":" + port
|
||||
}
|
||||
ips = append(ips, s)
|
||||
}
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(line, ":") {
|
||||
line = line + ":" + port
|
||||
}
|
||||
ips = append(ips, line)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseBypass(s string) *gost.Bypass {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
var matchers []gost.Matcher
|
||||
var reversed bool
|
||||
if strings.HasPrefix(s, "~") {
|
||||
reversed = true
|
||||
s = strings.TrimLeft(s, "~")
|
||||
}
|
||||
|
||||
f, err := os.Open(s)
|
||||
if err != nil {
|
||||
for _, s := range strings.Split(s, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
matchers = append(matchers, gost.NewMatcher(s))
|
||||
}
|
||||
return gost.NewBypass(reversed, matchers...)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
bp := gost.NewBypass(reversed)
|
||||
bp.Reload(f)
|
||||
go gost.PeriodReload(bp, s)
|
||||
|
||||
return bp
|
||||
}
|
||||
|
||||
func parseResolver(cfg string) gost.Resolver {
|
||||
if cfg == "" {
|
||||
return nil
|
||||
}
|
||||
var nss []gost.NameServer
|
||||
|
||||
f, err := os.Open(cfg)
|
||||
if err != nil {
|
||||
for _, s := range strings.Split(cfg, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(s, "https") {
|
||||
p := "https"
|
||||
u, _ := url.Parse(s)
|
||||
if u == nil || u.Scheme == "" {
|
||||
continue
|
||||
}
|
||||
if u.Scheme == "https-chain" {
|
||||
p = u.Scheme
|
||||
}
|
||||
ns := gost.NameServer{
|
||||
Addr: s,
|
||||
Protocol: p,
|
||||
}
|
||||
nss = append(nss, ns)
|
||||
continue
|
||||
}
|
||||
|
||||
ss := strings.Split(s, "/")
|
||||
if len(ss) == 1 {
|
||||
ns := gost.NameServer{
|
||||
Addr: ss[0],
|
||||
}
|
||||
nss = append(nss, ns)
|
||||
}
|
||||
if len(ss) == 2 {
|
||||
ns := gost.NameServer{
|
||||
Addr: ss[0],
|
||||
Protocol: ss[1],
|
||||
}
|
||||
nss = append(nss, ns)
|
||||
}
|
||||
}
|
||||
return gost.NewResolver(0, nss...)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
resolver := gost.NewResolver(0)
|
||||
resolver.Reload(f)
|
||||
|
||||
go gost.PeriodReload(resolver, cfg)
|
||||
|
||||
return resolver
|
||||
}
|
||||
|
||||
func parseHosts(s string) *gost.Hosts {
|
||||
f, err := os.Open(s)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
hosts := gost.NewHosts()
|
||||
hosts.Reload(f)
|
||||
|
||||
go gost.PeriodReload(hosts, s)
|
||||
|
||||
return hosts
|
||||
}
|
||||
|
||||
func parseIPRoutes(s string) (routes []gost.IPRoute) {
|
||||
if s == "" {
|
||||
return
|
||||
}
|
||||
|
||||
file, err := os.Open(s)
|
||||
if err != nil {
|
||||
ss := strings.Split(s, ",")
|
||||
for _, s := range ss {
|
||||
if _, inet, _ := net.ParseCIDR(strings.TrimSpace(s)); inet != nil {
|
||||
routes = append(routes, gost.IPRoute{Dest: inet})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := strings.Replace(scanner.Text(), "\t", " ", -1)
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
var route gost.IPRoute
|
||||
var ss []string
|
||||
for _, s := range strings.Split(line, " ") {
|
||||
if s = strings.TrimSpace(s); s != "" {
|
||||
ss = append(ss, s)
|
||||
}
|
||||
}
|
||||
if len(ss) > 0 && ss[0] != "" {
|
||||
_, route.Dest, _ = net.ParseCIDR(strings.TrimSpace(ss[0]))
|
||||
if route.Dest == nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if len(ss) > 1 && ss[1] != "" {
|
||||
route.Gateway = net.ParseIP(ss[1])
|
||||
}
|
||||
routes = append(routes, route)
|
||||
}
|
||||
return routes
|
||||
}
|
||||
154
pkg/main.go
Normal file
154
pkg/main.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
log "github.com/sirupsen/logrus"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/client-go/util/homedir"
|
||||
"kubevpn/exe"
|
||||
"kubevpn/remote"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
baseCfg = &baseConfig{}
|
||||
namespace string
|
||||
clientset *kubernetes.Clientset
|
||||
config *restclient.Config
|
||||
name string
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
clientConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
|
||||
&clientcmd.ClientConfigLoadingRules{
|
||||
ExplicitPath: filepath.Join(homedir.HomeDir(), clientcmd.RecommendedHomeDir, clientcmd.RecommendedFileName),
|
||||
},
|
||||
nil,
|
||||
)
|
||||
config, err = clientConfig.ClientConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
clientset, err = kubernetes.NewForConfig(config)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
namespace, _, _ = clientConfig.Namespace()
|
||||
|
||||
k8sCIDR, err := getCIDR(clientset, namespace)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
list := []string{k8sCIDR.String()}
|
||||
|
||||
name = remote.CreateServer(clientset, namespace, "192.168.254.100/24")
|
||||
fmt.Println(name)
|
||||
|
||||
err = remote.InitDHCP(clientset, namespace, &net.IPNet{IP: net.IPv4(196, 168, 254, 100), Mask: net.IPv4Mask(255, 255, 255, 0)})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
dhcp, err := remote.GetIpFromDHCP(clientset, namespace)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
list = append(list, dhcp.String())
|
||||
|
||||
baseCfg.route.ChainNodes = []string{"ssh://127.0.0.1:2222"}
|
||||
baseCfg.route.ServeNodes = []string{
|
||||
fmt.Sprintf("tun://:8421/127.0.0.1:8421?net=%s&route=%s", dhcp.String(), strings.Join(list, ",")),
|
||||
}
|
||||
fmt.Println("your ip is " + dhcp.String())
|
||||
baseCfg.Debug = true
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
exe.InstallTunTapDriver()
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
readyChan := make(chan struct{})
|
||||
stop := make(chan struct{})
|
||||
go func() {
|
||||
err := PortForwardPod(config,
|
||||
clientset,
|
||||
name,
|
||||
namespace,
|
||||
"2222:2222",
|
||||
readyChan,
|
||||
stop,
|
||||
)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
}()
|
||||
<-readyChan
|
||||
log.Info("port forward ready")
|
||||
|
||||
if err := start(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
select {}
|
||||
}
|
||||
|
||||
func start() error {
|
||||
var routerList []router
|
||||
rts, err := baseCfg.route.GenRouters()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
routerList = append(routerList, rts...)
|
||||
|
||||
for _, route := range baseCfg.Routes {
|
||||
rts, err = route.GenRouters()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
routerList = append(routerList, rts...)
|
||||
}
|
||||
|
||||
if len(routerList) == 0 {
|
||||
return errors.New("invalid config")
|
||||
}
|
||||
for i := range routerList {
|
||||
go routerList[i].Serve()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCIDR(clientset *kubernetes.Clientset, ns string) (*net.IPNet, error) {
|
||||
if nodeList, err := clientset.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{}); err == nil {
|
||||
for _, node := range nodeList.Items {
|
||||
if _, ip, err := net.ParseCIDR(node.Spec.PodCIDR); err == nil && ip != nil {
|
||||
ip.Mask = net.IPv4Mask(255, 255, 0, 0)
|
||||
return ip, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if services, err := clientset.CoreV1().Services(ns).List(context.TODO(), metav1.ListOptions{}); err == nil {
|
||||
for _, service := range services.Items {
|
||||
if ip := net.ParseIP(service.Spec.ClusterIP); ip != nil {
|
||||
return &net.IPNet{IP: ip, Mask: net.IPv4Mask(255, 255, 0, 0)}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if podList, err := clientset.CoreV1().Pods(ns).List(context.TODO(), metav1.ListOptions{}); err == nil {
|
||||
for _, pod := range podList.Items {
|
||||
if ip := net.ParseIP(pod.Status.PodIP); ip != nil {
|
||||
return &net.IPNet{IP: ip, Mask: net.IPv4Mask(255, 255, 0, 0)}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("can not found cidr")
|
||||
}
|
||||
41
pkg/main_test.go
Normal file
41
pkg/main_test.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/client-go/util/homedir"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
clientConfig = clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
|
||||
&clientcmd.ClientConfigLoadingRules{
|
||||
ExplicitPath: filepath.Join(homedir.HomeDir(), clientcmd.RecommendedHomeDir, clientcmd.RecommendedFileName),
|
||||
},
|
||||
nil,
|
||||
)
|
||||
clientconfig, _ = clientConfig.ClientConfig()
|
||||
clientsets, _ = kubernetes.NewForConfig(clientconfig)
|
||||
namespaces, _, _ = clientConfig.Namespace()
|
||||
)
|
||||
|
||||
func TestCidr(t *testing.T) {
|
||||
cidr, err := getCIDR(clientsets, namespaces)
|
||||
if err == nil {
|
||||
fmt.Println(cidr.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPing(t *testing.T) {
|
||||
list, _ := clientsets.CoreV1().Services(namespaces).List(context.Background(), metav1.ListOptions{})
|
||||
for _, service := range list.Items {
|
||||
for _, clusterIP := range service.Spec.ClusterIPs {
|
||||
_ = exec.Command("ping", clusterIP, "-c", "4").Run()
|
||||
}
|
||||
}
|
||||
}
|
||||
164
pkg/peer.go
Normal file
164
pkg/peer.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"github.com/ginuerzh/gost"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type peerConfig struct {
|
||||
Strategy string `json:"strategy"`
|
||||
MaxFails int `json:"max_fails"`
|
||||
FailTimeout time.Duration
|
||||
period time.Duration // the period for live reloading
|
||||
Nodes []string `json:"nodes"`
|
||||
group *gost.NodeGroup
|
||||
baseNodes []gost.Node
|
||||
stopped chan struct{}
|
||||
}
|
||||
|
||||
func newPeerConfig() *peerConfig {
|
||||
return &peerConfig{
|
||||
stopped: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *peerConfig) Validate() {
|
||||
}
|
||||
|
||||
func (cfg *peerConfig) Reload(r io.Reader) error {
|
||||
if cfg.Stopped() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := cfg.parse(r); err != nil {
|
||||
return err
|
||||
}
|
||||
cfg.Validate()
|
||||
|
||||
group := cfg.group
|
||||
group.SetSelector(
|
||||
nil,
|
||||
gost.WithFilter(
|
||||
&gost.FailFilter{
|
||||
MaxFails: cfg.MaxFails,
|
||||
FailTimeout: cfg.FailTimeout,
|
||||
},
|
||||
&gost.InvalidFilter{},
|
||||
),
|
||||
gost.WithStrategy(gost.NewStrategy(cfg.Strategy)),
|
||||
)
|
||||
|
||||
gNodes := cfg.baseNodes
|
||||
nid := len(gNodes) + 1
|
||||
for _, s := range cfg.Nodes {
|
||||
nodes, err := parseChainNode(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range nodes {
|
||||
nodes[i].ID = nid
|
||||
nid++
|
||||
}
|
||||
|
||||
gNodes = append(gNodes, nodes...)
|
||||
}
|
||||
|
||||
nodes := group.SetNodes(gNodes...)
|
||||
for _, node := range nodes[len(cfg.baseNodes):] {
|
||||
if node.Bypass != nil {
|
||||
node.Bypass.Stop() // clear the old nodes
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *peerConfig) parse(r io.Reader) error {
|
||||
data, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// compatible with JSON format
|
||||
if err := json.NewDecoder(bytes.NewReader(data)).Decode(cfg); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
split := func(line string) []string {
|
||||
if line == "" {
|
||||
return nil
|
||||
}
|
||||
if n := strings.IndexByte(line, '#'); n >= 0 {
|
||||
line = line[:n]
|
||||
}
|
||||
line = strings.Replace(line, "\t", " ", -1)
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
var ss []string
|
||||
for _, s := range strings.Split(line, " ") {
|
||||
if s = strings.TrimSpace(s); s != "" {
|
||||
ss = append(ss, s)
|
||||
}
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
||||
cfg.Nodes = nil
|
||||
scanner := bufio.NewScanner(bytes.NewReader(data))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
ss := split(line)
|
||||
if len(ss) < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch ss[0] {
|
||||
case "strategy":
|
||||
cfg.Strategy = ss[1]
|
||||
case "max_fails":
|
||||
cfg.MaxFails, _ = strconv.Atoi(ss[1])
|
||||
case "fail_timeout":
|
||||
cfg.FailTimeout, _ = time.ParseDuration(ss[1])
|
||||
case "reload":
|
||||
cfg.period, _ = time.ParseDuration(ss[1])
|
||||
case "peer":
|
||||
cfg.Nodes = append(cfg.Nodes, ss[1])
|
||||
}
|
||||
}
|
||||
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
func (cfg *peerConfig) Period() time.Duration {
|
||||
if cfg.Stopped() {
|
||||
return -1
|
||||
}
|
||||
return cfg.period
|
||||
}
|
||||
|
||||
// Stop stops reloading.
|
||||
func (cfg *peerConfig) Stop() {
|
||||
select {
|
||||
case <-cfg.stopped:
|
||||
default:
|
||||
close(cfg.stopped)
|
||||
}
|
||||
}
|
||||
|
||||
// Stopped checks whether the reloader is stopped.
|
||||
func (cfg *peerConfig) Stopped() bool {
|
||||
select {
|
||||
case <-cfg.stopped:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
464
pkg/route.go
Normal file
464
pkg/route.go
Normal file
@@ -0,0 +1,464 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/ginuerzh/gost"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-log/log"
|
||||
)
|
||||
|
||||
type stringList []string
|
||||
|
||||
func (l *stringList) String() string {
|
||||
return fmt.Sprintf("%s", *l)
|
||||
}
|
||||
func (l *stringList) Set(value string) error {
|
||||
*l = append(*l, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
type route struct {
|
||||
ServeNodes stringList
|
||||
ChainNodes stringList
|
||||
Retries int
|
||||
}
|
||||
|
||||
func (r *route) parseChain() (*gost.Chain, error) {
|
||||
chain := gost.NewChain()
|
||||
chain.Retries = r.Retries
|
||||
gid := 1 // group ID
|
||||
|
||||
for _, ns := range r.ChainNodes {
|
||||
ngroup := gost.NewNodeGroup()
|
||||
ngroup.ID = gid
|
||||
gid++
|
||||
|
||||
// parse the base nodes
|
||||
nodes, err := parseChainNode(ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nid := 1 // node ID
|
||||
for i := range nodes {
|
||||
nodes[i].ID = nid
|
||||
nid++
|
||||
}
|
||||
ngroup.AddNode(nodes...)
|
||||
|
||||
ngroup.SetSelector(nil,
|
||||
gost.WithFilter(
|
||||
&gost.FailFilter{
|
||||
MaxFails: nodes[0].GetInt("max_fails"),
|
||||
FailTimeout: nodes[0].GetDuration("fail_timeout"),
|
||||
},
|
||||
&gost.InvalidFilter{},
|
||||
),
|
||||
gost.WithStrategy(gost.NewStrategy(nodes[0].Get("strategy"))),
|
||||
)
|
||||
|
||||
if cfg := nodes[0].Get("peer"); cfg != "" {
|
||||
f, err := os.Open(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
peerCfg := newPeerConfig()
|
||||
peerCfg.group = ngroup
|
||||
peerCfg.baseNodes = nodes
|
||||
peerCfg.Reload(f)
|
||||
f.Close()
|
||||
|
||||
go gost.PeriodReload(peerCfg, cfg)
|
||||
}
|
||||
|
||||
chain.AddNodeGroup(ngroup)
|
||||
}
|
||||
|
||||
return chain, nil
|
||||
}
|
||||
|
||||
func parseChainNode(ns string) (nodes []gost.Node, err error) {
|
||||
node, err := gost.ParseNode(ns)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if auth := node.Get("auth"); auth != "" && node.User == nil {
|
||||
c, err := base64.StdEncoding.DecodeString(auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cs := string(c)
|
||||
s := strings.IndexByte(cs, ':')
|
||||
if s < 0 {
|
||||
node.User = url.User(cs)
|
||||
} else {
|
||||
node.User = url.UserPassword(cs[:s], cs[s+1:])
|
||||
}
|
||||
}
|
||||
if node.User == nil {
|
||||
users, err := parseUsers(node.Get("secrets"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(users) > 0 {
|
||||
node.User = users[0]
|
||||
}
|
||||
}
|
||||
|
||||
serverName, sport, _ := net.SplitHostPort(node.Addr)
|
||||
if serverName == "" {
|
||||
serverName = "localhost" // default server name
|
||||
}
|
||||
|
||||
rootCAs, err := loadCA(node.Get("ca"))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tlsCfg := &tls.Config{
|
||||
ServerName: serverName,
|
||||
InsecureSkipVerify: !node.GetBool("secure"),
|
||||
RootCAs: rootCAs,
|
||||
}
|
||||
|
||||
// If the argument `ca` is given, but not open `secure`, we verify the
|
||||
// certificate manually.
|
||||
if rootCAs != nil && !node.GetBool("secure") {
|
||||
tlsCfg.VerifyConnection = func(state tls.ConnectionState) error {
|
||||
opts := x509.VerifyOptions{
|
||||
Roots: rootCAs,
|
||||
CurrentTime: time.Now(),
|
||||
DNSName: "",
|
||||
Intermediates: x509.NewCertPool(),
|
||||
}
|
||||
|
||||
certs := state.PeerCertificates
|
||||
for i, cert := range certs {
|
||||
if i == 0 {
|
||||
continue
|
||||
}
|
||||
opts.Intermediates.AddCert(cert)
|
||||
}
|
||||
|
||||
_, err = certs[0].Verify(opts)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if cert, err := tls.LoadX509KeyPair(node.Get("cert"), node.Get("key")); err == nil {
|
||||
tlsCfg.Certificates = []tls.Certificate{cert}
|
||||
}
|
||||
|
||||
timeout := node.GetDuration("timeout")
|
||||
|
||||
var tr gost.Transporter
|
||||
switch node.Transport {
|
||||
case "ssh":
|
||||
if node.Protocol == "direct" || node.Protocol == "remote" {
|
||||
tr = gost.SSHForwardTransporter()
|
||||
} else {
|
||||
tr = gost.SSHTunnelTransporter()
|
||||
}
|
||||
default:
|
||||
tr = gost.TCPTransporter()
|
||||
}
|
||||
|
||||
var connector gost.Connector
|
||||
switch node.Protocol {
|
||||
case "ssu":
|
||||
connector = gost.ShadowUDPConnector(node.User)
|
||||
case "direct":
|
||||
connector = gost.SSHDirectForwardConnector()
|
||||
case "remote":
|
||||
connector = gost.SSHRemoteForwardConnector()
|
||||
default:
|
||||
connector = gost.AutoConnector(node.User)
|
||||
}
|
||||
|
||||
host := node.Get("host")
|
||||
if host == "" {
|
||||
host = node.Host
|
||||
}
|
||||
|
||||
node.DialOptions = append(node.DialOptions,
|
||||
gost.TimeoutDialOption(timeout),
|
||||
gost.HostDialOption(host),
|
||||
)
|
||||
|
||||
node.ConnectOptions = []gost.ConnectOption{
|
||||
gost.UserAgentConnectOption(node.Get("agent")),
|
||||
gost.NoTLSConnectOption(node.GetBool("notls")),
|
||||
gost.NoDelayConnectOption(node.GetBool("nodelay")),
|
||||
}
|
||||
|
||||
sshConfig := &gost.SSHConfig{}
|
||||
if s := node.Get("ssh_key"); s != "" {
|
||||
key, err := gost.ParseSSHKeyFile(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sshConfig.Key = key
|
||||
}
|
||||
handshakeOptions := []gost.HandshakeOption{
|
||||
gost.AddrHandshakeOption(node.Addr),
|
||||
gost.HostHandshakeOption(host),
|
||||
gost.UserHandshakeOption(node.User),
|
||||
gost.TLSConfigHandshakeOption(tlsCfg),
|
||||
gost.IntervalHandshakeOption(node.GetDuration("ping")),
|
||||
gost.TimeoutHandshakeOption(timeout),
|
||||
gost.RetryHandshakeOption(node.GetInt("retry")),
|
||||
gost.SSHConfigHandshakeOption(sshConfig),
|
||||
}
|
||||
|
||||
node.Client = &gost.Client{
|
||||
Connector: connector,
|
||||
Transporter: tr,
|
||||
}
|
||||
|
||||
node.Bypass = parseBypass(node.Get("bypass"))
|
||||
|
||||
ips := parseIP(node.Get("ip"), sport)
|
||||
for _, ip := range ips {
|
||||
nd := node.Clone()
|
||||
nd.Addr = ip
|
||||
// override the default node address
|
||||
nd.HandshakeOptions = append(handshakeOptions, gost.AddrHandshakeOption(ip))
|
||||
// One node per IP
|
||||
nodes = append(nodes, nd)
|
||||
}
|
||||
if len(ips) == 0 {
|
||||
node.HandshakeOptions = handshakeOptions
|
||||
nodes = []gost.Node{node}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (r *route) GenRouters() ([]router, error) {
|
||||
chain, err := r.parseChain()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var rts []router
|
||||
|
||||
for _, ns := range r.ServeNodes {
|
||||
node, err := gost.ParseNode(ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if auth := node.Get("auth"); auth != "" && node.User == nil {
|
||||
c, err := base64.StdEncoding.DecodeString(auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cs := string(c)
|
||||
s := strings.IndexByte(cs, ':')
|
||||
if s < 0 {
|
||||
node.User = url.User(cs)
|
||||
} else {
|
||||
node.User = url.UserPassword(cs[:s], cs[s+1:])
|
||||
}
|
||||
}
|
||||
authenticator, err := parseAuthenticator(node.Get("secrets"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if authenticator == nil && node.User != nil {
|
||||
kvs := make(map[string]string)
|
||||
kvs[node.User.Username()], _ = node.User.Password()
|
||||
authenticator = gost.NewLocalAuthenticator(kvs)
|
||||
}
|
||||
if node.User == nil {
|
||||
if users, _ := parseUsers(node.Get("secrets")); len(users) > 0 {
|
||||
node.User = users[0]
|
||||
}
|
||||
}
|
||||
certFile, keyFile := node.Get("cert"), node.Get("key")
|
||||
tlsCfg, err := tlsConfig(certFile, keyFile, node.Get("ca"))
|
||||
if err != nil && certFile != "" && keyFile != "" {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ttl := node.GetDuration("ttl")
|
||||
timeout := node.GetDuration("timeout")
|
||||
|
||||
tunRoutes := parseIPRoutes(node.Get("route"))
|
||||
gw := net.ParseIP(node.Get("gw")) // default gateway
|
||||
for i := range tunRoutes {
|
||||
if tunRoutes[i].Gateway == nil {
|
||||
tunRoutes[i].Gateway = gw
|
||||
}
|
||||
}
|
||||
|
||||
var ln gost.Listener
|
||||
switch node.Transport {
|
||||
case "ssh":
|
||||
config := &gost.SSHConfig{
|
||||
Authenticator: authenticator,
|
||||
TLSConfig: tlsCfg,
|
||||
}
|
||||
if s := node.Get("ssh_key"); s != "" {
|
||||
key, err := gost.ParseSSHKeyFile(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Key = key
|
||||
}
|
||||
if s := node.Get("ssh_authorized_keys"); s != "" {
|
||||
keys, err := gost.ParseSSHAuthorizedKeysFile(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.AuthorizedKeys = keys
|
||||
}
|
||||
if node.Protocol == "forward" {
|
||||
ln, err = gost.TCPListener(node.Addr)
|
||||
} else {
|
||||
ln, err = gost.SSHTunnelListener(node.Addr, config)
|
||||
}
|
||||
case "tcp":
|
||||
// Directly use SSH port forwarding if the last chain node is forward+ssh
|
||||
if chain.LastNode().Protocol == "forward" && chain.LastNode().Transport == "ssh" {
|
||||
chain.Nodes()[len(chain.Nodes())-1].Client.Connector = gost.SSHDirectForwardConnector()
|
||||
chain.Nodes()[len(chain.Nodes())-1].Client.Transporter = gost.SSHForwardTransporter()
|
||||
}
|
||||
ln, err = gost.TCPListener(node.Addr)
|
||||
case "udp":
|
||||
ln, err = gost.UDPListener(node.Addr, &gost.UDPListenConfig{
|
||||
TTL: ttl,
|
||||
Backlog: node.GetInt("backlog"),
|
||||
QueueSize: node.GetInt("queue"),
|
||||
})
|
||||
case "tun":
|
||||
cfg := gost.TunConfig{
|
||||
Name: node.Get("name"),
|
||||
Addr: node.Get("net"),
|
||||
Peer: node.Get("peer"),
|
||||
MTU: node.GetInt("mtu"),
|
||||
Routes: tunRoutes,
|
||||
Gateway: node.Get("gw"),
|
||||
}
|
||||
ln, err = gost.TunListener(cfg)
|
||||
case "tap":
|
||||
cfg := gost.TapConfig{
|
||||
Name: node.Get("name"),
|
||||
Addr: node.Get("net"),
|
||||
MTU: node.GetInt("mtu"),
|
||||
Routes: strings.Split(node.Get("route"), ","),
|
||||
Gateway: node.Get("gw"),
|
||||
}
|
||||
ln, err = gost.TapListener(cfg)
|
||||
case "dns":
|
||||
ln, err = gost.DNSListener(
|
||||
node.Addr,
|
||||
&gost.DNSOptions{
|
||||
Mode: node.Get("mode"),
|
||||
TLSConfig: tlsCfg,
|
||||
},
|
||||
)
|
||||
default:
|
||||
ln, err = gost.TCPListener(node.Addr)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var handler gost.Handler
|
||||
switch node.Protocol {
|
||||
case "tcp":
|
||||
handler = gost.TCPDirectForwardHandler(node.Remote)
|
||||
case "udp":
|
||||
handler = gost.UDPDirectForwardHandler(node.Remote)
|
||||
case "tun":
|
||||
handler = gost.TunHandler()
|
||||
case "tap":
|
||||
handler = gost.TapHandler()
|
||||
case "dns":
|
||||
handler = gost.DNSHandler(node.Remote)
|
||||
default:
|
||||
// start from 2.5, if remote is not empty, then we assume that it is a forward tunnel.
|
||||
if node.Remote != "" {
|
||||
handler = gost.TCPDirectForwardHandler(node.Remote)
|
||||
} else {
|
||||
handler = gost.AutoHandler()
|
||||
}
|
||||
}
|
||||
|
||||
node.Bypass = parseBypass(node.Get("bypass"))
|
||||
hosts := parseHosts(node.Get("hosts"))
|
||||
ips := parseIP(node.Get("ip"), "")
|
||||
|
||||
resolver := parseResolver(node.Get("dns"))
|
||||
if resolver != nil {
|
||||
resolver.Init(
|
||||
gost.ChainResolverOption(chain),
|
||||
gost.TimeoutResolverOption(timeout),
|
||||
gost.TTLResolverOption(ttl),
|
||||
gost.PreferResolverOption(node.Get("prefer")),
|
||||
gost.SrcIPResolverOption(net.ParseIP(node.Get("ip"))),
|
||||
)
|
||||
}
|
||||
|
||||
handler.Init(
|
||||
gost.AddrHandlerOption(ln.Addr().String()),
|
||||
gost.ChainHandlerOption(chain),
|
||||
gost.UsersHandlerOption(node.User),
|
||||
gost.AuthenticatorHandlerOption(authenticator),
|
||||
gost.BypassHandlerOption(node.Bypass),
|
||||
gost.ResolverHandlerOption(resolver),
|
||||
gost.HostsHandlerOption(hosts),
|
||||
gost.RetryHandlerOption(node.GetInt("retry")), // override the global retry option.
|
||||
gost.TimeoutHandlerOption(timeout),
|
||||
gost.ProbeResistHandlerOption(node.Get("probe_resist")),
|
||||
gost.KnockingHandlerOption(node.Get("knock")),
|
||||
gost.NodeHandlerOption(node),
|
||||
gost.IPsHandlerOption(ips),
|
||||
gost.TCPModeHandlerOption(node.GetBool("tcp")),
|
||||
gost.IPRoutesHandlerOption(tunRoutes...),
|
||||
)
|
||||
|
||||
rt := router{
|
||||
node: node,
|
||||
server: &gost.Server{Listener: ln},
|
||||
handler: handler,
|
||||
chain: chain,
|
||||
resolver: resolver,
|
||||
hosts: hosts,
|
||||
}
|
||||
rts = append(rts, rt)
|
||||
}
|
||||
|
||||
return rts, nil
|
||||
}
|
||||
|
||||
type router struct {
|
||||
node gost.Node
|
||||
server *gost.Server
|
||||
handler gost.Handler
|
||||
chain *gost.Chain
|
||||
resolver gost.Resolver
|
||||
hosts *gost.Hosts
|
||||
}
|
||||
|
||||
func (r *router) Serve() error {
|
||||
log.Logf("%s on %s", r.node.String(), r.server.Addr())
|
||||
return r.server.Serve(r.handler)
|
||||
}
|
||||
|
||||
func (r *router) Close() error {
|
||||
if r == nil || r.server == nil {
|
||||
return nil
|
||||
}
|
||||
return r.server.Close()
|
||||
}
|
||||
246
pkg/util.go
Normal file
246
pkg/util.go
Normal file
@@ -0,0 +1,246 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/moby/term"
|
||||
log "github.com/sirupsen/logrus"
|
||||
v12 "k8s.io/api/autoscaling/v1"
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/restmapper"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/portforward"
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
clientgowatch "k8s.io/client-go/tools/watch"
|
||||
"k8s.io/client-go/transport/spdy"
|
||||
"k8s.io/client-go/util/retry"
|
||||
"k8s.io/kubectl/pkg/cmd/util"
|
||||
term2 "k8s.io/kubectl/pkg/util/term"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
func WaitResource(client *kubernetes.Clientset, getter cache.Getter, namespace, apiVersion, kind string, list metav1.ListOptions, checker func(interface{}) bool) error {
|
||||
groupResources, _ := restmapper.GetAPIGroupResources(client)
|
||||
mapper := restmapper.NewDiscoveryRESTMapper(groupResources)
|
||||
groupVersionKind := schema.FromAPIVersionAndKind(apiVersion, kind)
|
||||
mapping, err := mapper.RESTMapping(groupVersionKind.GroupKind(), groupVersionKind.Version)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
watchlist := cache.NewFilteredListWatchFromClient(
|
||||
getter,
|
||||
mapping.Resource.Resource,
|
||||
namespace,
|
||||
func(options *metav1.ListOptions) {
|
||||
options.LabelSelector = list.LabelSelector
|
||||
options.FieldSelector = list.FieldSelector
|
||||
options.Watch = list.Watch
|
||||
},
|
||||
)
|
||||
|
||||
preConditionFunc := func(store cache.Store) (bool, error) {
|
||||
if len(store.List()) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
for _, p := range store.List() {
|
||||
if !checker(p) {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
conditionFunc := func(e watch.Event) (bool, error) { return checker(e.Object), nil }
|
||||
|
||||
object, err := scheme.Scheme.New(mapping.GroupVersionKind)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
event, err := clientgowatch.UntilWithSync(ctx, watchlist, object, preConditionFunc, conditionFunc)
|
||||
if err != nil {
|
||||
log.Infof("wait to ready failed, error: %v, event: %v", err, event)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetAvailablePortOrDie() int {
|
||||
address, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:0", "0.0.0.0"))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
listener, err := net.ListenTCP("tcp", address)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer listener.Close()
|
||||
return listener.Addr().(*net.TCPAddr).Port
|
||||
}
|
||||
|
||||
func WaitPod(clientset *kubernetes.Clientset, namespace string, list metav1.ListOptions, checker func(*v1.Pod) bool) error {
|
||||
return WaitResource(
|
||||
clientset,
|
||||
clientset.CoreV1().RESTClient(),
|
||||
namespace,
|
||||
"v1",
|
||||
"Pod",
|
||||
list,
|
||||
func(i interface{}) bool { return checker(i.(*v1.Pod)) },
|
||||
)
|
||||
}
|
||||
|
||||
func PortForwardPod(config *rest.Config, clientset *kubernetes.Clientset, podName, namespace, portPair string, readyChan, stopChan chan struct{}) error {
|
||||
url := clientset.CoreV1().
|
||||
RESTClient().
|
||||
Post().
|
||||
Resource("pods").
|
||||
Namespace(namespace).
|
||||
Name(podName).
|
||||
SubResource("portforward").
|
||||
URL()
|
||||
transport, upgrader, err := spdy.RoundTripperFor(config)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", url)
|
||||
p := []string{portPair}
|
||||
forwarder, err := portforward.New(dialer, p, stopChan, readyChan, os.Stdout, os.Stderr)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err = forwarder.ForwardPorts(); err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ScaleDeploymentReplicasTo(options *kubernetes.Clientset, name, namespace string, replicas int32) {
|
||||
err := retry.OnError(
|
||||
retry.DefaultRetry,
|
||||
func(err error) bool { return err != nil },
|
||||
func() error {
|
||||
_, err := options.AppsV1().Deployments(namespace).
|
||||
UpdateScale(context.TODO(), name, &v12.Scale{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
|
||||
Spec: v12.ScaleSpec{Replicas: replicas},
|
||||
}, metav1.UpdateOptions{})
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
log.Errorf("update deployment: %s's replicas to %d failed, error: %v", name, replicas, err)
|
||||
}
|
||||
}
|
||||
|
||||
type shellOptions interface {
|
||||
GetNamespace() string
|
||||
GetDeployment() string
|
||||
GetLocalDir() string
|
||||
GetRemoteDir() string
|
||||
GetKubeconfig() string
|
||||
}
|
||||
|
||||
func Shell(client *kubernetes.Clientset, options shellOptions) error {
|
||||
deployment, err2 := client.AppsV1().Deployments(options.GetNamespace()).
|
||||
Get(context.TODO(), options.GetDeployment(), metav1.GetOptions{})
|
||||
if err2 != nil {
|
||||
log.Error(err2)
|
||||
}
|
||||
labelMap, _ := metav1.LabelSelectorAsMap(deployment.Spec.Selector)
|
||||
pods, err := client.CoreV1().Pods(options.GetNamespace()).
|
||||
List(context.TODO(), metav1.ListOptions{LabelSelector: labels.SelectorFromSet(labelMap).String()})
|
||||
if err != nil {
|
||||
log.Errorf("get kubedev pod error: %v", err)
|
||||
}
|
||||
if len(pods.Items) <= 0 {
|
||||
log.Warnf("this should not happened, pods items length: %d", len(pods.Items))
|
||||
}
|
||||
index := -1
|
||||
for i, pod := range pods.Items {
|
||||
if pod.Status.Phase == v1.PodRunning {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if index < 0 {
|
||||
return fmt.Errorf("cannot exec into a container in a completed pod; current phase is %s", pods.Items[0].Status.Phase)
|
||||
}
|
||||
stdin, stdout, stderr := term.StdStreams()
|
||||
tty := term2.TTY{
|
||||
Out: stdout,
|
||||
In: stdin,
|
||||
Raw: true,
|
||||
}
|
||||
if !tty.IsTerminalIn() {
|
||||
log.Error("Unable to use a TTY - input is not a terminal or the right kind of file")
|
||||
}
|
||||
var terminalSizeQueue remotecommand.TerminalSizeQueue
|
||||
if tty.Raw {
|
||||
terminalSizeQueue = tty.MonitorSize(tty.GetSize())
|
||||
}
|
||||
f := func() error {
|
||||
configFlags := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag()
|
||||
kubeconfig := options.GetKubeconfig()
|
||||
configFlags.KubeConfig = &kubeconfig
|
||||
namespace := options.GetNamespace()
|
||||
configFlags.Namespace = &namespace
|
||||
f := util.NewFactory(util.NewMatchVersionFlags(configFlags))
|
||||
config, _ := f.ToRESTConfig()
|
||||
restClient, err := rest.RESTClientFor(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req := restClient.Post().
|
||||
Resource("pods").
|
||||
Name(pods.Items[index].Name).
|
||||
Namespace(options.GetNamespace()).
|
||||
SubResource("exec").
|
||||
VersionedParams(
|
||||
&v1.PodExecOptions{
|
||||
Container: pods.Items[index].Spec.Containers[0].Name,
|
||||
Command: []string{"sh", "-c", "(bash||sh)"},
|
||||
Stdin: true,
|
||||
Stdout: true,
|
||||
Stderr: true,
|
||||
TTY: true,
|
||||
},
|
||||
scheme.ParameterCodec,
|
||||
)
|
||||
executor, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return executor.Stream(remotecommand.StreamOptions{
|
||||
Stdin: tty.In,
|
||||
Stdout: tty.Out,
|
||||
Stderr: stderr,
|
||||
Tty: true,
|
||||
TerminalSizeQueue: terminalSizeQueue,
|
||||
})
|
||||
}
|
||||
|
||||
if err = tty.Safe(f); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user