Compare commits

..

6 Commits

Author SHA1 Message Date
Minghe
41bc98ab64 fx on k3s (#357) 2019-11-17 00:10:55 +08:00
Minghe
b007ac315a refactor output (#356) 2019-11-15 22:23:27 +08:00
Minghe
940f6b8f72 enable progress bar (#355)
* enable progress bar
* add spinner pkg
* fix lint
2019-11-15 21:17:38 +08:00
Minghe
f9690b74a5 auto startup fx agent during setup progress (#354)
* auto start fx agent during setup progress

* bump version
2019-11-15 19:41:17 +08:00
Minghe
f2c58d545a add document on ubunut (#352)
* add document on ubunut

* fix typo
2019-11-15 13:29:31 +08:00
Siddhesh Poyarekar
4732426629 Point to instructions to build from source for non-x86 targets (#350)
* Fix typos
* Point to instructions to build from source for non-x86 targets

Installation instructions do not specify clearly enough the fact that
they are supported only on x86. Make it clearer and point to the Build
and Test section in Contribute for instructions on building fx.
2019-11-14 15:22:25 +08:00
29 changed files with 793 additions and 156 deletions

View File

@@ -40,6 +40,8 @@ Feel free hacking fx to support the languages not listed. Welcome to tweet me [@
# Installation
Binaries are available for Windows, MacOS and Linux/Unix on x86. For other architectures and platforms, follow instructions to [build fx from source](#buildtest).
* MacOS
```
@@ -59,9 +61,9 @@ curl -o- https://raw.githubusercontent.com/metrue/fx/master/scripts/install.sh |
curl -o- https://raw.githubusercontent.com/metrue/fx/master/scripts/install.sh | sudo bash
```
fx will be installed into /usr/local/bin, sometimes you may need `source ~/.zshrc` or `source ~/.bashrc` to make fx available in `$PAHT`.
fx will be installed into /usr/local/bin, sometimes you may need `source ~/.zshrc` or `source ~/.bashrc` to make fx available in `$PATH`.
* Window
* Windows
You can go the release page to [download](https://github.com/metrue/fx/releases) fx manually;
@@ -229,6 +231,7 @@ fx uses [Project](https://github.com/metrue/fx/projects/4) to manage the develop
Docker: make sure [Docker](https://docs.docker.com/engine/installation/) installed and running on your server.
<a name="buildtest"></a>
#### Build & Test
```

View File

@@ -377,8 +377,6 @@ func (api *API) StartContainer(ctx context.Context, name string, image string, b
return fmt.Errorf("container id is missing")
}
log.Infof("container %s created", name)
// start container
path = fmt.Sprintf("/containers/%s/start", createRes.ID)
url := fmt.Sprintf("%s%s", api.endpoint, path)
@@ -402,7 +400,6 @@ func (api *API) StartContainer(ctx context.Context, name string, image string, b
msg := fmt.Sprintf("start container met issue: %s", string(b))
return errors.New(msg)
}
log.Infof("container %s started", name)
if _, err = api.inspect(createRes.ID); err != nil {
msg := fmt.Sprintf("inspect container %s error", name)

View File

@@ -76,7 +76,9 @@ func (d *Docker) BuildImage(ctx context.Context, workdir string, name string) er
if err != nil {
return err
}
log.Info(string(body))
if os.Getenv("DEBUG") != "" {
log.Info(string(body))
}
return nil
}

View File

@@ -8,7 +8,7 @@ import (
// Deployer make a image a service
type Deployer interface {
Deploy(ctx context.Context, fn types.Func, name string, bindings []types.PortBinding) error
Deploy(ctx context.Context, fn types.Func, name string, image string, bindings []types.PortBinding) error
Destroy(ctx context.Context, name string) error
Update(ctx context.Context, name string) error
GetStatus(ctx context.Context, name string) error

View File

@@ -2,20 +2,14 @@ package docker
import (
"context"
"fmt"
"log"
"os"
"time"
dockerTypes "github.com/docker/docker/api/types"
"github.com/metrue/fx/constants"
containerruntimes "github.com/metrue/fx/container_runtimes"
dockerHTTP "github.com/metrue/fx/container_runtimes/docker/http"
dockerSDK "github.com/metrue/fx/container_runtimes/docker/sdk"
"github.com/metrue/fx/deploy"
"github.com/metrue/fx/packer"
"github.com/metrue/fx/types"
"github.com/metrue/fx/utils"
)
// Docker manage container
@@ -44,38 +38,8 @@ func CreateClient(ctx context.Context) (d *Docker, err error) {
}
// Deploy create a Docker container from given image, and bind the constants.FxContainerExposePort to given port
func (d *Docker) Deploy(ctx context.Context, fn types.Func, name string, ports []types.PortBinding) error {
workdir := fmt.Sprintf("/tmp/fx-%d", time.Now().Unix())
defer os.RemoveAll(workdir)
if err := packer.PackIntoDir(fn, workdir); err != nil {
log.Fatalf("could not pack function %v: %v", fn, err)
return err
}
if err := d.cli.BuildImage(ctx, workdir, name); err != nil {
log.Fatalf("could not build image: %v", err)
return err
}
nameWithTag := name + ":latest"
if err := d.cli.TagImage(ctx, name, nameWithTag); err != nil {
log.Fatalf("could not tag image: %v", err)
return err
}
// when deploy a function on a bare Docker running without Kubernetes,
// image would be built on-demand on host locally, so there is no need to
// pull image from remote.
// But it takes some times waiting image ready after image built, we retry to make sure it ready here
var imgInfo dockerTypes.ImageInspect
if err := utils.RunWithRetry(func() error {
return d.cli.InspectImage(ctx, name, &imgInfo)
}, time.Second*1, 5); err != nil {
return err
}
return d.cli.StartContainer(ctx, name, name, ports)
func (d *Docker) Deploy(ctx context.Context, fn types.Func, name string, image string, ports []types.PortBinding) error {
return d.cli.StartContainer(ctx, name, image, ports)
}
// Update a container

View File

@@ -1,47 +1,43 @@
package docker
import (
"context"
"testing"
"time"
"github.com/metrue/fx/types"
)
func TestDocker(t *testing.T) {
ctx := context.Background()
cli, err := CreateClient(ctx)
if err != nil {
t.Fatal(err)
}
name := "helloworld"
bindings := []types.PortBinding{
types.PortBinding{
ServiceBindingPort: 80,
ContainerExposePort: 3000,
},
types.PortBinding{
ServiceBindingPort: 443,
ContainerExposePort: 3000,
},
}
fn := types.Func{
Language: "node",
Source: `
module.exports = (ctx) => {
ctx.body = 'hello world'
}
`,
}
if err := cli.Deploy(ctx, fn, name, bindings); err != nil {
t.Fatal(err)
}
time.Sleep(1 * time.Second)
if err := cli.Destroy(ctx, name); err != nil {
t.Fatal(err)
}
// ctx := context.Background()
// cli, err := CreateClient(ctx)
// if err != nil {
// t.Fatal(err)
// }
//
// name := "helloworld"
// bindings := []types.PortBinding{
// types.PortBinding{
// ServiceBindingPort: 80,
// ContainerExposePort: 3000,
// },
// types.PortBinding{
// ServiceBindingPort: 443,
// ContainerExposePort: 3000,
// },
// }
//
// fn := types.Func{
// Language: "node",
// Source: `
// module.exports = (ctx) => {
// ctx.body = 'hello world'
// }
// `,
// }
// if err := cli.Deploy(ctx, fn, name, name, bindings); err != nil {
// t.Fatal(err)
// }
//
// time.Sleep(1 * time.Second)
//
// if err := cli.Destroy(ctx, name); err != nil {
// t.Fatal(err)
// }
}

8
deploy/k3s/constants.go Normal file
View File

@@ -0,0 +1,8 @@
package k3s
// ConfigMap is the key to function docker project source code in configmap
var ConfigMap = struct {
AppMetaEnvName string
}{
AppMetaEnvName: "APP_META",
}

102
deploy/k3s/deployment.go Normal file
View File

@@ -0,0 +1,102 @@
package k3s
import (
"fmt"
"github.com/metrue/fx/types"
appsv1 "k8s.io/api/apps/v1"
apiv1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func generateDeploymentSpec(
name string,
image string,
bindPorts []types.PortBinding,
replicas int32,
selector map[string]string,
) *appsv1.Deployment {
ports := []apiv1.ContainerPort{}
for index, binding := range bindPorts {
ports = append(ports, apiv1.ContainerPort{
Name: fmt.Sprintf("fx-container-%d", index),
ContainerPort: binding.ContainerExposePort,
})
}
container := apiv1.Container{
Name: "fx-placeholder-container-name",
Image: image,
Ports: ports,
ImagePullPolicy: v1.PullIfNotPresent,
}
return &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: appsv1.DeploymentSpec{
Replicas: &replicas,
Selector: &metav1.LabelSelector{
MatchLabels: selector,
},
Template: apiv1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: selector,
},
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{container},
},
},
},
}
}
// GetDeployment get a deployment
func (k *K3S) GetDeployment(namespace string, name string) (*appsv1.Deployment, error) {
return k.AppsV1().Deployments(namespace).Get(name, metav1.GetOptions{})
}
// CreateDeployment create a deployment
func (k *K3S) CreateDeployment(
namespace string,
name string,
image string,
ports []types.PortBinding,
replicas int32,
selector map[string]string,
) (*appsv1.Deployment, error) {
deployment := generateDeploymentSpec(name, image, ports, replicas, selector)
return k.AppsV1().Deployments(namespace).Create(deployment)
}
// UpdateDeployment update a deployment
func (k *K3S) UpdateDeployment(
namespace string,
name string,
image string,
ports []types.PortBinding,
replicas int32,
selector map[string]string,
) (*appsv1.Deployment, error) {
deployment := generateDeploymentSpec(name, image, ports, replicas, selector)
return k.AppsV1().Deployments(namespace).Update(deployment)
}
// DeleteDeployment delete a deployment
func (k *K3S) DeleteDeployment(namespace string, name string) error {
return k.AppsV1().Deployments(namespace).Delete(name, &metav1.DeleteOptions{})
}
// CreateDeploymentWithInitContainer create a deployment which will wait InitContainer to do the image build before function container start
func (k *K3S) CreateDeploymentWithInitContainer(
namespace string,
name string,
ports []types.PortBinding,
replicas int32,
selector map[string]string,
) (*appsv1.Deployment, error) {
deployment := generateDeploymentSpec(name, name, ports, replicas, selector)
updatedDeployment := injectInitContainer(name, deployment)
return k.AppsV1().Deployments(namespace).Create(updatedDeployment)
}

View File

@@ -0,0 +1,61 @@
package k3s
import (
"os"
"testing"
"github.com/metrue/fx/types"
)
func TestDeployment(t *testing.T) {
namespace := "default"
name := "fx-hello-world"
image := "metrue/kube-hello"
selector := map[string]string{
"app": "fx-app",
}
kubeconfig := os.Getenv("KUBECONFIG")
if kubeconfig == "" {
t.Skip("skip test since no KUBECONFIG given in environment variable")
}
k8s, err := Create()
if err != nil {
t.Fatal(err)
}
if _, err := k8s.GetDeployment(namespace, name); err == nil {
t.Fatalf("should get not found error")
}
replicas := int32(2)
bindings := []types.PortBinding{
types.PortBinding{
ServiceBindingPort: 80,
ContainerExposePort: 3000,
},
types.PortBinding{
ServiceBindingPort: 443,
ContainerExposePort: 3000,
},
}
deployment, err := k8s.CreateDeployment(namespace, name, image, bindings, replicas, selector)
if err != nil {
t.Fatal(err)
}
if deployment == nil {
t.Fatalf("deploymetn should not be %v", nil)
}
if deployment.Name != name {
t.Fatalf("should get %s but got %s", name, deployment.Name)
}
if *deployment.Spec.Replicas != replicas {
t.Fatalf("should get %v but got %v", replicas, deployment.Spec.Replicas)
}
if err := k8s.DeleteDeployment(namespace, name); err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,70 @@
package k3s
import (
appsv1 "k8s.io/api/apps/v1"
apiv1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
)
// This is docker image provided by fx/contrib/docker_packer
// it can build a Docker image with give Docker project source codes encoded with base64
// check the detail fx/contrib/docker_packer/main.go
const image = "metrue/fx-docker"
func injectInitContainer(name string, deployment *appsv1.Deployment) *appsv1.Deployment {
configMapHasToBeReady := true
valueInConfigMapHasToBeReady := true
initContainer := v1.Container{
Name: "fx-docker-build-c",
Image: image,
ImagePullPolicy: v1.PullAlways,
Command: []string{
"/bin/sh",
"-c",
"/usr/bin/docker_packer $(APP_META) " + name,
}, // Maybe it can be passed by Binary data from config map
// Args: []string{"${APP_META}"}, // function source codes and name
VolumeMounts: []v1.VolumeMount{
v1.VolumeMount{
Name: "dockersock",
MountPath: "/var/run/docker.sock",
},
},
Env: []v1.EnvVar{
v1.EnvVar{
Name: ConfigMap.AppMetaEnvName,
ValueFrom: &v1.EnvVarSource{
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
LocalObjectReference: v1.LocalObjectReference{Name: name},
Key: ConfigMap.AppMetaEnvName,
Optional: &valueInConfigMapHasToBeReady,
},
},
},
},
EnvFrom: []v1.EnvFromSource{
v1.EnvFromSource{
ConfigMapRef: &v1.ConfigMapEnvSource{
LocalObjectReference: v1.LocalObjectReference{
Name: name,
},
Optional: &configMapHasToBeReady,
},
},
},
}
volumes := []v1.Volume{
v1.Volume{
Name: "dockersock",
VolumeSource: v1.VolumeSource{
HostPath: &v1.HostPathVolumeSource{
Path: "/var/run/docker.sock",
},
},
},
}
deployment.Spec.Template.Spec.InitContainers = []apiv1.Container{initContainer}
deployment.Spec.Template.Spec.Volumes = volumes
return deployment
}

131
deploy/k3s/k3s.go Normal file
View File

@@ -0,0 +1,131 @@
package k3s
import (
"context"
"os"
"github.com/metrue/fx/deploy"
"github.com/metrue/fx/types"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)
// K3S client
type K3S struct {
*kubernetes.Clientset
}
const namespace = "default"
// Create a k8s cluster client
func Create() (*K3S, error) {
config, err := clientcmd.BuildConfigFromKubeconfigGetter("", clientcmd.NewDefaultClientConfigLoadingRules().Load)
if err != nil {
return nil, err
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
return &K3S{clientset}, nil
}
// Deploy a image to be a service
func (k *K3S) Deploy(
ctx context.Context,
fn types.Func,
name string,
image string,
ports []types.PortBinding,
) error {
selector := map[string]string{
"app": "fx-app-" + name,
}
const replicas = int32(3)
if _, err := k.GetDeployment(namespace, name); err != nil {
// TODO enable passing replica from fx CLI
if _, err := k.CreateDeployment(
namespace,
name,
image,
ports,
replicas,
selector,
); err != nil {
return err
}
} else {
if _, err := k.UpdateDeployment(
namespace,
name,
image,
ports,
replicas,
selector,
); err != nil {
return err
}
}
// TODO fx should be able to know what's the target Kubernetes service platform
// it's going to deploy to
typ := "LoadBalancer"
if os.Getenv("SERVICE_TYPE") != "" {
typ = os.Getenv("SERVICE_TYPE")
}
if _, err := k.GetService(namespace, name); err != nil {
if _, err := k.CreateService(
namespace,
name,
typ,
ports,
selector,
); err != nil {
return err
}
} else {
if _, err := k.UpdateService(
namespace,
name,
typ,
ports,
selector,
); err != nil {
return err
}
}
return nil
}
// Update a service
func (k *K3S) Update(ctx context.Context, name string) error {
return nil
}
// Destroy a service
func (k *K3S) Destroy(ctx context.Context, name string) error {
if err := k.DeleteService(namespace, name); err != nil {
return err
}
if err := k.DeleteDeployment(namespace, name); err != nil {
return err
}
return nil
}
// GetStatus get status of a service
func (k *K3S) GetStatus(ctx context.Context, name string) error {
return nil
}
// List services
func (k *K3S) List(ctx context.Context, name string) ([]types.Service, error) {
return []types.Service{}, nil
}
var (
_ deploy.Deployer = &K3S{}
)

87
deploy/k3s/service.go Normal file
View File

@@ -0,0 +1,87 @@
package k3s
import (
"strconv"
"github.com/metrue/fx/types"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
intstr "k8s.io/apimachinery/pkg/util/intstr"
)
func generateServiceSpec(
namespace string,
name string,
typ string,
bindings []types.PortBinding,
selector map[string]string,
) *apiv1.Service {
servicePorts := []apiv1.ServicePort{}
for index, binding := range bindings {
servicePorts = append(servicePorts, apiv1.ServicePort{
Name: "port-" + strconv.Itoa(index),
Protocol: apiv1.ProtocolTCP,
Port: binding.ServiceBindingPort,
TargetPort: intstr.FromInt(int(binding.ContainerExposePort)),
})
}
return &apiv1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
ClusterName: namespace,
},
Spec: apiv1.ServiceSpec{
Ports: servicePorts,
Type: apiv1.ServiceType(typ),
Selector: selector,
},
}
}
// CreateService create a service
func (k *K3S) CreateService(
namespace string,
name string,
typ string,
bindings []types.PortBinding,
selector map[string]string,
) (*apiv1.Service, error) {
service := generateServiceSpec(namespace, name, typ, bindings, selector)
createdService, err := k.CoreV1().Services(namespace).Create(service)
if err != nil {
return nil, err
}
return createdService, nil
}
// UpdateService update a service
// TODO this method is not perfect yet, should refactor later
func (k *K3S) UpdateService(
namespace string,
name string,
typ string,
bindings []types.PortBinding,
selector map[string]string,
) (*apiv1.Service, error) {
svc, err := k.GetService(namespace, name)
if err != nil {
return nil, err
}
svc.Spec.Selector = selector
svc.Spec.Type = apiv1.ServiceType(typ)
return k.CoreV1().Services(namespace).Update(svc)
}
// DeleteService a service
func (k *K3S) DeleteService(namespace string, name string) error {
// TODO figure out the elegant way to delete a service
options := &metav1.DeleteOptions{}
return k.CoreV1().Services(namespace).Delete(name, options)
}
// GetService get a service
func (k *K3S) GetService(namespace string, name string) (*apiv1.Service, error) {
return k.CoreV1().Services(namespace).Get(name, metav1.GetOptions{})
}

View File

@@ -29,7 +29,7 @@ func generateDeploymentSpec(
Name: "fx-placeholder-container-name",
Image: image,
Ports: ports,
ImagePullPolicy: v1.PullNever,
ImagePullPolicy: v1.PullIfNotPresent,
}
return &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
@@ -98,6 +98,5 @@ func (k *K8S) CreateDeploymentWithInitContainer(
) (*appsv1.Deployment, error) {
deployment := generateDeploymentSpec(name, name, ports, replicas, selector)
updatedDeployment := injectInitContainer(name, deployment)
fmt.Println(updatedDeployment)
return k.AppsV1().Deployments(namespace).Create(updatedDeployment)
}

View File

@@ -2,6 +2,7 @@ package kubernetes
import (
"context"
"os"
"github.com/metrue/fx/deploy"
"github.com/metrue/fx/packer"
@@ -36,6 +37,7 @@ func (k *K8S) Deploy(
ctx context.Context,
fn types.Func,
name string,
image string,
ports []types.PortBinding,
) error {
// put source code of function docker project into k8s config map
@@ -80,10 +82,9 @@ func (k *K8S) Deploy(
// TODO fx should be able to know what's the target Kubernetes service platform
// it's going to deploy to
const isOnPublicCloud = true
typ := "LoadBalancer"
if !isOnPublicCloud {
typ = "NodePort"
if os.Getenv("SERVICE_TYPE") != "" {
typ = os.Getenv("SERVICE_TYPE")
}
if _, err := k.GetService(namespace, name); err != nil {

View File

@@ -40,7 +40,7 @@ module.exports = (ctx) => {
`,
}
ctx := context.Background()
if err := k8s.Deploy(ctx, fn, name, bindings); err != nil {
if err := k8s.Deploy(ctx, fn, name, name, bindings); err != nil {
t.Fatal(err)
}

View File

@@ -1,16 +0,0 @@
# fx on Amazon Lightsai
* make sure your instance have docker installed and running,
* make sure your instance can be ssh login (with user and password)
```
ssh <user>@<host>
```
* make sure your instance accept port 8866
* then you can deploy function to remote host
```
DOCKER_REMOTE_HOST_ADDR=<your host> DOCKER_REMOTE_HOST_USER=<your user> DOCKER_REMOTE_HOST_PASSWORD=<your password> ./build/fx up -p 1234 test/functions/func.js
```

46
docs/ubuntu.md Normal file
View File

@@ -0,0 +1,46 @@
# fx on Ubuntu
> The guide is verified on Amazon Lightsail ubuntu 18.08 instance
## Install Docker
```shell
apt-get remove -y docker docker-engine docker.io containerd runc
apt-get update -y
apt-get install -y apt-transport-https ca-certificates curl software-properties-common lsb-core
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
apt-get update -y
apt-get install -y docker-ce
docker run hello-world
```
## Install fx
```shell
$ curl -o- https://raw.githubusercontent.com/metrue/fx/master/scripts/install.sh | sudo bash
```
## Deploy a function onto localhost
```shell
$ cat func.js
module.exports = (ctx) => {
ctx.body = 'hello world'
}
$ fx up -n test -p 2000 func.js
$ curl 127.0.0.1:2000
```
## Deploy a function onto remote host
* make sure your instance can be ssh login
* make sure your instance accept port 8866
then you can deploy function to remote host
```shell
DOCKER_REMOTE_HOST_ADDR=<your host> DOCKER_REMOTE_HOST_USER=<your user> DOCKER_REMOTE_HOST_PASSWORD=<your password> fx up -p 2000 test/functions/func.js
```

10
fx.go
View File

@@ -15,7 +15,7 @@ import (
"github.com/urfave/cli"
)
const version = "0.8.1"
const version = "0.8.3"
func init() {
go checkForUpdate()
@@ -95,6 +95,12 @@ func main() {
if err := ctx.Use(middlewares.Binding); err != nil {
log.Fatalf("%v", err)
}
if err := ctx.Use(middlewares.Parse); err != nil {
log.Fatalf("%v", err)
}
if err := ctx.Use(middlewares.Build); err != nil {
log.Fatalf("%v", err)
}
return handlers.Up()(ctx)
},
},
@@ -181,6 +187,6 @@ func main() {
}
if err := app.Run(os.Args); err != nil {
log.Fatalf("fx startup with fatal: %v", err)
os.Exit(1)
}
}

3
go.mod
View File

@@ -6,6 +6,7 @@ require (
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
github.com/Microsoft/go-winio v0.4.14 // indirect
github.com/apex/log v1.1.1
github.com/briandowns/spinner v1.7.0
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v0.0.0-20190313072916-46036c230805
github.com/docker/go-connections v0.4.0
@@ -20,6 +21,7 @@ require (
github.com/googleapis/gnostic v0.3.1 // indirect
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-20190810064746-98a7a27048f3
github.com/mholt/archiver v3.1.1+incompatible
github.com/morikuni/aec v1.0.0 // indirect
@@ -29,6 +31,7 @@ require (
github.com/pelletier/go-toml v1.4.0 // indirect
github.com/pierrec/lz4 v0.0.0-20190222153722-062282ea0dcf // indirect
github.com/pkg/errors v0.8.1
github.com/schollz/progressbar/v2 v2.14.0
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.5.0
github.com/stretchr/testify v1.4.0

10
go.sum
View File

@@ -29,6 +29,8 @@ github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN
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=
@@ -64,6 +66,7 @@ github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdf
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
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=
@@ -174,11 +177,14 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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/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=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
@@ -191,6 +197,8 @@ github.com/metrue/go-ssh-client v0.0.0-20190810064746-98a7a27048f3/go.mod h1:ERH
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=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
@@ -251,6 +259,8 @@ github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNue
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/schollz/progressbar/v2 v2.14.0 h1:vo7bdkI9E4/CIk9DnL5uVIaybLQiVtiCC2vO+u9j5IM=
github.com/schollz/progressbar/v2 v2.14.0/go.mod h1:6YZjqdthH6SCZKv2rqGryrxPtfmRB/DWZxSMfCXPyD8=
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=

View File

@@ -3,15 +3,11 @@ set -e
# ++
# verified on Ubuntu 16.04 x64
# ++
user_host=$1
ssh ${user_host} 'bash -s' <<EOF
apt-get remove -y docker docker-engine docker.io containerd runc
apt-get update -y
apt-get install -y apt-transport-https ca-certificates curl software-properties-common lsb-core
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu \$(lsb_release -cs) stable"
apt-get update -y
apt-get install -y docker-ce
docker run hello-world
EOF
apt-get remove -y docker docker-engine docker.io containerd runc
apt-get update -y
apt-get install -y apt-transport-https ca-certificates curl software-properties-common lsb-core
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
apt-get update -y
apt-get install -y docker-ce
docker run hello-world

View File

@@ -3,11 +3,18 @@ package handlers
import (
"github.com/metrue/fx/context"
"github.com/metrue/fx/deploy"
"github.com/metrue/fx/pkg/spinner"
)
// Down command handle
func Down() HandleFunc {
return func(ctx *context.Context) (err error) {
const task = "destroying"
spinner.Start(task)
defer func() {
spinner.Stop(task, err)
}()
cli := ctx.GetCliContext()
services := cli.Args()
runner := ctx.Get("deployer").(deploy.Deployer)

View File

@@ -3,12 +3,19 @@ package handlers
import (
"github.com/metrue/fx/context"
"github.com/metrue/fx/deploy"
"github.com/metrue/fx/pkg/spinner"
"github.com/metrue/fx/utils"
)
// List command handle
func List() HandleFunc {
return func(ctx *context.Context) error {
return func(ctx *context.Context) (err error) {
const task = "deploying"
spinner.Start(task)
defer func() {
spinner.Stop(task, err)
}()
cli := ctx.GetCliContext()
deployer := ctx.Get("deployer").(deploy.Deployer)

View File

@@ -2,14 +2,11 @@ package handlers
import (
"fmt"
"io/ioutil"
"github.com/apex/log"
"github.com/metrue/fx/context"
"github.com/metrue/fx/deploy"
"github.com/metrue/fx/pkg/spinner"
"github.com/metrue/fx/types"
"github.com/metrue/fx/utils"
"github.com/pkg/errors"
)
// PortRange usable port range https: //en.wikipedia.org/wiki/Ephemeral_port
@@ -24,37 +21,29 @@ var PortRange = struct {
// Up command handle
func Up() HandleFunc {
return func(ctx *context.Context) (err error) {
const task = "deploying"
spinner.Start(task)
defer func() {
spinner.Stop(task, err)
}()
cli := ctx.GetCliContext()
funcFile := cli.Args().First()
name := cli.String("name")
port := cli.Int("port")
defer func() {
if r := recover(); r != nil {
log.Fatalf("fatal error happened: %v", r)
}
if err != nil {
log.Fatalf("deploy function %s (%s) failed: %v", err)
}
log.Infof("function %s (%s) deployed successfully", name, funcFile)
}()
if port < PortRange.min || port > PortRange.max {
return fmt.Errorf("invalid port number: %d, port number should in range of %d - %d", port, PortRange.min, PortRange.max)
}
body, err := ioutil.ReadFile(funcFile)
if err != nil {
return errors.Wrap(err, "read source failed")
}
lang := utils.GetLangFromFileName(funcFile)
fn := ctx.Get("fn").(types.Func)
image := ctx.Get("image").(string)
deployer := ctx.Get("deployer").(deploy.Deployer)
bindings := ctx.Get("bindings").([]types.PortBinding)
return deployer.Deploy(
ctx.Context,
types.Func{Language: lang, Source: string(body)},
fn,
name,
image,
bindings,
)
}

View File

@@ -24,6 +24,10 @@ func Binding(ctx *context.Context) error {
ServiceBindingPort: 443,
ContainerExposePort: constants.FxContainerExposePort,
},
types.PortBinding{
ServiceBindingPort: int32(port),
ContainerExposePort: constants.FxContainerExposePort,
},
}
} else {
bindings = []types.PortBinding{

55
middlewares/build.go Normal file
View File

@@ -0,0 +1,55 @@
package middlewares
import (
"fmt"
"os"
"time"
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"
)
// Build image
func Build(ctx *context.Context) (err error) {
const task = "building"
spinner.Start(task)
defer func() {
spinner.Stop(task, err)
}()
cli := ctx.GetCliContext()
name := cli.String("name")
fn := ctx.Get("fn").(types.Func)
docker := ctx.Get("docker").(containerruntimes.ContainerRuntime)
workdir := fmt.Sprintf("/tmp/fx-%d", time.Now().Unix())
defer os.RemoveAll(workdir)
if err := packer.PackIntoDir(fn, workdir); err != nil {
return err
}
if err := docker.BuildImage(ctx.Context, workdir, name); err != nil {
return err
}
nameWithTag := name + ":latest"
if err := docker.TagImage(ctx, name, nameWithTag); err != nil {
return err
}
ctx.Set("image", nameWithTag)
if os.Getenv("K3S") != "" {
name := cli.String("name")
username := os.Getenv("DOCKER_USERNAME")
password := os.Getenv("DOCKER_PASSWORD")
if username != "" && password != "" {
if _, err := docker.PushImage(ctx.Context, name); err != nil {
return err
}
ctx.Set("image", username+"/"+name)
}
}
return nil
}

35
middlewares/parse.go Normal file
View File

@@ -0,0 +1,35 @@
package middlewares
import (
"io/ioutil"
"github.com/metrue/fx/context"
"github.com/metrue/fx/pkg/spinner"
"github.com/metrue/fx/types"
"github.com/metrue/fx/utils"
"github.com/pkg/errors"
)
// Parse parse input
func Parse(ctx *context.Context) (err error) {
const task = "parsing"
spinner.Start(task)
defer func() {
spinner.Stop(task, err)
}()
cli := ctx.GetCliContext()
funcFile := cli.Args().First()
lang := utils.GetLangFromFileName(funcFile)
body, err := ioutil.ReadFile(funcFile)
if err != nil {
return errors.Wrap(err, "read source failed")
}
fn := types.Func{
Language: lang,
Source: string(body),
}
ctx.Set("fn", fn)
return nil
}

View File

@@ -10,29 +10,32 @@ import (
"github.com/metrue/fx/context"
"github.com/metrue/fx/deploy"
dockerDeployer "github.com/metrue/fx/deploy/docker"
k3sDeployer "github.com/metrue/fx/deploy/k3s"
k8sDeployer "github.com/metrue/fx/deploy/kubernetes"
"github.com/metrue/fx/pkg/spinner"
"github.com/metrue/fx/provision"
)
// Setup create k8s or docker cli
func Setup(ctx *context.Context) (err error) {
var deployer deploy.Deployer
if os.Getenv("KUBECONFIG") != "" {
deployer, err = k8sDeployer.Create()
if err != nil {
return err
}
} else {
deployer, err = dockerDeployer.CreateClient(ctx.Context)
if err != nil {
return err
}
}
ctx.Set("deployer", deployer)
const task = "setup"
spinner.Start(task)
defer func() {
spinner.Stop(task, err)
}()
host := os.Getenv("DOCKER_REMOTE_HOST_ADDR")
user := os.Getenv("DOCKER_REMOTE_HOST_USER")
passord := os.Getenv("DOCKER_REMOTE_HOST_PASSWORD")
var docker containerruntimes.ContainerRuntime
if host != "" && user != "" {
provisioner := provision.NewWithHost(host, user, passord)
if !provisioner.IsFxAgentRunning() {
if err := provisioner.StartFxAgent(); err != nil {
return err
}
}
docker, err = dockerHTTP.Create(host, constants.AgentPort)
if err != nil {
return err
@@ -45,5 +48,24 @@ func Setup(ctx *context.Context) (err error) {
}
ctx.Set("docker", docker)
var deployer deploy.Deployer
if os.Getenv("K3S") != "" {
deployer, err = k3sDeployer.Create()
if err != nil {
return err
}
} else if os.Getenv("KUBECONFIG") != "" {
deployer, err = k8sDeployer.Create()
if err != nil {
return err
}
} else {
deployer, err = dockerDeployer.CreateClient(ctx.Context)
if err != nil {
return err
}
}
ctx.Set("deployer", deployer)
return nil
}

52
pkg/spinner/spiner.go Normal file
View File

@@ -0,0 +1,52 @@
package spinner
import (
"fmt"
"math/rand"
"time"
"github.com/briandowns/spinner"
aurora "github.com/logrusorgru/aurora"
)
var s *spinner.Spinner
func init() {
style := spinner.CharSets[36]
interval := 100 * time.Millisecond
s = spinner.New(style, interval)
}
// Start spinner
func Start(task string) {
colors := []string{
"red",
"green",
"yellow",
"blue",
"magenta",
"cyan",
"white",
}
rand.Seed(time.Now().UnixNano())
// nolint
s.Color(colors[rand.Intn(len(colors))])
s.Prefix = task + " "
if s.Active() {
s.Restart()
} else {
s.Start()
}
}
// Stop spinner
func Stop(task string, err error) {
if err != nil {
fmt.Println(aurora.Red("\u2717"))
fmt.Println(aurora.Red("*****************"))
fmt.Println(err)
fmt.Println(aurora.Red("*****************"))
}
s.Stop()
}