Compare commits
10 Commits
0.8.74-alp
...
0.8.77
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb492fa6f7 | ||
|
|
159714491d | ||
|
|
64cbbc70bb | ||
|
|
d0559f627e | ||
|
|
0a6784e270 | ||
|
|
b6fd3c7e98 | ||
|
|
1c05534071 | ||
|
|
3627d5bb40 | ||
|
|
1f7714c1e9 | ||
|
|
d868ebf4a1 |
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: |
|
||||
|
||||
4
Makefile
4
Makefile
@@ -26,13 +26,13 @@ clean:
|
||||
rm -rf ${DIST_DIR}
|
||||
|
||||
unit-test:
|
||||
./scripts/coverage.sh
|
||||
CI=true ./scripts/coverage.sh
|
||||
|
||||
cli-test-ci:
|
||||
./scripts/test_cli.sh 'js'
|
||||
|
||||
cli-test:
|
||||
./scripts/test_cli.sh 'js rb py go php java d rs'
|
||||
./scripts/test_cli.sh 'js rb py go php java d rs pl'
|
||||
|
||||
http-test:
|
||||
./scripts/http_test.sh
|
||||
|
||||
@@ -4,6 +4,7 @@ fx
|
||||
Poor man's function as a service.
|
||||
<br/>
|
||||

|
||||

