Compare commits
8 Commits
0.8.73
...
0.8.76-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c05534071 | ||
|
|
3627d5bb40 | ||
|
|
1f7714c1e9 | ||
|
|
d868ebf4a1 | ||
|
|
4640379b06 | ||
|
|
922120efbb | ||
|
|
91fec99b00 | ||
|
|
2f89c1fe1f |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
run: |
|
||||
export KUBECONFIG="$(kind get kubeconfig-path)"
|
||||
./scripts/coverage.sh
|
||||
make unit-test
|
||||
bash <(curl -s https://codecov.io/bash) -t ${CODECOV_TOKEN}
|
||||
|
||||
- name: build fx
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
run: |
|
||||
export KUBECONFIG="$(kind get kubeconfig-path)"
|
||||
DEBUG=true go test -v ./...
|
||||
make unit-test
|
||||
|
||||
- name: build fx
|
||||
run: |
|
||||
|
||||
6
Makefile
6
Makefile
@@ -10,10 +10,10 @@ generate:
|
||||
packr
|
||||
|
||||
b:
|
||||
go build -o ${OUTPUT_DIR}/fx fx.go
|
||||
go build -ldflags="-s -w" -o ${OUTPUT_DIR}/fx fx.go
|
||||
|
||||
build:
|
||||
go build -o ${OUTPUT_DIR}/fx fx.go
|
||||
go build -ldflags="-s -w" -o ${OUTPUT_DIR}/fx fx.go
|
||||
|
||||
pull:
|
||||
./scripts/pull.sh
|
||||
@@ -26,7 +26,7 @@ clean:
|
||||
rm -rf ${DIST_DIR}
|
||||
|
||||
unit-test:
|
||||
./scripts/coverage.sh
|
||||
CI=true ./scripts/coverage.sh
|
||||
|
||||
cli-test-ci:
|
||||
./scripts/test_cli.sh 'js'
|
||||
|
||||
232
config/config.go
232
config/config.go
@@ -7,45 +7,67 @@ import (
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"sync"
|
||||
"path/filepath"
|
||||
|
||||
dockerInfra "github.com/metrue/fx/infra/docker"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/metrue/fx/utils"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Items data of config file
|
||||
type Items struct {
|
||||
Clouds map[string]map[string]string `json:"clouds"`
|
||||
CurrentCloud string `json:"current_cloud"`
|
||||
// Configer manage fx config
|
||||
type Configer interface {
|
||||
GetCurrentCloud() ([]byte, error)
|
||||
GetCurrentCloudType() (string, error)
|
||||
GetKubeConfig() (string, error)
|
||||
UseCloud(name string) error
|
||||
View() ([]byte, error)
|
||||
AddCloud(name string, meta []byte) error
|
||||
Dir() (string, error)
|
||||
}
|
||||
|
||||
// Config config of fx
|
||||
type Config struct {
|
||||
mux sync.Mutex
|
||||
configFile string
|
||||
Items
|
||||
container *Container
|
||||
}
|
||||
|
||||
const defaultFxConfig = "~/.fx/config.yml"
|
||||
|
||||
// LoadDefault load default config
|
||||
func LoadDefault() (*Config, error) {
|
||||
configFile, err := homedir.Expand("~/.fx/config.yml")
|
||||
configFile, err := homedir.Expand(defaultFxConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if os.Getenv("FX_CONFIG") != "" {
|
||||
configFile = os.Getenv("FX_CONFIG")
|
||||
|
||||
}
|
||||
|
||||
if _, err := os.Stat(configFile); os.IsNotExist(err) {
|
||||
if err := utils.EnsureFile(configFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := writeDefaultConfig(configFile); err != nil {
|
||||
}
|
||||
return load(configFile)
|
||||
}
|
||||
|
||||
func load(configFile string) (*Config, error) {
|
||||
container, err := CreateContainer(configFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config := &Config{
|
||||
configFile: configFile,
|
||||
container: container,
|
||||
}
|
||||
|
||||
if container.get("clouds") == nil {
|
||||
if err := config.writeDefaultConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return load(configFile)
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// Load config
|
||||
@@ -58,138 +80,128 @@ func Load(configFile string) (*Config, error) {
|
||||
if err := utils.EnsureFile(configFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := writeDefaultConfig(configFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return load(configFile)
|
||||
}
|
||||
|
||||
// AddCloud add a cloud
|
||||
func (c *Config) addCloud(name string, cloud map[string]string) error {
|
||||
c.Items.Clouds[name] = cloud
|
||||
return save(c)
|
||||
}
|
||||
|
||||
// AddDockerCloud add docker cloud
|
||||
func (c *Config) AddDockerCloud(name string, config []byte) error {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
var conf map[string]string
|
||||
err := json.Unmarshal(config, &conf)
|
||||
if err != nil {
|
||||
// AddCloud add k8s cloud
|
||||
func (c *Config) AddCloud(name string, meta []byte) error {
|
||||
var cloudMeta map[string]interface{}
|
||||
if err := json.Unmarshal(meta, &cloudMeta); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cloud := map[string]string{
|
||||
"type": "docker",
|
||||
"host": conf["ip"],
|
||||
"user": conf["user"],
|
||||
}
|
||||
return c.addCloud(name, cloud)
|
||||
}
|
||||
|
||||
// AddK8SCloud add k8s cloud
|
||||
func (c *Config) AddK8SCloud(name string, kubeconfig []byte) error {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
dir := path.Dir(c.configFile)
|
||||
kubecfg := path.Join(dir, name+".kubeconfig")
|
||||
if err := utils.EnsureFile(kubecfg); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(kubecfg, kubeconfig, 0666); err != nil {
|
||||
return err
|
||||
cloudType, ok := cloudMeta["type"].(string)
|
||||
if !ok || cloudType == "" {
|
||||
return fmt.Errorf("unknown cloud type")
|
||||
}
|
||||
|
||||
cloud := map[string]string{
|
||||
"type": "k8s",
|
||||
"kubeconfig": kubecfg,
|
||||
}
|
||||
|
||||
return c.addCloud(name, cloud)
|
||||
}
|
||||
|
||||
// Use set cloud instance with name as current context
|
||||
func (c *Config) Use(name string) error {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
has := false
|
||||
for n := range c.Clouds {
|
||||
if n == name {
|
||||
has = true
|
||||
break
|
||||
if cloudType == types.CloudTypeK8S {
|
||||
dir := path.Dir(c.configFile)
|
||||
kubecfg := path.Join(dir, name+".kubeconfig")
|
||||
if err := utils.EnsureFile(kubecfg); err != nil {
|
||||
return err
|
||||
}
|
||||
config, ok := cloudMeta["config"].(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid k8s config")
|
||||
}
|
||||
if err := ioutil.WriteFile(kubecfg, []byte(config), 0666); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !has {
|
||||
return fmt.Errorf("no cloud with name = %s", name)
|
||||
|
||||
if err := c.container.set("clouds."+name, cloudMeta); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Items.CurrentCloud = name
|
||||
return save(c)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UseCloud set cloud instance with name as current context
|
||||
func (c *Config) UseCloud(name string) error {
|
||||
if name == "" {
|
||||
return fmt.Errorf("could not use empty name")
|
||||
}
|
||||
|
||||
if c.container.get("clouds."+name) == nil {
|
||||
return fmt.Errorf("no such cloud with name: %s", name)
|
||||
}
|
||||
return c.container.set("current_cloud", name)
|
||||
}
|
||||
|
||||
// View view current config
|
||||
func (c *Config) View() ([]byte, error) {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
return ioutil.ReadFile(c.configFile)
|
||||
}
|
||||
|
||||
func load(configFile string) (*Config, error) {
|
||||
conf, err := ioutil.ReadFile(configFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// GetCurrentCloud get current using cloud's meta
|
||||
func (c *Config) GetCurrentCloud() ([]byte, error) {
|
||||
name, ok := c.container.get("current_cloud").(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no active cloud")
|
||||
}
|
||||
var items Items
|
||||
if err := yaml.Unmarshal(conf, &items); err != nil {
|
||||
return nil, err
|
||||
meta := c.container.get("clouds." + name)
|
||||
if meta == nil {
|
||||
return nil, fmt.Errorf("invalid config")
|
||||
}
|
||||
var c = Config{
|
||||
configFile: configFile,
|
||||
Items: items,
|
||||
}
|
||||
return &c, nil
|
||||
return json.Marshal(meta)
|
||||
}
|
||||
|
||||
func save(c *Config) error {
|
||||
conf, err := yaml.Marshal(c.Items)
|
||||
if err != nil {
|
||||
return err
|
||||
// GetCurrentCloudType get current cloud type
|
||||
func (c *Config) GetCurrentCloudType() (string, error) {
|
||||
name, ok := c.container.get("current_cloud").(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("no active cloud")
|
||||
}
|
||||
if err := ioutil.WriteFile(c.configFile, conf, 0666); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return c.container.get("clouds." + name + ".type").(string), nil
|
||||
}
|
||||
|
||||
func writeDefaultConfig(configFile string) error {
|
||||
// GetKubeConfig get kubeconfig
|
||||
func (c *Config) GetKubeConfig() (string, error) {
|
||||
name, ok := c.container.get("current_cloud").(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("no active cloud")
|
||||
}
|
||||
dir := path.Dir(c.configFile)
|
||||
kubecfg := path.Join(dir, name+".kubeconfig")
|
||||
return kubecfg, nil
|
||||
}
|
||||
|
||||
func (c *Config) writeDefaultConfig() error {
|
||||
me, err := user.Current()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
items := Items{
|
||||
Clouds: map[string]map[string]string{
|
||||
"default": map[string]string{
|
||||
"type": "docker",
|
||||
"host": "127.0.0.1",
|
||||
"user": me.Username,
|
||||
},
|
||||
},
|
||||
CurrentCloud: "default",
|
||||
}
|
||||
|
||||
body, err := yaml.Marshal(items)
|
||||
defaultCloud := &dockerInfra.Cloud{
|
||||
IP: "127.0.0.1",
|
||||
User: me.Username,
|
||||
Name: "default",
|
||||
Type: types.CloudTypeDocker,
|
||||
}
|
||||
meta, err := defaultCloud.Dump()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(configFile, body, 0666); err != nil {
|
||||
if err := c.container.set("clouds", map[string]interface{}{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
if err := c.AddCloud("default", meta); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.UseCloud("default")
|
||||
}
|
||||
|
||||
// Dir get directory of config
|
||||
func (c *Config) Dir() (string, error) {
|
||||
p, err := filepath.Abs(c.configFile)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return path.Dir(p), nil
|
||||
}
|
||||
|
||||
var (
|
||||
_ Configer = &Config{}
|
||||
)
|
||||
|
||||
@@ -4,57 +4,111 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
k8sInfra "github.com/metrue/fx/infra/k8s"
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
func TestConfig(t *testing.T) {
|
||||
configPath := "./tmp/config.yml"
|
||||
defer func() {
|
||||
if err := os.RemoveAll("./tmp"); err != nil {
|
||||
if err := os.RemoveAll("./tmp/config.yml"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// default cloud
|
||||
c, err := Load(configPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(c.Clouds) != 1 {
|
||||
t.Fatal("should contain default cloud")
|
||||
}
|
||||
|
||||
name := "fx_cluster_1"
|
||||
if err := c.Use(name); err == nil {
|
||||
t.Fatal("should get no such cloud error")
|
||||
}
|
||||
|
||||
if err := c.AddK8SCloud(name, []byte("sampe kubeconfg")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
config := map[string]string{
|
||||
"ip": "127.0.0.1",
|
||||
"user": "use1",
|
||||
}
|
||||
configData, _ := json.Marshal(config)
|
||||
if err := c.AddDockerCloud("docker-1", configData); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := c.Use(name); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if c.CurrentCloud != name {
|
||||
t.Fatalf("should get %s but got %s", name, c.CurrentCloud)
|
||||
}
|
||||
|
||||
conf, err := Load(configPath)
|
||||
defaultMeta, err := c.GetCurrentCloud()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if conf.CurrentCloud != name {
|
||||
t.Fatalf("should get %s but got %s", name, c.CurrentCloud)
|
||||
var cloudMeta map[string]string
|
||||
if err := json.Unmarshal(defaultMeta, &cloudMeta); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cloudMeta["ip"] != "127.0.0.1" {
|
||||
t.Fatalf("should get %s but got %s", "127.0.0.1", cloudMeta["ip"])
|
||||
}
|
||||
|
||||
me, _ := user.Current()
|
||||
if cloudMeta["user"] != me.Username {
|
||||
t.Fatalf("should get %s but got %s", me.Username, cloudMeta["user"])
|
||||
}
|
||||
if cloudMeta["type"] != types.CloudTypeDocker {
|
||||
t.Fatalf("should get %s but got %s", types.CloudTypeDocker, cloudMeta["type"])
|
||||
}
|
||||
if cloudMeta["name"] != "default" {
|
||||
t.Fatalf("should get %s but got %s", "default", cloudMeta["name"])
|
||||
}
|
||||
|
||||
n1, err := k8sInfra.CreateNode(
|
||||
"1.1.1.1",
|
||||
"user-1",
|
||||
"k3s-master",
|
||||
"master-node",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
n2, err := k8sInfra.CreateNode(
|
||||
"1.1.1.1",
|
||||
"user-1",
|
||||
"k3s-agent",
|
||||
"agent-node-1",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
kName := "k8s-1"
|
||||
kubeconf := "./tmp/" + kName + "config.yml"
|
||||
defer func() {
|
||||
if err := os.RemoveAll(kubeconf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// add k8s cloud
|
||||
kCloud := k8sInfra.NewCloud(kubeconf, n1, n2)
|
||||
kMeta, err := kCloud.Dump()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := c.AddCloud(kName, kMeta); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
curMeta, err := c.GetCurrentCloud()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(curMeta, defaultMeta) {
|
||||
t.Fatalf("should get %v but got %v", defaultMeta, curMeta)
|
||||
}
|
||||
|
||||
if err := c.UseCloud("cloud-not-existed"); err == nil {
|
||||
t.Fatalf("should get error when there is not given cloud name")
|
||||
}
|
||||
|
||||
if err := c.UseCloud(kName); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
curMeta, err = c.GetCurrentCloud()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if reflect.DeepEqual(curMeta, kMeta) {
|
||||
t.Fatalf("should get %v but got %v", kMeta, curMeta)
|
||||
}
|
||||
|
||||
body, err := c.View()
|
||||
@@ -62,4 +116,16 @@ func TestConfig(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Println(string(body))
|
||||
|
||||
dir, err := c.Dir()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
here, err := os.Getwd()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if dir != filepath.Join(here, "./tmp") {
|
||||
t.Fatalf("should get %s but got %s", "./tmp", dir)
|
||||
}
|
||||
}
|
||||
|
||||
73
config/container.go
Normal file
73
config/container.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/metrue/fx/utils"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Container config container, wrap viper as a key-value store with lock
|
||||
type Container struct {
|
||||
mux sync.Mutex
|
||||
store string
|
||||
}
|
||||
|
||||
// CreateContainer new a container
|
||||
func CreateContainer(storeFile string) (*Container, error) {
|
||||
if err := utils.EnsureFile(storeFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dir := filepath.Dir(storeFile)
|
||||
ext := filepath.Ext(storeFile)
|
||||
name := filepath.Base(storeFile)
|
||||
viper.AddConfigPath(dir)
|
||||
viper.SetConfigName(strings.Replace(name, ext, "", 1))
|
||||
viper.SetConfigType(strings.Replace(ext, ".", "", 1))
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Container{
|
||||
store: storeFile,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (c *Container) set(key string, value interface{}) error {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
if key == "" {
|
||||
return fmt.Errorf("empty key not allowed")
|
||||
}
|
||||
|
||||
keys := strings.Split(key, ".")
|
||||
if len(keys) == 1 {
|
||||
viper.Set(key, value)
|
||||
} else {
|
||||
prePath := keys[0]
|
||||
for i := 1; i < len(keys)-2; i++ {
|
||||
prePath += "." + keys[i]
|
||||
}
|
||||
if viper.Get(prePath) == nil {
|
||||
return fmt.Errorf("%s not existed", prePath)
|
||||
}
|
||||
viper.Set(key, value)
|
||||
}
|
||||
// viper.Set(key, value)
|
||||
if err := viper.WriteConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Container) get(key string) interface{} {
|
||||
c.mux.Lock()
|
||||
defer c.mux.Unlock()
|
||||
|
||||
return viper.Get(key)
|
||||
}
|
||||
84
config/container_test.go
Normal file
84
config/container_test.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestContainer(t *testing.T) {
|
||||
configPath := "./tmp/container.yml"
|
||||
defer func() {
|
||||
if err := os.RemoveAll("./tmp/container.yml"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
c, err := CreateContainer(configPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := c.set("", ""); err == nil {
|
||||
t.Fatalf("should get error when key is empty")
|
||||
}
|
||||
|
||||
if c.get("1") != nil {
|
||||
t.Fatalf("should get %v but got %v", nil, c.get("key"))
|
||||
}
|
||||
|
||||
// create
|
||||
if err := c.set("1", "1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// read
|
||||
if c.get("1").(string) != "1" {
|
||||
t.Fatalf("should get %s but got %s", "val-1", c.get("key"))
|
||||
}
|
||||
|
||||
// invaliad set
|
||||
if err := c.set("1.1", "1.1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c.get("1.1").(string) != "1.1" {
|
||||
t.Fatalf("should get 1.1 but got %s", c.get("1.1"))
|
||||
}
|
||||
|
||||
// update
|
||||
if err := c.set("1", "11"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c.get("1").(string) != "11" {
|
||||
t.Fatalf("should get 11 but got %s", c.get("1").(string))
|
||||
}
|
||||
|
||||
// nested set
|
||||
if err := c.set("2.2.2.2", "2222"); err == nil {
|
||||
t.Fatalf("should throw error since 2.2.2 not ready yet")
|
||||
}
|
||||
|
||||
if err := c.set("2", map[string]interface{}{
|
||||
"2": map[string]interface{}{
|
||||
"2": "2",
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if c.get("2.2.2").(string) != "2" {
|
||||
t.Fatalf("should get 2 but got %s", c.get("2.2.2"))
|
||||
}
|
||||
if err := c.set("2.2.2.2", "2222"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if c.get("2.2.2.2").(string) != "2222" {
|
||||
t.Fatalf("should get 2222 but got %s", c.get("2.2.2.2"))
|
||||
}
|
||||
|
||||
if err := c.set("2.2.2.1", "1111"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if c.get("2.2.2.1").(string) != "1111" {
|
||||
t.Fatalf("should get 1111 but got %s", c.get("2.2.2.1"))
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package config
|
||||
|
||||
// CloudTypeDocker docker type
|
||||
const CloudTypeDocker = "docker"
|
||||
|
||||
// CloudTypeK8S k8s type
|
||||
const CloudTypeK8S = "k8s"
|
||||
2
fx.go
2
fx.go
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const version = "0.8.73"
|
||||
const version = "0.8.76"
|
||||
|
||||
func init() {
|
||||
go checkForUpdate()
|
||||
|
||||
5
go.mod
5
go.mod
@@ -22,7 +22,7 @@ require (
|
||||
github.com/gorilla/mux v1.7.3 // indirect
|
||||
github.com/imdario/mergo v0.3.7 // indirect
|
||||
github.com/logrusorgru/aurora v0.0.0-20191017060258-dc85c304c434
|
||||
github.com/metrue/go-ssh-client v0.0.0-20191125030649-4ac058ee958b
|
||||
github.com/metrue/go-ssh-client v0.0.0-20191209160027-5773243a8bc9
|
||||
github.com/mholt/archiver v3.1.1+incompatible
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
@@ -34,13 +34,12 @@ require (
|
||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2
|
||||
github.com/pierrec/lz4 v0.0.0-20190222153722-062282ea0dcf // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/spf13/viper v1.6.1
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/ugorji/go v1.1.7 // indirect
|
||||
github.com/urfave/cli v1.22.2
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
|
||||
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 // indirect
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
|
||||
google.golang.org/grpc v1.21.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.7
|
||||
gotest.tools v2.2.0+incompatible // indirect
|
||||
|
||||
77
go.sum
77
go.sum
@@ -15,8 +15,11 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||
github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/apex/log v1.1.1 h1:BwhRZ0qbjYtTob0I+2M+smavV0kOC8XgcnGZcyL9liA=
|
||||
github.com/apex/log v1.1.1/go.mod h1:Ls949n1HFtXfbDcjiTTFQqkVUrte0puoIBfO3SVgwOA=
|
||||
github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy8kCu4PNA+aP7WUV72eXWJeP9/r3/K9aLE=
|
||||
@@ -24,12 +27,18 @@ github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3st
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/briandowns/spinner v1.7.0 h1:aan1hBBOoscry2TXAkgtxkJiq7Se0+9pt+TUWaPrB4g=
|
||||
github.com/briandowns/spinner v1.7.0/go.mod h1://Zf9tMcxfRUA36V23M6YGEAv+kECGfvpnLTnb8n4XQ=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
@@ -39,6 +48,7 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v0.0.0-20190313072916-46036c230805 h1:Imk7y5LY4ljn+DhwaPVj9d8kvAxiZw8DQGwNmivIom0=
|
||||
@@ -61,16 +71,20 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3 h1:t8FVkw33L+wilf2QiWkw0UV77qRpcH/JHPKGpKa2E8g=
|
||||
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||
github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ=
|
||||
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
||||
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
|
||||
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
|
||||
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU=
|
||||
github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI=
|
||||
github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs=
|
||||
@@ -79,10 +93,13 @@ github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b
|
||||
github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg=
|
||||
github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk=
|
||||
github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d h1:3PaI8p3seN09VjbTYC/QWlUZdZ1qS1zGjy7LH2Wt07I=
|
||||
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
@@ -96,6 +113,7 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
@@ -113,9 +131,14 @@ github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsC
|
||||
github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk=
|
||||
github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
|
||||
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
@@ -128,14 +151,18 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
|
||||
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/karrick/godirwalk v1.10.12 h1:BqUm+LuJcXjGv1d2mj3gBiQyrQ57a0rYoAmhvJQ7RDU=
|
||||
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
@@ -151,7 +178,10 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/logrusorgru/aurora v0.0.0-20191017060258-dc85c304c434 h1:im9kkmH0WWwxzegiv18gSUJbuXR9y028rXrWuPp6Jug=
|
||||
github.com/logrusorgru/aurora v0.0.0-20191017060258-dc85c304c434/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||
@@ -165,8 +195,11 @@ github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1N
|
||||
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
|
||||
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/metrue/go-ssh-client v0.0.0-20191125030649-4ac058ee958b h1:JGD0sJ44XzhsT1voOg00zji4ubuMNcVNK3m7d9GI88k=
|
||||
github.com/metrue/go-ssh-client v0.0.0-20191125030649-4ac058ee958b/go.mod h1:ERHOEBrDy6+8vfoJjjmhdmBpOzdvvP7bLtwYTTK6LOs=
|
||||
github.com/metrue/go-ssh-client v0.0.0-20191209160027-5773243a8bc9 h1:HHfMhG77ZLn3FOH3AGXW/F5RpAABVH6Fr5mVZZ97S6w=
|
||||
github.com/metrue/go-ssh-client v0.0.0-20191209160027-5773243a8bc9/go.mod h1:aPG/JtXTyLliKDDlkv+nzHbSbz2p2CBMAjNJRK4uhzY=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU=
|
||||
github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU=
|
||||
@@ -184,9 +217,11 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
|
||||
github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs=
|
||||
github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/olekukonko/tablewriter v0.0.3 h1:i0LBnzgiChAWHJYTQAZJDOgf8MNxAVYZJ2m63SIDimI=
|
||||
github.com/olekukonko/tablewriter v0.0.3/go.mod h1:YZeBtGzYYEsCHp2LST/u/0NDwGkRoBtmn1cIWCJiS6M=
|
||||
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
|
||||
@@ -204,18 +239,30 @@ github.com/otiai10/copy v1.0.2 h1:DDNipYy6RkIkjMwy+AWzgKiNTyj2RUI9yEMeETEpVyc=
|
||||
github.com/otiai10/copy v1.0.2/go.mod h1:c7RpqBkwMom4bYTSkLSym4VSJz/XtncWRAj/J4PEIMY=
|
||||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
|
||||
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
|
||||
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
|
||||
github.com/pierrec/lz4 v0.0.0-20190222153722-062282ea0dcf h1:0d7SseXGaeqFXfRTLbiCkuLhSGEHZyKpz1XD3e5lbSo=
|
||||
github.com/pierrec/lz4 v0.0.0-20190222153722-062282ea0dcf/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA=
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk=
|
||||
@@ -227,12 +274,17 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
|
||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
@@ -240,11 +292,15 @@ github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/spf13/viper v1.6.1 h1:VPZzIkznI1YhVMRi6vNFLHSwhnhReBfgTxIPccpfdZk=
|
||||
github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
@@ -254,10 +310,13 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
|
||||
github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=
|
||||
github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=
|
||||
github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
@@ -272,8 +331,14 @@ github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
|
||||
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
|
||||
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
@@ -291,11 +356,14 @@ golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73r
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc h1:gkKoSkUmnU6bpS/VhkuO27bzQeSA51uaEfbOW5dNb68=
|
||||
@@ -315,6 +383,8 @@ golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -334,6 +404,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -341,6 +412,7 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c h1:KfpJVdWhuRqNk4XVXzjXf2KAV4TBEP77SYdFGjeGuIE=
|
||||
golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
@@ -356,6 +428,7 @@ google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRn
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -369,7 +442,11 @@ gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o=
|
||||
gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
@@ -2,52 +2,65 @@ package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/metrue/fx/config"
|
||||
"github.com/metrue/fx/context"
|
||||
dockerInfra "github.com/metrue/fx/infra/docker"
|
||||
"github.com/metrue/fx/infra/k8s"
|
||||
k8sInfra "github.com/metrue/fx/infra/k8s"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
)
|
||||
|
||||
func setupK8S(masterInfo string, agentsInfo string) ([]byte, error) {
|
||||
func setupK8S(configDir string, name, masterInfo string, agentsInfo string) ([]byte, error) {
|
||||
info := strings.Split(masterInfo, "@")
|
||||
if len(info) != 2 {
|
||||
return nil, fmt.Errorf("incorrect master info, should be <user>@<ip> format")
|
||||
}
|
||||
master := k8s.MasterNode{
|
||||
User: info[0],
|
||||
IP: info[1],
|
||||
master, err := k8sInfra.CreateNode(info[1], info[0], "k3s_master", "master")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
agents := []k8s.AgentNode{}
|
||||
nodes := []k8sInfra.Noder{master}
|
||||
if agentsInfo != "" {
|
||||
agentsInfoList := strings.Split(agentsInfo, ",")
|
||||
for _, agent := range agentsInfoList {
|
||||
for idx, agent := range agentsInfoList {
|
||||
info := strings.Split(agent, "@")
|
||||
if len(info) != 2 {
|
||||
return nil, fmt.Errorf("incorrect agent info, should be <user>@<ip> format")
|
||||
}
|
||||
agents = append(agents, k8s.AgentNode{
|
||||
User: info[0],
|
||||
IP: info[1],
|
||||
})
|
||||
node, err := k8sInfra.CreateNode(info[1], info[0], "k3s_agent", fmt.Sprintf("agent-%d", idx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
}
|
||||
|
||||
k8sOperator := k8s.New(master, agents)
|
||||
return k8sOperator.Provision()
|
||||
kubeconfigPath := filepath.Join(configDir, name+".kubeconfig")
|
||||
cloud := k8sInfra.NewCloud(kubeconfigPath, nodes...)
|
||||
if err := cloud.Provision(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cloud.Dump()
|
||||
}
|
||||
|
||||
func setupDocker(hostInfo string) ([]byte, error) {
|
||||
func setupDocker(hostInfo string, name string) ([]byte, error) {
|
||||
info := strings.Split(hostInfo, "@")
|
||||
if len(info) != 2 {
|
||||
return nil, fmt.Errorf("incorrect master info, should be <user>@<ip> format")
|
||||
}
|
||||
user := info[1]
|
||||
host := info[0]
|
||||
dockr := dockerInfra.CreateProvisioner(user, host)
|
||||
return dockr.Provision()
|
||||
user := info[0]
|
||||
host := info[1]
|
||||
|
||||
cloud, err := dockerInfra.Create(host, user, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := cloud.Provision(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cloud.Dump()
|
||||
}
|
||||
|
||||
// Setup infra
|
||||
@@ -80,17 +93,21 @@ func Setup(ctx context.Contexter) (err error) {
|
||||
|
||||
switch strings.ToLower(typ) {
|
||||
case "k8s":
|
||||
kubeconf, err := setupK8S(cli.String("master"), cli.String("agents"))
|
||||
dir, err := fxConfig.Dir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fxConfig.AddK8SCloud(name, kubeconf)
|
||||
kubeconf, err := setupK8S(dir, name, cli.String("master"), cli.String("agents"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fxConfig.AddCloud(name, kubeconf)
|
||||
case "docker":
|
||||
config, err := setupDocker(cli.String("host"))
|
||||
config, err := setupDocker(cli.String("host"), name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fxConfig.AddDockerCloud(name, config)
|
||||
return fxConfig.AddCloud(name, config)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,5 +9,5 @@ import (
|
||||
func UseInfra(ctx context.Contexter) error {
|
||||
fxConfig := ctx.Get("config").(*config.Config)
|
||||
cli := ctx.GetCliContext()
|
||||
return fxConfig.Use(cli.Args().First())
|
||||
return fxConfig.UseCloud(cli.Args().First())
|
||||
}
|
||||
|
||||
180
infra/docker/cloud.go
Normal file
180
infra/docker/cloud.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/metrue/go-ssh-client"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
// Cloud define a docker host
|
||||
type Cloud struct {
|
||||
IP string `json:"ip"`
|
||||
User string `json:"user"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
|
||||
sshClient ssh.Clienter
|
||||
}
|
||||
|
||||
// New new a docker cloud
|
||||
func New(ip string, user string, name string) *Cloud {
|
||||
return &Cloud{
|
||||
IP: ip,
|
||||
User: user,
|
||||
Name: name,
|
||||
Type: types.CloudTypeDocker,
|
||||
}
|
||||
}
|
||||
|
||||
// Create a docker node
|
||||
func Create(ip string, user string, name string) (*Cloud, error) {
|
||||
key, err := sshkey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
port := sshport()
|
||||
sshClient := ssh.New(ip).WithUser(user).WithKey(key).WithPort(port)
|
||||
return &Cloud{
|
||||
IP: ip,
|
||||
User: user,
|
||||
Name: name,
|
||||
Type: types.CloudTypeDocker,
|
||||
|
||||
sshClient: sshClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Load a docker node from meta
|
||||
func Load(meta []byte) (*Cloud, error) {
|
||||
var cloud Cloud
|
||||
if err := json.Unmarshal(meta, &cloud); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key, err := sshkey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
port := sshport()
|
||||
sshClient := ssh.New(cloud.IP).WithUser(cloud.User).WithKey(key).WithPort(port)
|
||||
cloud.sshClient = sshClient
|
||||
|
||||
return &cloud, nil
|
||||
}
|
||||
|
||||
// Provision a host
|
||||
func (c *Cloud) Provision() error {
|
||||
if err := c.runCmd(infra.Scripts["docker_version"].(string)); err != nil {
|
||||
if err := c.runCmd(infra.Scripts["install_docker"].(string)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.runCmd(infra.Scripts["start_dockerd"].(string)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.runCmd(infra.Scripts["check_fx_agent"].(string)); err != nil {
|
||||
if err := c.runCmd(infra.Scripts["start_fx_agent"].(string)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetType cloud type
|
||||
func (c *Cloud) GetType() string {
|
||||
return c.Type
|
||||
}
|
||||
|
||||
func (c *Cloud) GetConfig() (string, error) {
|
||||
data, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func (c *Cloud) Dump() ([]byte, error) {
|
||||
return json.Marshal(c)
|
||||
}
|
||||
|
||||
// IsHealth check if cloud is in health
|
||||
func (c *Cloud) IsHealth() (bool, error) {
|
||||
if err := c.runCmd(infra.Scripts["check_fx_agent"].(string)); err != nil {
|
||||
if err := c.runCmd(infra.Scripts["start_fx_agent"].(string)); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// NOTE only using for unit testing
|
||||
func (c *Cloud) setsshClient(client ssh.Clienter) {
|
||||
c.sshClient = client
|
||||
}
|
||||
|
||||
// nolint:unparam
|
||||
func (c *Cloud) runCmd(script string, options ...ssh.CommandOptions) error {
|
||||
option := ssh.CommandOptions{}
|
||||
if len(options) >= 1 {
|
||||
option = options[0]
|
||||
}
|
||||
|
||||
local := c.IP == "127.0.0.1" || c.IP == "localhost"
|
||||
if local && os.Getenv("CI") == "" {
|
||||
params := strings.Split(script, " ")
|
||||
if len(params) == 0 {
|
||||
return fmt.Errorf("invalid script: %s", script)
|
||||
}
|
||||
// nolint
|
||||
cmd := exec.Command(params[0], params[1:]...)
|
||||
cmd.Stdout = option.Stdout
|
||||
cmd.Stderr = option.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.sshClient.RunCommand(script, option)
|
||||
}
|
||||
|
||||
// NOTE the reason putting sshkey() and sshport here inside node.go is because
|
||||
// ssh key and ssh port is related to node it self, we may extend this in future
|
||||
func sshkey() (string, error) {
|
||||
path := os.Getenv("SSH_KEY_FILE")
|
||||
if path != "" {
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return absPath, nil
|
||||
}
|
||||
|
||||
key, err := homedir.Expand("~/.ssh/id_rsa")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func sshport() string {
|
||||
port := os.Getenv("SSH_PORT")
|
||||
if port != "" {
|
||||
return port
|
||||
}
|
||||
return "22"
|
||||
}
|
||||
|
||||
var (
|
||||
_ infra.Clouder = &Cloud{}
|
||||
)
|
||||
158
infra/docker/cloud_test.go
Normal file
158
infra/docker/cloud_test.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/go-ssh-client"
|
||||
sshMocks "github.com/metrue/go-ssh-client/mocks"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
func TestCloudProvision(t *testing.T) {
|
||||
t.Run("fx agent started", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
n := New("127.0.0.1", "fx", "master")
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
n.setsshClient(sshClient)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["docker_version"].(string), ssh.CommandOptions{}).Return(nil)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["check_fx_agent"].(string), ssh.CommandOptions{}).Return(nil)
|
||||
if err := n.Provision(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("fx agent not started", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
n := New("127.0.0.1", "fx", "master")
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
n.setsshClient(sshClient)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["docker_version"].(string), ssh.CommandOptions{}).Return(nil)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["check_fx_agent"].(string), ssh.CommandOptions{}).Return(fmt.Errorf("no such container"))
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["start_fx_agent"].(string), ssh.CommandOptions{}).Return(nil)
|
||||
if err := n.Provision(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("docker not installed and fx agent not started", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
n := New("127.0.0.1", "fx", "master")
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
n.setsshClient(sshClient)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["docker_version"].(string), ssh.CommandOptions{}).Return(fmt.Errorf("no such command"))
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["install_docker"].(string), ssh.CommandOptions{}).Return(nil)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["start_dockerd"].(string), ssh.CommandOptions{}).Return(nil)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["check_fx_agent"].(string), ssh.CommandOptions{}).Return(fmt.Errorf("no such container"))
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["start_fx_agent"].(string), ssh.CommandOptions{}).Return(nil)
|
||||
if err := n.Provision(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCloudIsHealth(t *testing.T) {
|
||||
t.Run("agent started", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
cloud := New("127.0.0.1", "fx", "master")
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
cloud.setsshClient(sshClient)
|
||||
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["check_fx_agent"].(string), ssh.CommandOptions{}).Return(nil)
|
||||
ok, err := cloud.IsHealth()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatalf("cloud should be healthy")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("agent not started, and retart ok", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
cloud := New("127.0.0.1", "fx", "master")
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
cloud.setsshClient(sshClient)
|
||||
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["check_fx_agent"].(string), ssh.CommandOptions{}).Return(fmt.Errorf("fx agent not started"))
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["start_fx_agent"].(string), ssh.CommandOptions{}).Return(nil)
|
||||
ok, err := cloud.IsHealth()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !ok {
|
||||
t.Fatalf("cloud should be healthy")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("agent not started, but restart failed", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
cloud := New("127.0.0.1", "fx", "master")
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
cloud.setsshClient(sshClient)
|
||||
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["check_fx_agent"].(string), ssh.CommandOptions{}).Return(fmt.Errorf("fx agent not started"))
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["start_fx_agent"].(string), ssh.CommandOptions{}).Return(fmt.Errorf("fx agent started failed"))
|
||||
ok, err := cloud.IsHealth()
|
||||
if err == nil {
|
||||
t.Fatal("should got failed starting")
|
||||
}
|
||||
if ok {
|
||||
t.Fatalf("cloud should not be healthy")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetSSHKeyFile(t *testing.T) {
|
||||
t.Run("defaut", func(t *testing.T) {
|
||||
defau, err := sshkey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defaultPath, _ := homedir.Expand("~/.ssh/id_rsa")
|
||||
if defau != defaultPath {
|
||||
t.Fatalf("should get %s but got %s", defaultPath, defau)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("override from env", func(t *testing.T) {
|
||||
os.Setenv("SSH_KEY_FILE", "/tmp/id_rsa")
|
||||
keyFile, err := sshkey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if keyFile != "/tmp/id_rsa" {
|
||||
t.Fatalf("should get %s but got %s", "/tmp/id_rsa", keyFile)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetSSHPort(t *testing.T) {
|
||||
t.Run("defaut", func(t *testing.T) {
|
||||
defau := sshport()
|
||||
if defau != "22" {
|
||||
t.Fatalf("should get %s but got %s", "22", defau)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("override from env", func(t *testing.T) {
|
||||
os.Setenv("SSH_PORT", "2222")
|
||||
defau := sshport()
|
||||
if defau != "2222" {
|
||||
t.Fatalf("should get %s but got %s", "2222", defau)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -2,11 +2,6 @@ package docker
|
||||
|
||||
import containerruntimes "github.com/metrue/fx/container_runtimes"
|
||||
|
||||
// CreateProvisioner create a provisioner
|
||||
func CreateProvisioner(ip string, user string) *Provisioner {
|
||||
return NewProvisioner(ip, user)
|
||||
}
|
||||
|
||||
// CreateDeployer create a deployer
|
||||
func CreateDeployer(client containerruntimes.ContainerRuntime) (*Deployer, error) {
|
||||
return &Deployer{cli: client}, nil
|
||||
|
||||
@@ -1,217 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/metrue/fx/constants"
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
sshOperator "github.com/metrue/go-ssh-client"
|
||||
)
|
||||
|
||||
// Provisioner docker host
|
||||
type Provisioner struct {
|
||||
IP string
|
||||
User string
|
||||
}
|
||||
|
||||
// NewProvisioner new a docker object
|
||||
func NewProvisioner(ip string, user string) *Provisioner {
|
||||
return &Provisioner{
|
||||
IP: ip,
|
||||
User: user,
|
||||
}
|
||||
}
|
||||
|
||||
// Provision provision a host, install docker and start dockerd
|
||||
func (d *Provisioner) Provision() (config []byte, err error) {
|
||||
spinner.Start("provisioning")
|
||||
defer func() {
|
||||
spinner.Stop("provisioning", err)
|
||||
}()
|
||||
|
||||
// TODO clean up, skip check localhost or not if in CICD env
|
||||
if os.Getenv("CICD") != "" {
|
||||
if err := d.Install(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := d.StartDockerd(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := d.StartFxAgent(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config, _ := json.Marshal(map[string]string{
|
||||
"ip": d.IP,
|
||||
"user": d.User,
|
||||
})
|
||||
return config, nil
|
||||
}
|
||||
|
||||
if d.isLocalHost() {
|
||||
if !d.hasDocker() {
|
||||
return nil, fmt.Errorf("please make sure docker installed and running")
|
||||
}
|
||||
|
||||
if err := d.StartFxAgentLocally(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config, _ := json.Marshal(map[string]string{
|
||||
"ip": d.IP,
|
||||
"user": d.User,
|
||||
})
|
||||
return config, nil
|
||||
}
|
||||
|
||||
if err := d.Install(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := d.StartDockerd(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := d.StartFxAgent(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(map[string]string{
|
||||
"ip": d.IP,
|
||||
"user": d.User,
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Provisioner) isLocalHost() bool {
|
||||
return strings.ToLower(d.IP) == "localhost" || d.IP == "127.0.0.1"
|
||||
}
|
||||
|
||||
func (d *Provisioner) hasDocker() bool {
|
||||
cmd := exec.Command("docker", "version")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// HealthCheck check healthy status of host
|
||||
func (d *Provisioner) HealthCheck() (bool, error) {
|
||||
if d.isLocalHost() {
|
||||
return d.IfFxAgentRunningLocally(), nil
|
||||
}
|
||||
return d.IfFxAgentRunning(), nil
|
||||
}
|
||||
|
||||
// Install docker on host
|
||||
func (d *Provisioner) Install() error {
|
||||
sudo := ""
|
||||
if d.User != "root" {
|
||||
sudo = "sudo"
|
||||
}
|
||||
installCmd := fmt.Sprintf("curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-18.06.3-ce.tgz -o docker.tgz && tar zxvf docker.tgz && %s mv docker/* /usr/bin && rm -rf docker docker.tgz", sudo)
|
||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||
sshPort := infra.GetSSHPort()
|
||||
ssh := sshOperator.New(d.IP).WithUser(d.User).WithKey(sshKeyFile).WithPort(sshPort)
|
||||
if err := ssh.RunCommand(installCmd, sshOperator.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}); err != nil {
|
||||
fmt.Println("install docker failed \n================")
|
||||
fmt.Println(err)
|
||||
fmt.Println("================")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartDockerd start dockerd
|
||||
func (d *Provisioner) StartDockerd() error {
|
||||
sudo := ""
|
||||
if d.User != "root" {
|
||||
sudo = "sudo"
|
||||
}
|
||||
installCmd := fmt.Sprintf("%s dockerd >/dev/null 2>&1 & sleep 2", sudo)
|
||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||
sshPort := infra.GetSSHPort()
|
||||
ssh := sshOperator.New(d.IP).WithUser(d.User).WithKey(sshKeyFile).WithPort(sshPort)
|
||||
if err := ssh.RunCommand(installCmd, sshOperator.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}); err != nil {
|
||||
fmt.Println("start dockerd failed \n================")
|
||||
fmt.Println(err)
|
||||
fmt.Println("================")
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartFxAgent start fx agent
|
||||
func (d *Provisioner) StartFxAgent() error {
|
||||
startCmd := fmt.Sprintf("sleep 3 && docker stop %s || true && docker run -d --name=%s --rm -v /var/run/docker.sock:/var/run/docker.sock -p 0.0.0.0:%s:1234 bobrik/socat TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock", constants.AgentContainerName, constants.AgentContainerName, constants.AgentPort)
|
||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||
sshPort := infra.GetSSHPort()
|
||||
ssh := sshOperator.New(d.IP).WithUser(d.User).WithKey(sshKeyFile).WithPort(sshPort)
|
||||
if err := ssh.RunCommand(startCmd, sshOperator.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}); err != nil {
|
||||
fmt.Println("start fx agent failed \n================")
|
||||
fmt.Println(err)
|
||||
fmt.Println("================")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartFxAgentLocally start fx agent
|
||||
func (d *Provisioner) StartFxAgentLocally() error {
|
||||
startCmd := fmt.Sprintf("docker run -d --name=%s --rm -v /var/run/docker.sock:/var/run/docker.sock -p 0.0.0.0:%s:1234 bobrik/socat TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock", constants.AgentContainerName, constants.AgentPort)
|
||||
params := strings.Split(startCmd, " ")
|
||||
var cmd *exec.Cmd
|
||||
if len(params) > 1 {
|
||||
// nolint: gosec
|
||||
cmd = exec.Command(params[0], params[1:]...)
|
||||
} else {
|
||||
// nolint: gosec
|
||||
cmd = exec.Command(params[0])
|
||||
}
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
fmt.Println(string(out))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IfFxAgentRunningLocally check if fx agent is running
|
||||
func (d *Provisioner) IfFxAgentRunningLocally() bool {
|
||||
cmd := exec.Command("docker", "inspect", "fx-agent")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// IfFxAgentRunning check if fx agent is running
|
||||
func (d *Provisioner) IfFxAgentRunning() bool {
|
||||
inspectCmd := infra.Sudo("docker inspect fx-agent", d.User)
|
||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||
sshPort := infra.GetSSHPort()
|
||||
ssh := sshOperator.New(d.IP).WithUser(d.User).WithKey(sshKeyFile).WithPort(sshPort)
|
||||
if err := ssh.RunCommand(inspectCmd, sshOperator.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}); err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
var _ infra.Provisioner = &Provisioner{}
|
||||
@@ -1,23 +0,0 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProvisioner(t *testing.T) {
|
||||
if os.Getenv("DOCKER_HOST") == "" ||
|
||||
os.Getenv("DOCKER_USER") == "" {
|
||||
t.Skip("skip test since DOCKER_HOST and DOCKER_USER not ready")
|
||||
}
|
||||
d := NewProvisioner(os.Getenv("DOCKER_HOST"), os.Getenv("DOCKER_USER"))
|
||||
if err := d.Install(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := d.StartDockerd(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := d.StartFxAgent(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,13 @@ import (
|
||||
"github.com/metrue/fx/types"
|
||||
)
|
||||
|
||||
// Provisioner provision interface
|
||||
type Provisioner interface {
|
||||
Provision() (config []byte, err error)
|
||||
HealthCheck() (bool, error)
|
||||
// Clouder cloud interface
|
||||
type Clouder interface {
|
||||
Provision() error
|
||||
GetConfig() (string, error)
|
||||
GetType() string
|
||||
Dump() ([]byte, error)
|
||||
IsHealth() (bool, error)
|
||||
}
|
||||
|
||||
// Deployer deploy interface
|
||||
@@ -24,6 +27,5 @@ type Deployer interface {
|
||||
|
||||
// Infra infrastructure provision interface
|
||||
type Infra interface {
|
||||
Provisioner
|
||||
Deployer
|
||||
}
|
||||
|
||||
270
infra/k8s/cloud.go
Normal file
270
infra/k8s/cloud.go
Normal file
@@ -0,0 +1,270 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/metrue/fx/utils"
|
||||
)
|
||||
|
||||
// Cloud define a cloud
|
||||
type Cloud struct {
|
||||
// Define where is the location of kubeconf would be saved to
|
||||
KubeConfig string `json:"config"`
|
||||
Type string `json:"type"`
|
||||
Nodes map[string]Noder `json:"nodes"`
|
||||
|
||||
token string
|
||||
url string
|
||||
}
|
||||
|
||||
// Load a cloud from config
|
||||
func Load(meta []byte, messup ...func(n Noder) (Noder, error)) (*Cloud, error) {
|
||||
var cloud Cloud
|
||||
if err := json.Unmarshal(meta, &cloud); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for name, n := range cloud.Nodes {
|
||||
// NOTE messup function is just for unit testing
|
||||
// we use it to replace the real not with mock node
|
||||
if len(messup) > 0 {
|
||||
node, err := messup[0](n)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cloud.Nodes[name] = node
|
||||
}
|
||||
}
|
||||
return &cloud, nil
|
||||
}
|
||||
|
||||
// NewCloud new a cloud
|
||||
func NewCloud(kubeconf string, node ...Noder) *Cloud {
|
||||
nodes := map[string]Noder{}
|
||||
for _, n := range node {
|
||||
nodes[n.GetName()] = n
|
||||
}
|
||||
|
||||
return &Cloud{
|
||||
KubeConfig: kubeconf,
|
||||
Type: types.CloudTypeK8S,
|
||||
Nodes: nodes,
|
||||
}
|
||||
}
|
||||
|
||||
// Provision provision cloud
|
||||
func (c *Cloud) Provision() error {
|
||||
var master Noder
|
||||
agents := []Noder{}
|
||||
for _, n := range c.Nodes {
|
||||
if n.GetType() == NodeTypeMaster {
|
||||
master = n
|
||||
} else {
|
||||
agents = append(agents, n)
|
||||
}
|
||||
}
|
||||
|
||||
// when it's k3s cluster
|
||||
if master != nil {
|
||||
c.url = fmt.Sprintf("https://%s:6443", master.GetIP())
|
||||
if err := master.Provision(map[string]string{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tok, err := master.GetToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.token = tok
|
||||
|
||||
config, err := master.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := utils.EnsureFile(c.KubeConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(c.KubeConfig, []byte(config), 0666); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(agents) > 0 {
|
||||
errCh := make(chan error, len(agents))
|
||||
defer close(errCh)
|
||||
|
||||
for _, agent := range agents {
|
||||
go func(node Noder) {
|
||||
errCh <- node.Provision(map[string]string{
|
||||
"url": c.url,
|
||||
"token": c.token,
|
||||
})
|
||||
}(agent)
|
||||
}
|
||||
|
||||
for range agents {
|
||||
err := <-errCh
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddNode a node
|
||||
func (c *Cloud) AddNode(n Noder, skipProvision bool) error {
|
||||
if !skipProvision {
|
||||
if err := n.Provision(map[string]string{
|
||||
"url": c.url,
|
||||
"token": c.token,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
c.Nodes[n.GetName()] = n
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteNode a node
|
||||
func (c *Cloud) DeleteNode(name string) error {
|
||||
node, ok := c.Nodes[name]
|
||||
if ok {
|
||||
delete(c.Nodes, name)
|
||||
}
|
||||
if node.GetType() == NodeTypeMaster && len(c.Nodes) > 0 {
|
||||
return fmt.Errorf("could not delete master node since there is still agent node running")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// State get cloud state
|
||||
func (c *Cloud) State() {}
|
||||
|
||||
// UnmarshalJSON unmarsha json
|
||||
func (c *Cloud) UnmarshalJSON(data []byte) error {
|
||||
var m map[string]interface{}
|
||||
if err := json.Unmarshal(data, &m); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Nodes = make(map[string]Noder)
|
||||
|
||||
for k, v := range m {
|
||||
if k == "nodes" {
|
||||
nodes, ok := v.(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid nodes data")
|
||||
}
|
||||
for name, n := range nodes {
|
||||
node, ok := n.(map[string]interface{})
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid node data")
|
||||
}
|
||||
n, err := CreateNode(node["ip"].(string), node["user"].(string), node["type"].(string), node["name"].(string))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Nodes[name] = n
|
||||
}
|
||||
} else if k == "token" {
|
||||
tok, ok := v.(string)
|
||||
if ok {
|
||||
c.token = tok
|
||||
} else {
|
||||
c.token = ""
|
||||
}
|
||||
} else if k == "config" {
|
||||
config, ok := v.(string)
|
||||
if ok {
|
||||
c.KubeConfig = config
|
||||
} else {
|
||||
c.KubeConfig = ""
|
||||
}
|
||||
} else if k == "type" {
|
||||
typ, ok := v.(string)
|
||||
if ok {
|
||||
c.Type = typ
|
||||
} else {
|
||||
c.Type = ""
|
||||
}
|
||||
} else if k == "url" {
|
||||
url, ok := v.(string)
|
||||
if ok {
|
||||
c.url = url
|
||||
} else {
|
||||
c.url = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON cloud information
|
||||
func (c *Cloud) MarshalJSON() ([]byte, error) {
|
||||
nodes := map[string]Node{}
|
||||
for name, node := range c.Nodes {
|
||||
nodes[name] = Node{
|
||||
IP: node.GetIP(),
|
||||
Type: node.GetType(),
|
||||
User: node.GetUser(),
|
||||
Name: node.GetName(),
|
||||
}
|
||||
}
|
||||
|
||||
body, err := json.Marshal(struct {
|
||||
URL string `json:"url"`
|
||||
KubeConfig string `json:"config"`
|
||||
Type string `json:"type"`
|
||||
Token string `json:"token"`
|
||||
Nodes map[string]Node `json:"nodes"`
|
||||
}{
|
||||
KubeConfig: c.KubeConfig,
|
||||
Type: c.Type,
|
||||
Token: c.token,
|
||||
URL: c.url,
|
||||
Nodes: nodes,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return body, nil
|
||||
}
|
||||
|
||||
// GetType get type of cloud
|
||||
func (c *Cloud) GetType() string {
|
||||
return c.Type
|
||||
}
|
||||
|
||||
// Dump cloud data
|
||||
func (c *Cloud) Dump() ([]byte, error) {
|
||||
return json.Marshal(c)
|
||||
}
|
||||
|
||||
// GetConfig get config
|
||||
func (c *Cloud) GetConfig() (string, error) {
|
||||
if c.KubeConfig != "" {
|
||||
return c.KubeConfig, nil
|
||||
}
|
||||
if err := c.Provision(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return c.KubeConfig, nil
|
||||
}
|
||||
|
||||
// IsHealth check if cloud is in health
|
||||
func (c *Cloud) IsHealth() (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
var (
|
||||
_ infra.Clouder = &Cloud{}
|
||||
)
|
||||
170
infra/k8s/cloud_test.go
Normal file
170
infra/k8s/cloud_test.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
mock_infra "github.com/metrue/fx/infra/k8s/mocks"
|
||||
)
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
t.Run("empty meta", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
var createNodeFn = func(n Noder) (Noder, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
_, err := Load([]byte{}, createNodeFn)
|
||||
if err == nil {
|
||||
t.Fatalf("should load with error")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("only master node", func(t *testing.T) {
|
||||
kubeconfig := "./kubeconfig.yml"
|
||||
defer func() {
|
||||
if err := os.RemoveAll("./kubeconfig.yml"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
master := mock_infra.NewMockNoder(ctrl)
|
||||
var createNodeFn = func(n Noder) (Noder, error) {
|
||||
return master, nil
|
||||
}
|
||||
typ := NodeTypeMaster
|
||||
name := "master"
|
||||
ip := "127.0.0.1"
|
||||
user := "testuser"
|
||||
kubeconfContent := "sample-content"
|
||||
master.EXPECT().GetName().Return(name)
|
||||
master.EXPECT().GetType().Return(typ).Times(2)
|
||||
master.EXPECT().GetIP().Return(ip).Times(2)
|
||||
master.EXPECT().GetUser().Return(user)
|
||||
master.EXPECT().GetConfig().Return(kubeconfContent, nil)
|
||||
|
||||
claud := &Cloud{
|
||||
KubeConfig: kubeconfig,
|
||||
Type: "k8s",
|
||||
url: "",
|
||||
token: "",
|
||||
Nodes: map[string]Noder{"master-node": master},
|
||||
}
|
||||
|
||||
meta, err := json.Marshal(claud)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cloud, err := Load(meta, createNodeFn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(cloud.Nodes) != 1 {
|
||||
t.Fatalf("should get %d but got %d", 1, len(cloud.Nodes))
|
||||
}
|
||||
|
||||
master.EXPECT().Provision(map[string]string{}).Return(nil)
|
||||
master.EXPECT().GetToken().Return("tok-1", nil)
|
||||
if err := cloud.Provision(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
content, err := ioutil.ReadFile(claud.KubeConfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(content) != kubeconfContent {
|
||||
t.Fatalf("should get %s but got %s", kubeconfContent, content)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("one master node and one agent", func(t *testing.T) {
|
||||
kubeconfig := "./kubeconfig.yml"
|
||||
defer func() {
|
||||
if err := os.RemoveAll("./kubeconfig.yml"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
master := mock_infra.NewMockNoder(ctrl)
|
||||
node := mock_infra.NewMockNoder(ctrl)
|
||||
var createNodeFn = func(n Noder) (Noder, error) {
|
||||
if n.GetType() == NodeTypeMaster {
|
||||
return master, nil
|
||||
}
|
||||
return node, nil
|
||||
}
|
||||
typ := NodeTypeMaster
|
||||
name := "master"
|
||||
ip := "127.0.0.1"
|
||||
user := "testuser"
|
||||
kubeconfContent := "sample-config"
|
||||
master.EXPECT().GetName().Return(name)
|
||||
master.EXPECT().GetType().Return(typ).Times(2)
|
||||
master.EXPECT().GetIP().Return(ip).Times(3)
|
||||
master.EXPECT().GetConfig().Return(kubeconfContent, nil)
|
||||
master.EXPECT().GetUser().Return(user)
|
||||
|
||||
nodeType := NodeTypeAgent
|
||||
nodeName := "agent_name"
|
||||
nodeIP := "12.12.12.12"
|
||||
nodeUser := "testuser"
|
||||
node.EXPECT().GetName().Return(nodeName)
|
||||
node.EXPECT().GetType().Return(nodeType).Times(2)
|
||||
node.EXPECT().GetIP().Return(nodeIP)
|
||||
node.EXPECT().GetUser().Return(nodeUser)
|
||||
|
||||
url := fmt.Sprintf("https://%s:6443", master.GetIP())
|
||||
tok := "tok-1"
|
||||
claud := &Cloud{
|
||||
KubeConfig: kubeconfig,
|
||||
url: url,
|
||||
token: tok,
|
||||
Type: "k8s",
|
||||
Nodes: map[string]Noder{"master-node": master, "agent-node": node},
|
||||
}
|
||||
meta, err := json.Marshal(claud)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cloud, err := Load(meta, createNodeFn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(cloud.Nodes) != 2 {
|
||||
t.Fatalf("should get %d but got %d", 2, len(cloud.Nodes))
|
||||
}
|
||||
|
||||
master.EXPECT().Provision(map[string]string{}).Return(nil)
|
||||
master.EXPECT().GetToken().Return(tok, nil)
|
||||
node.EXPECT().Provision(map[string]string{
|
||||
"url": cloud.url,
|
||||
"token": cloud.token,
|
||||
}).Return(nil)
|
||||
if err := cloud.Provision(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
content, err := ioutil.ReadFile(claud.KubeConfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(content) != kubeconfContent {
|
||||
t.Fatalf("should get %s but got %s", kubeconfContent, content)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestProvision(t *testing.T) {}
|
||||
5
infra/k8s/doc.go
Normal file
5
infra/k8s/doc.go
Normal file
@@ -0,0 +1,5 @@
|
||||
/*
|
||||
|
||||
*/
|
||||
|
||||
package k8s
|
||||
@@ -1,10 +1,5 @@
|
||||
package k8s
|
||||
|
||||
// CreateProvisioner create a provisioner
|
||||
func CreateProvisioner(master MasterNode, agents []AgentNode) *Provisioner {
|
||||
return New(master, agents)
|
||||
}
|
||||
|
||||
// CreateDeployer create a deployer
|
||||
func CreateDeployer(kubeconfig string) (*K8S, error) {
|
||||
return Create(kubeconfig)
|
||||
|
||||
15
infra/k8s/k8s_node.go
Normal file
15
infra/k8s/k8s_node.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// ListNodes list node
|
||||
func (k *K8S) ListNodes() (*v1.NodeList, error) {
|
||||
nodes, err := k.CoreV1().Nodes().List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
147
infra/k8s/mocks/node.go
Normal file
147
infra/k8s/mocks/node.go
Normal file
@@ -0,0 +1,147 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: node.go
|
||||
|
||||
// Package mock_k8s is a generated GoMock package.
|
||||
package mock_k8s
|
||||
|
||||
import (
|
||||
gomock "github.com/golang/mock/gomock"
|
||||
reflect "reflect"
|
||||
)
|
||||
|
||||
// MockNoder is a mock of Noder interface
|
||||
type MockNoder struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockNoderMockRecorder
|
||||
}
|
||||
|
||||
// MockNoderMockRecorder is the mock recorder for MockNoder
|
||||
type MockNoderMockRecorder struct {
|
||||
mock *MockNoder
|
||||
}
|
||||
|
||||
// NewMockNoder creates a new mock instance
|
||||
func NewMockNoder(ctrl *gomock.Controller) *MockNoder {
|
||||
mock := &MockNoder{ctrl: ctrl}
|
||||
mock.recorder = &MockNoderMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use
|
||||
func (m *MockNoder) EXPECT() *MockNoderMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Provision mocks base method
|
||||
func (m *MockNoder) Provision(meta map[string]string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Provision", meta)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Provision indicates an expected call of Provision
|
||||
func (mr *MockNoderMockRecorder) Provision(meta interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Provision", reflect.TypeOf((*MockNoder)(nil).Provision), meta)
|
||||
}
|
||||
|
||||
// GetConfig mocks base method
|
||||
func (m *MockNoder) GetConfig() (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetConfig")
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetConfig indicates an expected call of GetConfig
|
||||
func (mr *MockNoderMockRecorder) GetConfig() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetConfig", reflect.TypeOf((*MockNoder)(nil).GetConfig))
|
||||
}
|
||||
|
||||
// GetType mocks base method
|
||||
func (m *MockNoder) GetType() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetType")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetType indicates an expected call of GetType
|
||||
func (mr *MockNoderMockRecorder) GetType() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetType", reflect.TypeOf((*MockNoder)(nil).GetType))
|
||||
}
|
||||
|
||||
// GetName mocks base method
|
||||
func (m *MockNoder) GetName() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetName")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetName indicates an expected call of GetName
|
||||
func (mr *MockNoderMockRecorder) GetName() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetName", reflect.TypeOf((*MockNoder)(nil).GetName))
|
||||
}
|
||||
|
||||
// GetUser mocks base method
|
||||
func (m *MockNoder) GetUser() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetUser")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetUser indicates an expected call of GetUser
|
||||
func (mr *MockNoderMockRecorder) GetUser() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUser", reflect.TypeOf((*MockNoder)(nil).GetUser))
|
||||
}
|
||||
|
||||
// GetToken mocks base method
|
||||
func (m *MockNoder) GetToken() (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetToken")
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetToken indicates an expected call of GetToken
|
||||
func (mr *MockNoderMockRecorder) GetToken() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetToken", reflect.TypeOf((*MockNoder)(nil).GetToken))
|
||||
}
|
||||
|
||||
// GetIP mocks base method
|
||||
func (m *MockNoder) GetIP() string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetIP")
|
||||
ret0, _ := ret[0].(string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// GetIP indicates an expected call of GetIP
|
||||
func (mr *MockNoderMockRecorder) GetIP() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetIP", reflect.TypeOf((*MockNoder)(nil).GetIP))
|
||||
}
|
||||
|
||||
// Dump mocks base method
|
||||
func (m *MockNoder) Dump() map[string]string {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Dump")
|
||||
ret0, _ := ret[0].(map[string]string)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Dump indicates an expected call of Dump
|
||||
func (mr *MockNoderMockRecorder) Dump() *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Dump", reflect.TypeOf((*MockNoder)(nil).Dump))
|
||||
}
|
||||
@@ -1,15 +1,216 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/go-ssh-client"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
// ListNodes list node
|
||||
func (k *K8S) ListNodes() (*v1.NodeList, error) {
|
||||
nodes, err := k.CoreV1().Nodes().List(metav1.ListOptions{})
|
||||
const NodeTypeMaster = "k3s_master"
|
||||
const NodeTypeAgent = "k3s_agent"
|
||||
const NodeTypeDocker = "docker_agent"
|
||||
|
||||
// Noder node interface
|
||||
type Noder interface {
|
||||
Provision(meta map[string]string) error
|
||||
GetConfig() (string, error)
|
||||
GetType() string
|
||||
GetName() string
|
||||
GetUser() string
|
||||
GetToken() (string, error)
|
||||
GetIP() string
|
||||
Dump() map[string]string
|
||||
}
|
||||
|
||||
// Node define a node
|
||||
type Node struct {
|
||||
IP string `json:"ip"`
|
||||
User string `json:"user"`
|
||||
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
|
||||
sshClient ssh.Clienter
|
||||
}
|
||||
|
||||
// CreateNode create a node
|
||||
func CreateNode(ip string, user string, typ string, name string) (*Node, error) {
|
||||
key, err := sshkey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nodes, nil
|
||||
port := sshport()
|
||||
sshClient := ssh.New(ip).WithUser(user).WithKey(key).WithPort(port)
|
||||
|
||||
return &Node{
|
||||
IP: ip,
|
||||
User: user,
|
||||
Type: typ,
|
||||
Name: name,
|
||||
|
||||
sshClient: sshClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (n *Node) runCmd(script string) error {
|
||||
return n.sshClient.RunCommand(script, ssh.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
})
|
||||
}
|
||||
|
||||
// Provision provision node
|
||||
func (n *Node) Provision(meta map[string]string) error {
|
||||
if err := n.runCmd(infra.Scripts["docker_version"].(string)); err != nil {
|
||||
if err := n.runCmd(infra.Scripts["install_docker"].(string)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := n.runCmd(infra.Scripts["start_dockerd"].(string)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if n.Type == NodeTypeMaster {
|
||||
if err := n.runCmd(infra.Scripts["check_k3s_server"].(string)); err != nil {
|
||||
cmd := infra.Scripts["setup_k3s_master"].(func(ip string) string)(n.IP)
|
||||
if err := n.runCmd(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if n.Type == NodeTypeAgent {
|
||||
if err := n.runCmd(infra.Scripts["check_k3s_agent"].(string)); err != nil {
|
||||
cmd := infra.Scripts["setup_k3s_agent"].(func(url string, tok string) string)(meta["url"], meta["token"])
|
||||
if err := n.runCmd(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetToken get token from master node
|
||||
func (n *Node) GetToken() (string, error) {
|
||||
if n.Type != NodeTypeMaster {
|
||||
return "", fmt.Errorf("could not get token from a non-master node")
|
||||
}
|
||||
var outPipe bytes.Buffer
|
||||
if err := n.sshClient.RunCommand(infra.Scripts["get_k3s_token"].(string), ssh.CommandOptions{Stdout: bufio.NewWriter(&outPipe)}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return outPipe.String(), nil
|
||||
}
|
||||
|
||||
// State get node state
|
||||
func (n *Node) State() {}
|
||||
|
||||
// Dump node information to json
|
||||
func (n *Node) Dump() map[string]string {
|
||||
return map[string]string{
|
||||
"ip": n.IP,
|
||||
"name": n.Name,
|
||||
"user": n.User,
|
||||
"type": n.Type,
|
||||
}
|
||||
}
|
||||
|
||||
// GetType get node type
|
||||
func (n *Node) GetType() string {
|
||||
return n.Type
|
||||
}
|
||||
|
||||
// GetName get node type
|
||||
func (n *Node) GetName() string {
|
||||
return n.Name
|
||||
}
|
||||
|
||||
// GetIP get node type
|
||||
func (n *Node) GetIP() string {
|
||||
return n.IP
|
||||
}
|
||||
|
||||
// GetUser get user
|
||||
func (n *Node) GetUser() string {
|
||||
return n.User
|
||||
}
|
||||
|
||||
// GetConfig get config
|
||||
func (n *Node) GetConfig() (string, error) {
|
||||
if n.Type == NodeTypeMaster {
|
||||
var outPipe bytes.Buffer
|
||||
if err := n.sshClient.RunCommand(infra.Scripts["get_k3s_kubeconfig"].(string), ssh.CommandOptions{
|
||||
Stdout: bufio.NewWriter(&outPipe),
|
||||
}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(rewriteKubeconfig(outPipe.String(), n.IP, "default")), nil
|
||||
} else if n.Type == NodeTypeDocker {
|
||||
data, err := json.Marshal(n.Dump())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
return "", fmt.Errorf("no config for node type of %s", n.Type)
|
||||
}
|
||||
|
||||
// NOTE only using for unit testing
|
||||
func (n *Node) setsshClient(client ssh.Clienter) {
|
||||
n.sshClient = client
|
||||
}
|
||||
|
||||
// NOTE the reason putting sshkey() and sshport here inside node.go is because
|
||||
// ssh key and ssh port is related to node it self, we may extend this in future
|
||||
func sshkey() (string, error) {
|
||||
path := os.Getenv("SSH_KEY_FILE")
|
||||
if path != "" {
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return absPath, nil
|
||||
}
|
||||
|
||||
key, err := homedir.Expand("~/.ssh/id_rsa")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func sshport() string {
|
||||
port := os.Getenv("SSH_PORT")
|
||||
if port != "" {
|
||||
return port
|
||||
}
|
||||
return "22"
|
||||
}
|
||||
|
||||
func rewriteKubeconfig(kubeconfig string, ip string, context string) []byte {
|
||||
if context == "" {
|
||||
// nolint
|
||||
context = "default"
|
||||
}
|
||||
|
||||
kubeconfigReplacer := strings.NewReplacer(
|
||||
"127.0.0.1", ip,
|
||||
"localhost", ip,
|
||||
"default", context,
|
||||
)
|
||||
|
||||
return []byte(kubeconfigReplacer.Replace(kubeconfig))
|
||||
}
|
||||
|
||||
var (
|
||||
_ Noder = &Node{}
|
||||
)
|
||||
|
||||
211
infra/k8s/node_test.go
Normal file
211
infra/k8s/node_test.go
Normal file
@@ -0,0 +1,211 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/go-ssh-client"
|
||||
sshMocks "github.com/metrue/go-ssh-client/mocks"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
func TestGetSSHKeyFile(t *testing.T) {
|
||||
t.Run("defaut", func(t *testing.T) {
|
||||
defau, err := sshkey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defaultPath, _ := homedir.Expand("~/.ssh/id_rsa")
|
||||
if defau != defaultPath {
|
||||
t.Fatalf("should get %s but got %s", defaultPath, defau)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("override from env", func(t *testing.T) {
|
||||
os.Setenv("SSH_KEY_FILE", "/tmp/id_rsa")
|
||||
keyFile, err := sshkey()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if keyFile != "/tmp/id_rsa" {
|
||||
t.Fatalf("should get %s but got %s", "/tmp/id_rsa", keyFile)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetSSHPort(t *testing.T) {
|
||||
t.Run("defaut", func(t *testing.T) {
|
||||
defau := sshport()
|
||||
if defau != "22" {
|
||||
t.Fatalf("should get %s but got %s", "22", defau)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("override from env", func(t *testing.T) {
|
||||
os.Setenv("SSH_PORT", "2222")
|
||||
defau := sshport()
|
||||
if defau != "2222" {
|
||||
t.Fatalf("should get %s but got %s", "2222", defau)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestNode(t *testing.T) {
|
||||
t.Run("master node already has docker and k3s server", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
n, err := CreateNode("127.0.0.1", "fx", NodeTypeMaster, "master")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if n.sshClient == nil {
|
||||
t.Fatal("ssh client should not be nil")
|
||||
}
|
||||
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
n.setsshClient(sshClient)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["docker_version"].(string), ssh.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}).Return(nil)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["check_k3s_server"].(string), ssh.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}).Return(nil)
|
||||
if err := n.Provision(map[string]string{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
t.Run("master node no docker and k3s server", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
n, err := CreateNode("127.0.0.1", "fx", NodeTypeMaster, "master")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if n.sshClient == nil {
|
||||
t.Fatal("ssh client should not be nil")
|
||||
}
|
||||
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
n.setsshClient(sshClient)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["docker_version"].(string), ssh.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}).Return(fmt.Errorf("no such command"))
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["install_docker"].(string), ssh.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}).Return(nil)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["start_dockerd"].(string), ssh.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}).Return(nil)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["check_k3s_server"].(string), ssh.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}).Return(fmt.Errorf("no such progress"))
|
||||
|
||||
cmd := infra.Scripts["setup_k3s_master"].(func(ip string) string)(n.IP)
|
||||
sshClient.EXPECT().RunCommand(cmd, ssh.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}).Return(nil)
|
||||
if err := n.Provision(map[string]string{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("agent node already has docker and k3s agent", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
n, err := CreateNode("127.0.0.1", "fx", NodeTypeAgent, "agent")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if n.sshClient == nil {
|
||||
t.Fatal("ssh client should not be nil")
|
||||
}
|
||||
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
n.setsshClient(sshClient)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["docker_version"].(string), ssh.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}).Return(nil)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["check_k3s_agent"].(string), ssh.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}).Return(nil)
|
||||
if err := n.Provision(map[string]string{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
t.Run("agent node no docker and k3s agent", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
n, err := CreateNode("127.0.0.1", "fx", NodeTypeAgent, "agent")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if n.sshClient == nil {
|
||||
t.Fatal("ssh client should not be nil")
|
||||
}
|
||||
|
||||
sshClient := sshMocks.NewMockClienter(ctrl)
|
||||
n.setsshClient(sshClient)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["docker_version"].(string), ssh.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}).Return(fmt.Errorf("no such command"))
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["install_docker"].(string), ssh.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}).Return(nil)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["start_dockerd"].(string), ssh.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}).Return(nil)
|
||||
sshClient.EXPECT().RunCommand(infra.Scripts["check_k3s_agent"].(string), ssh.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}).Return(fmt.Errorf("no such progress"))
|
||||
|
||||
url := "url-1"
|
||||
token := "token-1"
|
||||
cmd := infra.Scripts["setup_k3s_agent"].(func(url string, ip string) string)(url, token)
|
||||
sshClient.EXPECT().RunCommand(cmd, ssh.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}).Return(nil)
|
||||
if err := n.Provision(map[string]string{"url": url, "token": token}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/metrue/fx/infra"
|
||||
sshOperator "github.com/metrue/go-ssh-client"
|
||||
)
|
||||
|
||||
// MasterNode master node instance
|
||||
type MasterNode struct {
|
||||
IP string
|
||||
User string
|
||||
}
|
||||
|
||||
// AgentNode agent node instance
|
||||
type AgentNode struct {
|
||||
IP string
|
||||
User string
|
||||
}
|
||||
|
||||
// Provisioner k3s operator
|
||||
type Provisioner struct {
|
||||
master MasterNode
|
||||
agents []AgentNode
|
||||
}
|
||||
|
||||
// TODO upgrade to latest when k3s fix the tls scan issue
|
||||
// https://github.com/rancher/k3s/issues/556
|
||||
const version = "v0.9.1"
|
||||
|
||||
// New new a operator
|
||||
func New(master MasterNode, agents []AgentNode) *Provisioner {
|
||||
return &Provisioner{
|
||||
master: master,
|
||||
agents: agents,
|
||||
}
|
||||
}
|
||||
|
||||
// Provision provision k3s cluster
|
||||
func (k *Provisioner) Provision() ([]byte, error) {
|
||||
if err := k.SetupMaster(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := k.SetupAgent(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return k.GetKubeConfig()
|
||||
}
|
||||
|
||||
// HealthCheck check healthy status of host
|
||||
func (k *Provisioner) HealthCheck() (bool, error) {
|
||||
// TODO
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// SetupMaster setup master node
|
||||
func (k *Provisioner) SetupMaster() error {
|
||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||
ssh := sshOperator.New(k.master.IP).WithUser(k.master.User).WithKey(sshKeyFile)
|
||||
installCmd := fmt.Sprintf("curl -sLS https://get.k3s.io | INSTALL_K3S_EXEC='server --docker --tls-san %s' INSTALL_K3S_VERSION='%s' sh -", k.master.IP, version)
|
||||
if err := ssh.RunCommand(infra.Sudo(installCmd, k.master.User), sshOperator.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}); err != nil {
|
||||
fmt.Println("setup master failed \n ===========")
|
||||
fmt.Println(err)
|
||||
fmt.Println("===========")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (k *Provisioner) getToken() (string, error) {
|
||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||
ssh := sshOperator.New(k.master.IP).WithUser(k.master.User).WithKey(sshKeyFile)
|
||||
script := "cat /var/lib/rancher/k3s/server/node-token"
|
||||
var outPipe bytes.Buffer
|
||||
if err := ssh.RunCommand(infra.Sudo(script, k.master.User), sshOperator.CommandOptions{
|
||||
Stdout: bufio.NewWriter(&outPipe),
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return outPipe.String(), nil
|
||||
}
|
||||
|
||||
// SetupAgent set agent node
|
||||
func (k *Provisioner) SetupAgent() error {
|
||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||
tok, err := k.getToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
const k3sExtraArgs = "--docker"
|
||||
joinCmd := fmt.Sprintf("curl -fL https://get.k3s.io/ | K3S_URL='https://%s:6443' K3S_TOKEN='%s' INSTALL_K3S_VERSION='%s' sh -s - %s", k.master.IP, tok, version, k3sExtraArgs)
|
||||
for _, agent := range k.agents {
|
||||
ssh := sshOperator.New(agent.IP).WithUser(agent.User).WithKey(sshKeyFile)
|
||||
if err := ssh.RunCommand(joinCmd, sshOperator.CommandOptions{
|
||||
Stdout: os.Stdout,
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}); err != nil {
|
||||
fmt.Println("setup agent failed \n================")
|
||||
fmt.Println(err)
|
||||
fmt.Println("================")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetKubeConfig get kubeconfig of k3s cluster
|
||||
func (k *Provisioner) GetKubeConfig() ([]byte, error) {
|
||||
sshKeyFile, _ := infra.GetSSHKeyFile()
|
||||
var config []byte
|
||||
getConfigCmd := "cat /etc/rancher/k3s/k3s.yaml\n"
|
||||
ssh := sshOperator.New(k.master.IP).WithUser(k.master.User).WithKey(sshKeyFile)
|
||||
var outPipe bytes.Buffer
|
||||
if err := ssh.RunCommand(infra.Sudo(getConfigCmd, k.master.User), sshOperator.CommandOptions{
|
||||
Stdout: bufio.NewWriter(&outPipe),
|
||||
Stdin: os.Stdin,
|
||||
Stderr: os.Stderr,
|
||||
}); err != nil {
|
||||
fmt.Println("setup agent failed \n================")
|
||||
fmt.Println("================")
|
||||
fmt.Println(err)
|
||||
return config, err
|
||||
}
|
||||
return rewriteKubeconfig(outPipe.String(), k.master.IP, "default"), nil
|
||||
}
|
||||
|
||||
func rewriteKubeconfig(kubeconfig string, ip string, context string) []byte {
|
||||
if context == "" {
|
||||
// nolint
|
||||
context = "default"
|
||||
}
|
||||
|
||||
kubeconfigReplacer := strings.NewReplacer(
|
||||
"127.0.0.1", ip,
|
||||
"localhost", ip,
|
||||
"default", context,
|
||||
)
|
||||
|
||||
return []byte(kubeconfigReplacer.Replace(kubeconfig))
|
||||
}
|
||||
|
||||
var _ infra.Provisioner = &Provisioner{}
|
||||
@@ -1,45 +0,0 @@
|
||||
package k8s
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProvisioner(t *testing.T) {
|
||||
if os.Getenv("K3S_MASTER_IP") == "" ||
|
||||
os.Getenv("K3S_MASTER_USER") == "" ||
|
||||
os.Getenv("K3S_AGENT_IP") == "" ||
|
||||
os.Getenv("K3S_AGENT_USER") == "" {
|
||||
t.Skip("skip k3s test since K3S_MASTER_IP, K3S_MASTER_USER and K3S_AGENT_IP, K3S_AGENT_USER not ready")
|
||||
}
|
||||
|
||||
master := MasterNode{
|
||||
IP: os.Getenv("K3S_MASTER_IP"),
|
||||
User: os.Getenv("K3S_MASTER_USER"),
|
||||
}
|
||||
agents := []AgentNode{
|
||||
AgentNode{
|
||||
IP: os.Getenv("K3S_AGENT_IP"),
|
||||
User: os.Getenv("K3S_AGENT_USER"),
|
||||
},
|
||||
}
|
||||
k3s := New(master, agents)
|
||||
if err := k3s.SetupMaster(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
kubeconfig, err := k3s.GetKubeConfig()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Println(string(kubeconfig))
|
||||
|
||||
if _, err := k3s.getToken(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := k3s.SetupAgent(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
28
infra/scripts.go
Normal file
28
infra/scripts.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// TODO upgrade to latest when k3s fix the tls scan issue
|
||||
// https://github.com/rancher/k3s/issues/556
|
||||
const k3sVersion = "v0.9.1"
|
||||
|
||||
// Scripts to provision host
|
||||
var Scripts = map[string]interface{}{
|
||||
"docker_version": "docker version",
|
||||
"install_docker": "curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-18.06.3-ce.tgz -o docker.tgz && tar zxvf docker.tgz && mv docker/* /usr/bin && rm -rf docker docker.tgz",
|
||||
"start_dockerd": "dockerd >/dev/null 2>&1 & sleep 2",
|
||||
"check_fx_agent": "docker inspect fx-agent",
|
||||
"start_fx_agent": "docker run -d --name=fx-agent --rm -v /var/run/docker.sock:/var/run/docker.sock -p 0.0.0.0:8866:1234 bobrik/socat TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock",
|
||||
"check_k3s_server": "ps aux | grep 'k3s server --docker'",
|
||||
"setup_k3s_master": func(ip string) string {
|
||||
return fmt.Sprintf("curl -sLS https://get.k3s.io | INSTALL_K3S_EXEC='server --docker --tls-san %s' INSTALL_K3S_VERSION='%s' sh -", ip, k3sVersion)
|
||||
},
|
||||
"check_k3s_agent": "ps aux | grep 'k3s agent --docker'",
|
||||
"setup_k3s_agent": func(masterURL string, tok string) string {
|
||||
return fmt.Sprintf("curl -fL https://get.k3s.io/ | K3S_URL='%s' K3S_TOKEN='%s' INSTALL_K3S_VERSION='%s' sh -s - --docker", masterURL, tok, k3sVersion)
|
||||
},
|
||||
"get_k3s_token": "cat /var/lib/rancher/k3s/server/node-token",
|
||||
"get_k3s_kubeconfig": "cat /etc/rancher/k3s/k3s.yaml",
|
||||
}
|
||||
35
infra/ssh.go
35
infra/ssh.go
@@ -1,35 +0,0 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
// GetSSHKeyFile get ssh private key file
|
||||
func GetSSHKeyFile() (string, error) {
|
||||
path := os.Getenv("SSH_KEY_FILE")
|
||||
if path != "" {
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return absPath, nil
|
||||
}
|
||||
|
||||
key, err := homedir.Expand("~/.ssh/id_rsa")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// GetSSHPort get ssh port
|
||||
func GetSSHPort() string {
|
||||
port := os.Getenv("SSH_PORT")
|
||||
if port != "" {
|
||||
return port
|
||||
}
|
||||
return "22"
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
func TestGetSSHKeyFile(t *testing.T) {
|
||||
t.Run("defaut", func(t *testing.T) {
|
||||
defau, err := GetSSHKeyFile()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defaultPath, _ := homedir.Expand("~/.ssh/id_rsa")
|
||||
if defau != defaultPath {
|
||||
t.Fatalf("should get %s but got %s", defaultPath, defau)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("override from env", func(t *testing.T) {
|
||||
os.Setenv("SSH_KEY_FILE", "/tmp/id_rsa")
|
||||
keyFile, err := GetSSHKeyFile()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if keyFile != "/tmp/id_rsa" {
|
||||
t.Fatalf("should get %s but got %s", "/tmp/id_rsa", keyFile)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetSSHPort(t *testing.T) {
|
||||
t.Run("defaut", func(t *testing.T) {
|
||||
defau := GetSSHPort()
|
||||
if defau != "22" {
|
||||
t.Fatalf("should get %s but got %s", "22", defau)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("override from env", func(t *testing.T) {
|
||||
os.Setenv("SSH_PORT", "2222")
|
||||
defau := GetSSHPort()
|
||||
if defau != "2222" {
|
||||
t.Fatalf("should get %s but got %s", "2222", defau)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -5,11 +5,11 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/metrue/fx/config"
|
||||
containerruntimes "github.com/metrue/fx/container_runtimes"
|
||||
"github.com/metrue/fx/context"
|
||||
"github.com/metrue/fx/packer"
|
||||
"github.com/metrue/fx/pkg/spinner"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/metrue/fx/utils"
|
||||
"github.com/otiai10/copy"
|
||||
)
|
||||
@@ -55,7 +55,7 @@ func Build(ctx context.Contexter) (err error) {
|
||||
|
||||
cloudType := ctx.Get("cloud_type").(string)
|
||||
name := ctx.Get("name").(string)
|
||||
if cloudType == config.CloudTypeK8S && os.Getenv("K3S") == "" {
|
||||
if cloudType == types.CloudTypeK8S {
|
||||
data, err := packer.PackIntoK8SConfigMapFile(workdir)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -72,17 +72,6 @@ func Build(ctx context.Contexter) (err error) {
|
||||
return err
|
||||
}
|
||||
ctx.Set("image", nameWithTag)
|
||||
|
||||
if os.Getenv("K3S") != "" {
|
||||
username := os.Getenv("DOCKER_USERNAME")
|
||||
password := os.Getenv("DOCKER_PASSWORD")
|
||||
if username != "" && password != "" {
|
||||
if _, err := docker.PushImage(ctx.GetContext(), name); err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("image", username+"/"+name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package middlewares
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
@@ -11,34 +12,62 @@ import (
|
||||
"github.com/metrue/fx/infra"
|
||||
dockerInfra "github.com/metrue/fx/infra/docker"
|
||||
k8sInfra "github.com/metrue/fx/infra/k8s"
|
||||
"github.com/metrue/fx/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Provision make sure infrastructure is healthy
|
||||
func Provision(ctx context.Contexter) (err error) {
|
||||
fxConfig := ctx.Get("config").(*config.Config)
|
||||
cloud := fxConfig.Clouds[fxConfig.CurrentCloud]
|
||||
meta, err := fxConfig.GetCurrentCloud()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cloudType, err := fxConfig.GetCurrentCloudType()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Set("cloud_type", cloudType)
|
||||
var cloud infra.Clouder
|
||||
switch cloudType {
|
||||
case types.CloudTypeK8S:
|
||||
cloud, err = k8sInfra.Load(meta)
|
||||
case types.CloudTypeDocker:
|
||||
cloud, err = dockerInfra.Load(meta)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ok, err := cloud.IsHealth()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return fmt.Errorf("infrastrure is not health, please try to run create infrastructure use 'fx infra create ...' command")
|
||||
}
|
||||
|
||||
ctx.Set("cloud", cloud)
|
||||
|
||||
conf, err := cloud.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var deployer infra.Deployer
|
||||
if os.Getenv("KUBECONFIG") != "" {
|
||||
deployer, err = k8sInfra.CreateDeployer(os.Getenv("KUBECONFIG"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("cloud_type", config.CloudTypeK8S)
|
||||
} else if cloud["type"] == config.CloudTypeDocker {
|
||||
provisioner := dockerInfra.CreateProvisioner(cloud["host"], cloud["user"])
|
||||
ok, err := provisioner.HealthCheck()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
if _, err := provisioner.Provision(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
cloudType = types.CloudTypeK8S
|
||||
conf = os.Getenv("KUBECONFIG")
|
||||
}
|
||||
ctx.Set("cloud_type", types.CloudTypeK8S)
|
||||
|
||||
docker, err := dockerHTTP.Create(cloud["host"], constants.AgentPort)
|
||||
if cloudType == types.CloudTypeDocker {
|
||||
var meta map[string]string
|
||||
if err := json.Unmarshal([]byte(conf), &meta); err != nil {
|
||||
return err
|
||||
}
|
||||
docker, err := dockerHTTP.Create(meta["ip"], constants.AgentPort)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "please make sure docker is installed and running on your host")
|
||||
}
|
||||
@@ -49,15 +78,13 @@ func Provision(ctx context.Contexter) (err error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("cloud_type", config.CloudTypeDocker)
|
||||
} else if cloud["type"] == config.CloudTypeK8S {
|
||||
deployer, err = k8sInfra.CreateDeployer(cloud["kubeconfig"])
|
||||
} else if cloudType == types.CloudTypeK8S {
|
||||
deployer, err = k8sInfra.CreateDeployer(conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Set("cloud_type", config.CloudTypeK8S)
|
||||
} else {
|
||||
return fmt.Errorf("unsupport cloud type %s, please make sure you config is correct", cloud["type"])
|
||||
return fmt.Errorf("unsupport cloud type %s, please make sure you config is correct", cloud.GetType())
|
||||
}
|
||||
|
||||
ctx.Set("deployer", deployer)
|
||||
|
||||
7
types/cloud.go
Normal file
7
types/cloud.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package types
|
||||
|
||||
// CloudTypeDocker docker cloud type
|
||||
const CloudTypeDocker = "docker"
|
||||
|
||||
// CloudTypeK8S k8s cloud type
|
||||
const CloudTypeK8S = "k8s"
|
||||
Reference in New Issue
Block a user