|
||||
[](https://codecov.io/gh/metrue/fx)
|
||||
[](https://goreportcard.com/report/github.com/metrue/fx)
|
||||
[](http://godoc.org/github.com/metrue/fx)
|
||||
@@ -36,6 +37,7 @@ Feel free hacking fx to support the languages not listed. Welcome to tweet me [@
|
||||
| PHP | Supported | [@chlins](https://github.com/chlins)| [/examples/PHP](https://github.com/metrue/fx/tree/master/examples/functions/PHP) |
|
||||
| Julia | Supported | [@matbesancon](https://github.com/matbesancon)| [/examples/Julia](https://github.com/metrue/fx/tree/master/examples/functions/Julia) |
|
||||
| D | Supported | [@andre2007](https://github.com/andre2007)| [/examples/D](https://github.com/metrue/fx/tree/master/examples/functions/D) |
|
||||
| Perl | Supported | fx | [/examples/Perl](https://github.com/metrue/fx/tree/master/examples/functions/Perl) |
|
||||
| R | Working on [need your help](https://github.com/metrue/fx/issues/31) | ||
|
||||
|
||||
# Installation
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
dockerInfra "github.com/metrue/fx/infra/docker"
|
||||
"github.com/metrue/fx/types"
|
||||
@@ -22,6 +23,7 @@ type Configer interface {
|
||||
UseCloud(name string) error
|
||||
View() ([]byte, error)
|
||||
AddCloud(name string, meta []byte) error
|
||||
Dir() (string, error)
|
||||
}
|
||||
|
||||
// Config config of fx
|
||||
@@ -191,6 +193,15 @@ func (c *Config) writeDefaultConfig() error {
|
||||
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{}
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
@@ -49,32 +50,39 @@ func TestConfig(t *testing.T) {
|
||||
t.Fatalf("should get %s but got %s", "default", cloudMeta["name"])
|
||||
}
|
||||
|
||||
// add k8s cloud
|
||||
kCloud := k8sInfra.Cloud{
|
||||
Type: types.CloudTypeK8S,
|
||||
Config: "sample kubeconfg",
|
||||
Token: "",
|
||||
URL: "",
|
||||
Nodes: map[string]k8sInfra.Noder{
|
||||
"master-node": &k8sInfra.Node{
|
||||
IP: "1.1.1.1",
|
||||
User: "user-1",
|
||||
Type: "k3s-master",
|
||||
Name: "master-node",
|
||||
},
|
||||
"agent-node-1": &k8sInfra.Node{
|
||||
IP: "1.1.1.1",
|
||||
User: "user-1",
|
||||
Type: "k3s-agent",
|
||||
Name: "agent-node-1",
|
||||
},
|
||||
},
|
||||
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)
|
||||
}
|
||||
kName := "k8s-1"
|
||||
if err := c.AddCloud(kName, kMeta); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -108,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)
|
||||
}
|
||||
}
|
||||
|
||||
36
examples/functions/Perl/README.md
vendored
Normal file
36
examples/functions/Perl/README.md
vendored
Normal file
@@ -0,0 +1,36 @@
|
||||
# Make a Perl function a service with fx
|
||||
|
||||
Write a function like,
|
||||
|
||||
```perl
|
||||
sub fx {
|
||||
my $ctx = shift;
|
||||
return 'hello fx'
|
||||
}
|
||||
|
||||
1;
|
||||
```
|
||||
|
||||
then deploy it with `fx up` command,
|
||||
|
||||
```shell
|
||||
$ fx up -p 8080:3000 func.pl
|
||||
```
|
||||
|
||||
test it using `curl`
|
||||
|
||||
```shell
|
||||
$ curl 127.0.0.1:8080
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Connection: keep-alive
|
||||
Content-Length: 11
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
Date: Tue, 06 Aug 2019 15:58:41 GMT
|
||||
|
||||
hello fx
|
||||
```
|
||||
|
||||
### ctx
|
||||
|
||||
The `ctx` object is exactly the [Controller](https://mojolicious.org/perldoc/Mojolicious/Controller) of [Mojolicious](https://mojolicious.org/perldoc/Mojolicious) framework.
|
||||
6
examples/functions/Perl/func.pl
vendored
Normal file
6
examples/functions/Perl/func.pl
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
sub fx {
|
||||
my $ctx = shift;
|
||||
return 'hello fx'
|
||||
}
|
||||
|
||||
1;
|
||||
2
fx.go
2
fx.go
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const version = "0.8.74"
|
||||
const version = "0.8.77"
|
||||
|
||||
func init() {
|
||||
go checkForUpdate()
|
||||
|
||||
@@ -2,6 +2,7 @@ package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/metrue/fx/config"
|
||||
@@ -11,7 +12,7 @@ import (
|
||||
"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")
|
||||
@@ -36,7 +37,8 @@ func setupK8S(masterInfo string, agentsInfo string) ([]byte, error) {
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
}
|
||||
cloud := k8sInfra.NewCloud(nodes...)
|
||||
kubeconfigPath := filepath.Join(configDir, name+".kubeconfig")
|
||||
cloud := k8sInfra.NewCloud(kubeconfigPath, nodes...)
|
||||
if err := cloud.Provision(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -91,7 +93,11 @@ 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
|
||||
}
|
||||
kubeconf, err := setupK8S(dir, name, cli.String("master"), cli.String("agents"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2,8 +2,11 @@ package docker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/metrue/fx/infra"
|
||||
"github.com/metrue/fx/types"
|
||||
@@ -68,18 +71,18 @@ func Load(meta []byte) (*Cloud, error) {
|
||||
|
||||
// Provision a host
|
||||
func (c *Cloud) Provision() error {
|
||||
if err := c.sshClient.RunCommand(infra.Scripts["docker_version"].(string), ssh.CommandOptions{}); err != nil {
|
||||
if err := c.sshClient.RunCommand(infra.Scripts["install_docker"].(string), ssh.CommandOptions{}); err != nil {
|
||||
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.sshClient.RunCommand(infra.Scripts["start_dockerd"].(string), ssh.CommandOptions{}); err != nil {
|
||||
if err := c.runCmd(infra.Scripts["start_dockerd"].(string)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := c.sshClient.RunCommand(infra.Scripts["check_fx_agent"].(string), ssh.CommandOptions{}); err != nil {
|
||||
if err := c.sshClient.RunCommand(infra.Scripts["start_fx_agent"].(string), ssh.CommandOptions{}); err != nil {
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -103,11 +106,48 @@ 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) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"github.com/mitchellh/go-homedir"
|
||||
)
|
||||
|
||||
func TestCloud(t *testing.T) {
|
||||
func TestCloudProvision(t *testing.T) {
|
||||
t.Run("fx agent started", func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
@@ -60,6 +60,61 @@ func TestCloud(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
@@ -12,6 +12,7 @@ type Clouder interface {
|
||||
GetConfig() (string, error)
|
||||
GetType() string
|
||||
Dump() ([]byte, error)
|
||||
IsHealth() (bool, error)
|
||||
}
|
||||
|
||||
// Deployer deploy interface
|
||||
|
||||
@@ -3,18 +3,22 @@ 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 {
|
||||
Config string `json:"config"`
|
||||
URL string `json:"url"`
|
||||
Token string `json:"token"`
|
||||
Type string `json:"type"`
|
||||
Nodes map[string]Noder `json:"nodes"`
|
||||
// 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
|
||||
@@ -39,14 +43,16 @@ func Load(meta []byte, messup ...func(n Noder) (Noder, error)) (*Cloud, error) {
|
||||
}
|
||||
|
||||
// NewCloud new a cloud
|
||||
func NewCloud(node ...Noder) *Cloud {
|
||||
func NewCloud(kubeconf string, node ...Noder) *Cloud {
|
||||
nodes := map[string]Noder{}
|
||||
for _, n := range node {
|
||||
nodes[n.GetName()] = n
|
||||
}
|
||||
|
||||
return &Cloud{
|
||||
Type: types.CloudTypeK8S,
|
||||
Nodes: nodes,
|
||||
KubeConfig: kubeconf,
|
||||
Type: types.CloudTypeK8S,
|
||||
Nodes: nodes,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +70,7 @@ func (c *Cloud) Provision() error {
|
||||
|
||||
// when it's k3s cluster
|
||||
if master != nil {
|
||||
c.URL = fmt.Sprintf("https://%s:6443", master.GetIP())
|
||||
c.url = fmt.Sprintf("https://%s:6443", master.GetIP())
|
||||
if err := master.Provision(map[string]string{}); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -73,22 +79,19 @@ func (c *Cloud) Provision() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Token = tok
|
||||
c.token = tok
|
||||
|
||||
config, err := master.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Config = config
|
||||
}
|
||||
|
||||
// when it's a docker agent
|
||||
if len(agents) == 1 && agents[0].GetType() == NodeTypeDocker {
|
||||
config, err := agents[0].GetConfig()
|
||||
if err != nil {
|
||||
if err := utils.EnsureFile(c.KubeConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ioutil.WriteFile(c.KubeConfig, []byte(config), 0666); err != nil {
|
||||
return err
|
||||
}
|
||||
c.Config = config
|
||||
}
|
||||
|
||||
if len(agents) > 0 {
|
||||
@@ -98,8 +101,8 @@ func (c *Cloud) Provision() error {
|
||||
for _, agent := range agents {
|
||||
go func(node Noder) {
|
||||
errCh <- node.Provision(map[string]string{
|
||||
"url": c.URL,
|
||||
"token": c.Token,
|
||||
"url": c.url,
|
||||
"token": c.token,
|
||||
})
|
||||
}(agent)
|
||||
}
|
||||
@@ -118,8 +121,8 @@ func (c *Cloud) Provision() error {
|
||||
func (c *Cloud) AddNode(n Noder, skipProvision bool) error {
|
||||
if !skipProvision {
|
||||
if err := n.Provision(map[string]string{
|
||||
"url": c.URL,
|
||||
"token": c.Token,
|
||||
"url": c.url,
|
||||
"token": c.token,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -173,16 +176,16 @@ func (c *Cloud) UnmarshalJSON(data []byte) error {
|
||||
} else if k == "token" {
|
||||
tok, ok := v.(string)
|
||||
if ok {
|
||||
c.Token = tok
|
||||
c.token = tok
|
||||
} else {
|
||||
c.Token = ""
|
||||
c.token = ""
|
||||
}
|
||||
} else if k == "config" {
|
||||
config, ok := v.(string)
|
||||
if ok {
|
||||
c.Config = config
|
||||
c.KubeConfig = config
|
||||
} else {
|
||||
c.Config = ""
|
||||
c.KubeConfig = ""
|
||||
}
|
||||
} else if k == "type" {
|
||||
typ, ok := v.(string)
|
||||
@@ -194,9 +197,9 @@ func (c *Cloud) UnmarshalJSON(data []byte) error {
|
||||
} else if k == "url" {
|
||||
url, ok := v.(string)
|
||||
if ok {
|
||||
c.URL = url
|
||||
c.url = url
|
||||
} else {
|
||||
c.URL = ""
|
||||
c.url = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,17 +220,17 @@ func (c *Cloud) MarshalJSON() ([]byte, error) {
|
||||
}
|
||||
|
||||
body, err := json.Marshal(struct {
|
||||
URL string `json:"url"`
|
||||
Config string `json:"config"`
|
||||
Type string `json:"type"`
|
||||
Token string `json:"token"`
|
||||
Nodes map[string]Node `json:"nodes"`
|
||||
URL string `json:"url"`
|
||||
KubeConfig string `json:"config"`
|
||||
Type string `json:"type"`
|
||||
Token string `json:"token"`
|
||||
Nodes map[string]Node `json:"nodes"`
|
||||
}{
|
||||
URL: c.URL,
|
||||
Config: c.Config,
|
||||
Type: c.Type,
|
||||
Token: c.Token,
|
||||
Nodes: nodes,
|
||||
KubeConfig: c.KubeConfig,
|
||||
Type: c.Type,
|
||||
Token: c.token,
|
||||
URL: c.url,
|
||||
Nodes: nodes,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -248,13 +251,18 @@ func (c *Cloud) Dump() ([]byte, error) {
|
||||
|
||||
// GetConfig get config
|
||||
func (c *Cloud) GetConfig() (string, error) {
|
||||
if c.Config != "" {
|
||||
return c.Config, nil
|
||||
if c.KubeConfig != "" {
|
||||
return c.KubeConfig, nil
|
||||
}
|
||||
if err := c.Provision(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return c.Config, nil
|
||||
return c.KubeConfig, nil
|
||||
}
|
||||
|
||||
// IsHealth check if cloud is in health
|
||||
func (c *Cloud) IsHealth() (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -3,6 +3,8 @@ package k8s
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
@@ -25,6 +27,13 @@ func TestLoad(t *testing.T) {
|
||||
})
|
||||
|
||||
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()
|
||||
|
||||
@@ -36,18 +45,19 @@ func TestLoad(t *testing.T) {
|
||||
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("sample-config", nil)
|
||||
master.EXPECT().GetConfig().Return(kubeconfContent, nil)
|
||||
|
||||
claud := &Cloud{
|
||||
Config: "",
|
||||
URL: "",
|
||||
Token: "",
|
||||
Type: "k8s",
|
||||
Nodes: map[string]Noder{"master-node": master},
|
||||
KubeConfig: kubeconfig,
|
||||
Type: "k8s",
|
||||
url: "",
|
||||
token: "",
|
||||
Nodes: map[string]Noder{"master-node": master},
|
||||
}
|
||||
|
||||
meta, err := json.Marshal(claud)
|
||||
@@ -67,9 +77,24 @@ func TestLoad(t *testing.T) {
|
||||
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()
|
||||
|
||||
@@ -85,10 +110,11 @@ func TestLoad(t *testing.T) {
|
||||
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("sample-config", nil)
|
||||
master.EXPECT().GetConfig().Return(kubeconfContent, nil)
|
||||
master.EXPECT().GetUser().Return(user)
|
||||
|
||||
nodeType := NodeTypeAgent
|
||||
@@ -96,18 +122,18 @@ func TestLoad(t *testing.T) {
|
||||
nodeIP := "12.12.12.12"
|
||||
nodeUser := "testuser"
|
||||
node.EXPECT().GetName().Return(nodeName)
|
||||
node.EXPECT().GetType().Return(nodeType).Times(3)
|
||||
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{
|
||||
Config: "",
|
||||
URL: url,
|
||||
Token: tok,
|
||||
Type: "k8s",
|
||||
Nodes: map[string]Noder{"master-node": master, "agent-node": node},
|
||||
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 {
|
||||
@@ -125,12 +151,19 @@ func TestLoad(t *testing.T) {
|
||||
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,
|
||||
"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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ func Build(ctx context.Contexter) (err error) {
|
||||
}()
|
||||
|
||||
workdir := fmt.Sprintf("/tmp/fx-%d", time.Now().Unix())
|
||||
if err := utils.EnsureDir(workdir); err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(workdir)
|
||||
|
||||
// Cases supports
|
||||
@@ -66,11 +69,11 @@ func Build(ctx context.Contexter) (err error) {
|
||||
if err := docker.BuildImage(ctx.GetContext(), workdir, name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nameWithTag := name + ":latest"
|
||||
if err := docker.TagImage(ctx.GetContext(), name, nameWithTag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Set("image", nameWithTag)
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,16 @@ func Provision(ctx context.Contexter) (err error) {
|
||||
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()
|
||||
@@ -47,12 +57,12 @@ func Provision(ctx context.Contexter) (err error) {
|
||||
}
|
||||
var deployer infra.Deployer
|
||||
if os.Getenv("KUBECONFIG") != "" {
|
||||
deployer, err = k8sInfra.CreateDeployer(os.Getenv("KUBECONFIG"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cloudType = types.CloudTypeK8S
|
||||
conf = os.Getenv("KUBECONFIG")
|
||||
ctx.Set("cloud_type", types.CloudTypeK8S)
|
||||
} else if cloud.GetType() == types.CloudTypeDocker {
|
||||
}
|
||||
|
||||
if cloudType == types.CloudTypeDocker {
|
||||
var meta map[string]string
|
||||
if err := json.Unmarshal([]byte(conf), &meta); err != nil {
|
||||
return err
|
||||
@@ -68,12 +78,8 @@ func Provision(ctx context.Contexter) (err error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if cloud.GetType() == types.CloudTypeK8S {
|
||||
kubeconfig, err := fxConfig.GetKubeConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
deployer, err = k8sInfra.CreateDeployer(kubeconfig)
|
||||
} else if cloudType == types.CloudTypeK8S {
|
||||
deployer, err = k8sInfra.CreateDeployer(conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
13
packer/images/perl/Dockerfile
vendored
Normal file
13
packer/images/perl/Dockerfile
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
FROM alpine:3.4
|
||||
MAINTAINER Mojolicious
|
||||
|
||||
ADD . .
|
||||
COPY cpanfile /
|
||||
ENV EV_EXTRA_DEFS -DEV_NO_ATFORK
|
||||
|
||||
RUN apk update && \
|
||||
apk add perl perl-io-socket-ssl perl-dbd-pg perl-dev g++ make wget curl && \
|
||||
curl -L https://cpanmin.us | perl - App::cpanminus && cpanm --installdeps . -M https://cpan.metacpan.org
|
||||
|
||||
EXPOSE 3000
|
||||
CMD ["perl", "app.pl", "daemon"]
|
||||
17
packer/images/perl/app.pl
vendored
Normal file
17
packer/images/perl/app.pl
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
use Mojolicious::Lite;
|
||||
|
||||
require "./fx.pl";
|
||||
|
||||
get '/' => sub {
|
||||
my $ctx = shift;
|
||||
my $res = fx($ctx);
|
||||
$ctx->render(json => $res);
|
||||
};
|
||||
|
||||
post '/' => sub {
|
||||
my $ctx = shift;
|
||||
my $res = fx($ctx);
|
||||
$ctx->render(json => $res);
|
||||
};
|
||||
|
||||
app->start;
|
||||
2
packer/images/perl/cpanfile
vendored
Normal file
2
packer/images/perl/cpanfile
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
requires "EV";
|
||||
requires "Mojolicious::Lite";
|
||||
6
packer/images/perl/fx.pl
vendored
Normal file
6
packer/images/perl/fx.pl
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
sub fx {
|
||||
my $ctx = shift;
|
||||
return 'hello fx'
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -26,17 +26,23 @@ func Pack(output string, input ...string) error {
|
||||
return fmt.Errorf("source file or directory required")
|
||||
}
|
||||
|
||||
var lang string
|
||||
var language string
|
||||
for _, f := range input {
|
||||
if utils.IsRegularFile(f) {
|
||||
lang = langFromFileName(f)
|
||||
lang, err := langFromFileName(f)
|
||||
if err == nil {
|
||||
language = lang
|
||||
}
|
||||
} else if utils.IsDir(f) {
|
||||
if err := filepath.Walk(f, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if utils.IsRegularFile(path) {
|
||||
lang = langFromFileName(path)
|
||||
lang, err := langFromFileName(path)
|
||||
if err == nil {
|
||||
language = lang
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
@@ -45,11 +51,11 @@ func Pack(output string, input ...string) error {
|
||||
}
|
||||
}
|
||||
|
||||
if lang == "" {
|
||||
if language == "" {
|
||||
return fmt.Errorf("could not tell programe language of your input source codes")
|
||||
}
|
||||
|
||||
if err := restore(output, lang); err != nil {
|
||||
if err := restore(output, language); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -79,7 +85,7 @@ func Pack(output string, input ...string) error {
|
||||
}
|
||||
|
||||
if !hasFxHandleFile(input...) {
|
||||
msg := `it requires a fx handle file when input is not a single file function, e.g.
|
||||
msg := `it requires a fx handle file when input is not a single file function, e.g.
|
||||
fx.go for Golang
|
||||
Fx.java for Java
|
||||
fx.php for PHP
|
||||
@@ -87,6 +93,7 @@ fx.py for Python
|
||||
fx.js for JavaScript or Node
|
||||
fx.rb for Ruby
|
||||
fx.jl for Julia
|
||||
fx.pl for Perl
|
||||
fx.d for D`
|
||||
return fmt.Errorf(msg)
|
||||
}
|
||||
@@ -154,54 +161,6 @@ func merge(dest string, input ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func isHandler(name string) bool {
|
||||
basename := filepath.Base(name)
|
||||
nameWithoutExt := strings.TrimSuffix(basename, filepath.Ext(basename))
|
||||
return nameWithoutExt == "fx" ||
|
||||
nameWithoutExt == "Fx" || // Fx is for Java
|
||||
nameWithoutExt == "mod" // mod.rs is for Rust
|
||||
}
|
||||
|
||||
func langFromFileName(fileName string) string {
|
||||
extLangMap := map[string]string{
|
||||
".js": "node",
|
||||
".go": "go",
|
||||
".rb": "ruby",
|
||||
".py": "python",
|
||||
".php": "php",
|
||||
".jl": "julia",
|
||||
".java": "java",
|
||||
".d": "d",
|
||||
".rs": "rust",
|
||||
}
|
||||
return extLangMap[filepath.Ext(fileName)]
|
||||
}
|
||||
|
||||
func hasFxHandleFile(input ...string) bool {
|
||||
var handleFile string
|
||||
for _, file := range input {
|
||||
if utils.IsRegularFile(file) && isHandler(file) {
|
||||
handleFile = file
|
||||
break
|
||||
} else if utils.IsDir(file) {
|
||||
if err := filepath.Walk(file, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if utils.IsRegularFile(path) && isHandler(info.Name()) {
|
||||
handleFile = path
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return handleFile != ""
|
||||
}
|
||||
|
||||
// PackIntoK8SConfigMapFile pack function a K8S config map file
|
||||
func PackIntoK8SConfigMapFile(dir string) (string, error) {
|
||||
tree := map[string]string{}
|
||||
|
||||
70
packer/rules.go
Normal file
70
packer/rules.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package packer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/metrue/fx/utils"
|
||||
)
|
||||
|
||||
// ExtLangMapping file extension mapping with programming language
|
||||
var ExtLangMapping = map[string]string{
|
||||
".js": "node",
|
||||
".go": "go",
|
||||
".rb": "ruby",
|
||||
".py": "python",
|
||||
".php": "php",
|
||||
".jl": "julia",
|
||||
".java": "java",
|
||||
".d": "d",
|
||||
".rs": "rust",
|
||||
".pl": "perl",
|
||||
}
|
||||
|
||||
func isHandler(name string) bool {
|
||||
basename := filepath.Base(name)
|
||||
nameWithoutExt := strings.TrimSuffix(basename, filepath.Ext(basename))
|
||||
return nameWithoutExt == "fx" ||
|
||||
nameWithoutExt == "Fx" || // Fx is for Java
|
||||
nameWithoutExt == "mod" // mod.rs is for Rust
|
||||
}
|
||||
|
||||
func langFromFileName(fileName string) (string, error) {
|
||||
if fileName == "" {
|
||||
return "", fmt.Errorf("file name should not be empty")
|
||||
}
|
||||
|
||||
ext := filepath.Ext(fileName)
|
||||
lang, ok := ExtLangMapping[ext]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("could not find corresponse programming language for file extension %s", ext)
|
||||
}
|
||||
return lang, nil
|
||||
}
|
||||
|
||||
func hasFxHandleFile(input ...string) bool {
|
||||
var handleFile string
|
||||
for _, file := range input {
|
||||
if utils.IsRegularFile(file) && isHandler(file) {
|
||||
handleFile = file
|
||||
break
|
||||
} else if utils.IsDir(file) {
|
||||
if err := filepath.Walk(file, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if utils.IsRegularFile(path) && isHandler(info.Name()) {
|
||||
handleFile = path
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return handleFile != ""
|
||||
}
|
||||
61
packer/rules_test.go
Normal file
61
packer/rules_test.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package packer
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestLangFromFileName(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
lang string
|
||||
}{
|
||||
{
|
||||
name: "a.js",
|
||||
lang: "node",
|
||||
},
|
||||
{
|
||||
name: "a.py",
|
||||
lang: "python",
|
||||
},
|
||||
{
|
||||
name: "a.go",
|
||||
lang: "go",
|
||||
},
|
||||
{
|
||||
name: "a.rb",
|
||||
lang: "ruby",
|
||||
},
|
||||
{
|
||||
name: "a.php",
|
||||
lang: "php",
|
||||
},
|
||||
{
|
||||
name: "a.jl",
|
||||
lang: "julia",
|
||||
},
|
||||
{
|
||||
name: "a.d",
|
||||
lang: "d",
|
||||
},
|
||||
{
|
||||
name: "a.rs",
|
||||
lang: "rust",
|
||||
},
|
||||
{
|
||||
name: "a.java",
|
||||
lang: "java",
|
||||
},
|
||||
{
|
||||
name: "a.pl",
|
||||
lang: "perl",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
lang, err := langFromFileName(c.name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if lang != c.lang {
|
||||
t.Fatalf("should get %s but got %s", c.lang, lang)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,9 @@ import (
|
||||
func HasDockerfile(dir string) bool {
|
||||
var dockerfile string
|
||||
if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// nolint
|
||||
if info.Mode().IsRegular() && info.Name() == "Dockerfile" {
|
||||
dockerfile = path
|
||||
|
||||
Reference in New Issue
Block a user