Compare commits
7 Commits
0.7.3-alph
...
0.7.51-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d91a9959a8 | ||
|
|
87e7c7d6ae | ||
|
|
89c94daebc | ||
|
|
047fac2a0a | ||
|
|
1cb68766f7 | ||
|
|
91fd5dc59f | ||
|
|
184235acb2 |
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@@ -39,6 +39,14 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
make build
|
make build
|
||||||
|
|
||||||
|
- name: test fx-docker
|
||||||
|
run: |
|
||||||
|
cd ./contrib/docker_packer
|
||||||
|
make linux-build
|
||||||
|
make docker-build
|
||||||
|
make test
|
||||||
|
# make docker-publish #TODO in release workflow
|
||||||
|
|
||||||
- name: lint
|
- name: lint
|
||||||
run: |
|
run: |
|
||||||
export GOBIN=$(go env GOPATH)/bin
|
export GOBIN=$(go env GOPATH)/bin
|
||||||
@@ -56,13 +64,11 @@ jobs:
|
|||||||
- name: test AKS
|
- name: test AKS
|
||||||
env:
|
env:
|
||||||
AKS_KUBECONFIG: ${{ secrets.AKS_KUBECONFIG }}
|
AKS_KUBECONFIG: ${{ secrets.AKS_KUBECONFIG }}
|
||||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
run: |
|
run: |
|
||||||
export KUBECONFIG=${HOME}/.kube/aks
|
export KUBECONFIG=${HOME}/.kube/aks
|
||||||
echo ${AKS_KUBECONFIG} | base64 -d > $KUBECONFIG
|
echo ${AKS_KUBECONFIG} | base64 -d > $KUBECONFIG
|
||||||
if [[ -z "$DOCKER_USERNAME" || -z "$DOCKER_PASSWORD" || -z "$AKS_KUBECONFIG" ]];then
|
if [[ -z "$AKS_KUBECONFIG" ]];then
|
||||||
echo "skip deploy test since no DOCKER_USERNAME and DOCKER_PASSWORD set"
|
echo "skip deploy test since no valid KUBECONFIG"
|
||||||
else
|
else
|
||||||
DEBUG=true ./build/fx deploy -n hello -p 12345 examples/functions/JavaScript/func.js
|
DEBUG=true ./build/fx deploy -n hello -p 12345 examples/functions/JavaScript/func.js
|
||||||
./build/fx destroy hello
|
./build/fx destroy hello
|
||||||
|
|||||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -97,7 +97,7 @@ jobs:
|
|||||||
git config --global user.name "Minghe Huang"
|
git config --global user.name "Minghe Huang"
|
||||||
|
|
||||||
commit=$(git rev-parse --short HEAD)
|
commit=$(git rev-parse --short HEAD)
|
||||||
version=$(cat fx.go| grep Version | awk -F'"' '{print $2}')
|
version=$(cat fx.go| grep 'const version' | awk -F'"' '{print $2}')
|
||||||
|
|
||||||
echo "workflow is running on branch ${GITHUB_REF}"
|
echo "workflow is running on branch ${GITHUB_REF}"
|
||||||
|
|
||||||
|
|||||||
47
README.md
47
README.md
@@ -304,33 +304,54 @@ Thank you to all the people who already contributed to fx!
|
|||||||
<a href="https://github.com/metrue" target="_blank">
|
<a href="https://github.com/metrue" target="_blank">
|
||||||
<img alt="metrue" src="https://avatars2.githubusercontent.com/u/1001246?v=4&s=50" width="50">
|
<img alt="metrue" src="https://avatars2.githubusercontent.com/u/1001246?v=4&s=50" width="50">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/pplam" target="_blank">
|
|
||||||
<img alt="pplam" src="https://avatars2.githubusercontent.com/u/12783579?v=4&s=50" width="50">
|
|
||||||
</a>
|
|
||||||
<a href="https://github.com/muka" target="_blank">
|
<a href="https://github.com/muka" target="_blank">
|
||||||
<img alt="muka" src="https://avatars2.githubusercontent.com/u/1021269?v=4&s=50" width="50">
|
<img alt="muka" src="https://avatars2.githubusercontent.com/u/1021269?v=4&s=50" width="50">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/xwjdsh" target="_blank">
|
<a href="https://github.com/pplam" target="_blank">
|
||||||
<img alt="xwjdsh" src="https://avatars2.githubusercontent.com/u/11025519?v=4&s=50" width="50">
|
<img alt="pplam" src="https://avatars2.githubusercontent.com/u/12783579?v=4&s=50" width="50">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/mbesancon" target="_blank">
|
<a href="https://github.com/matbesancon" target="_blank">
|
||||||
<img alt="mbesancon" src="https://avatars2.githubusercontent.com/u/7623090?v=4&s=50" width="50">
|
<img alt="mbesancon" src="https://avatars2.githubusercontent.com/u/7623090?s=60&v=4" width="50">
|
||||||
</a>
|
|
||||||
<a href="https://github.com/avelino" target="_blank">
|
|
||||||
<img alt="avelino" src="https://avatars2.githubusercontent.com/u/31996?v=4&s=50" width="50">
|
|
||||||
</a>
|
|
||||||
<a href="https://github.com/DaidoujiChen" target="_blank">
|
|
||||||
<img alt="DaidoujiChen" src="https://avatars0.githubusercontent.com/u/670441?v=4&s=50" width="50">
|
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/chlins" target="_blank">
|
<a href="https://github.com/chlins" target="_blank">
|
||||||
<img alt="chlins" src="https://avatars2.githubusercontent.com/u/31262637?v=4&s=50" width="50">
|
<img alt="chlins" src="https://avatars2.githubusercontent.com/u/31262637?v=4&s=50" width="50">
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://github.com/xwjdsh" target="_blank">
|
||||||
|
<img alt="xwjdsh" src="https://avatars2.githubusercontent.com/u/11025519?v=4&s=50" width="50">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/DaidoujiChen" target="_blank">
|
||||||
|
<img alt="DaidoujiChen" src="https://avatars0.githubusercontent.com/u/670441?v=4&s=50" width="50">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/avelino" target="_blank">
|
||||||
|
<img alt="avelino" src="https://avatars2.githubusercontent.com/u/31996?v=4&s=50" width="50">
|
||||||
|
</a>
|
||||||
<a href="https://github.com/andre2007" target="_blank">
|
<a href="https://github.com/andre2007" target="_blank">
|
||||||
<img alt="andre2007" src="https://avatars1.githubusercontent.com/u/1451047?s=50&v=4" width="50">
|
<img alt="andre2007" src="https://avatars1.githubusercontent.com/u/1451047?s=50&v=4" width="50">
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://github.com/polyrabbit" target="_blank">
|
||||||
|
<img alt="polyrabbit" src="https://avatars0.githubusercontent.com/u/2657334?s=60&v=4" width="50">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/johnlunney" target="_blank">
|
||||||
|
<img alt="johnlunney" src="https://avatars3.githubusercontent.com/u/536947?s=60&v=4" width="50">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/tbrand" target="_blank">
|
||||||
|
<img alt="tbrand" src="https://avatars0.githubusercontent.com/u/3483230?s=60&v=4" width="50">
|
||||||
|
</a>
|
||||||
<a href="https://github.com/steventhanna" target="_blank">
|
<a href="https://github.com/steventhanna" target="_blank">
|
||||||
<img alt="andre2007" src="https://avatars1.githubusercontent.com/u/2541678?s=50&v=4" width="50">
|
<img alt="andre2007" src="https://avatars1.githubusercontent.com/u/2541678?s=50&v=4" width="50">
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://github.com/border-radius" target="_blank">
|
||||||
|
<img alt="border-radius" src="https://avatars0.githubusercontent.com/u/3204785?s=60&v=4" width="50">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/Russtopia" target="_blank">
|
||||||
|
<img alt="Russtopia" src="https://avatars1.githubusercontent.com/u/2966177?s=60&v=4<Paste>" width="50">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/FrontMage" target="_blank">
|
||||||
|
<img alt="FrontMage" src="https://avatars2.githubusercontent.com/u/17007026?s=60&v=4" width="50">
|
||||||
|
</a>
|
||||||
|
<a href="https://github.com/DropNib" target="_blank">
|
||||||
|
<img alt="DropNib" src="https://avatars0.githubusercontent.com/u/32019589?s=60&v=4" width="50">
|
||||||
|
</a>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -135,27 +135,28 @@ func (d *Docker) InspectImage(ctx context.Context, name string, img interface{})
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StartContainer create and start a container from given image
|
// StartContainer create and start a container from given image
|
||||||
func (d *Docker) StartContainer(ctx context.Context, name string, image string, ports []int32) error {
|
func (d *Docker) StartContainer(ctx context.Context, name string, image string, ports []types.PortBinding) error {
|
||||||
config := &dockerTypesContainer.Config{
|
portSet := nat.PortSet{}
|
||||||
Image: image,
|
portMap := nat.PortMap{}
|
||||||
ExposedPorts: nat.PortSet{
|
for _, binding := range ports {
|
||||||
"3000/tcp": struct{}{},
|
bindings := []nat.PortBinding{
|
||||||
},
|
nat.PortBinding{
|
||||||
|
HostIP: types.DefaultHost,
|
||||||
|
HostPort: fmt.Sprintf("%d", binding.ServiceBindingPort),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
port := nat.Port(fmt.Sprintf("%d/tcp", binding.ContainerExposePort))
|
||||||
|
portSet[port] = struct{}{}
|
||||||
|
portMap[port] = bindings
|
||||||
}
|
}
|
||||||
|
config := &dockerTypesContainer.Config{
|
||||||
bindings := []nat.PortBinding{}
|
Image: image,
|
||||||
for _, port := range ports {
|
ExposedPorts: portSet,
|
||||||
bindings = append(bindings, nat.PortBinding{
|
|
||||||
HostIP: types.DefaultHost,
|
|
||||||
HostPort: fmt.Sprintf("%d", port),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hostConfig := &dockerTypesContainer.HostConfig{
|
hostConfig := &dockerTypesContainer.HostConfig{
|
||||||
AutoRemove: true,
|
AutoRemove: true,
|
||||||
PortBindings: nat.PortMap{
|
PortBindings: portMap,
|
||||||
"3000/tcp": bindings,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
resp, err := d.ContainerCreate(ctx, config, hostConfig, nil, name)
|
resp, err := d.ContainerCreate(ctx, config, hostConfig, nil, name)
|
||||||
if os.Getenv("DEBUG") != "" {
|
if os.Getenv("DEBUG") != "" {
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
package containerruntimes
|
package containerruntimes
|
||||||
|
|
||||||
import "context"
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/metrue/fx/types"
|
||||||
|
)
|
||||||
|
|
||||||
// ContainerRuntime interface
|
// ContainerRuntime interface
|
||||||
type ContainerRuntime interface {
|
type ContainerRuntime interface {
|
||||||
BuildImage(ctx context.Context, workdir string, name string) error
|
BuildImage(ctx context.Context, workdir string, name string) error
|
||||||
PushImage(ctx context.Context, name string) (string, error)
|
PushImage(ctx context.Context, name string) (string, error)
|
||||||
InspectImage(ct context.Context, name string, img interface{}) error
|
InspectImage(ct context.Context, name string, img interface{}) error
|
||||||
StartContainer(ctx context.Context, name string, image string, ports []int32) error
|
StartContainer(ctx context.Context, name string, image string, bindings []types.PortBinding) error
|
||||||
StopContainer(ctx context.Context, name string) error
|
StopContainer(ctx context.Context, name string) error
|
||||||
InspectContainer(ctx context.Context, name string, container interface{}) error
|
InspectContainer(ctx context.Context, name string, container interface{}) error
|
||||||
}
|
}
|
||||||
|
|||||||
3
contrib/docker_packer/Dockerfile
Normal file
3
contrib/docker_packer/Dockerfile
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
FROM docker
|
||||||
|
|
||||||
|
ADD ./build/docker_packer /usr/bin/docker_packer
|
||||||
21
contrib/docker_packer/Makefile
Normal file
21
contrib/docker_packer/Makefile
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
GOBIN ?= ./build
|
||||||
|
GIT_VERSION := $(shell git describe --tags)
|
||||||
|
VERSION ?= $(GIT_VERSION)
|
||||||
|
|
||||||
|
REPO ?= "metrue/fx-docker"
|
||||||
|
TAG ?= "latest"
|
||||||
|
|
||||||
|
build:
|
||||||
|
CGO_ENABLED=0 go build -ldflags "-X main.Version=$(VERSION)" -v -o $(GOBIN)/docker_packer main.go
|
||||||
|
linux-build:
|
||||||
|
CGO_ENABLED=0 GOOS=linux go build -ldflags "-X main.Version=$(VERSION)" -v -o $(GOBIN)/docker_packer main.go
|
||||||
|
docker-build:
|
||||||
|
docker build -t ${REPO}:${TAG} .
|
||||||
|
docker-publish:
|
||||||
|
docker push ${REPO}:${TAG}
|
||||||
|
test:
|
||||||
|
docker run -v /var/run/docker.sock:/var/run/docker.sock ${REPO}:${TAG} docker_packer 'eyJEb2NrZXJmaWxlIjoiRlJPTSBtZXRydWUvZngtbm9kZS1iYXNlXG5cbkNPUFkgLiAuXG5FWFBPU0UgMzAwMFxuQ01EIFtcIm5vZGVcIiwgXCJhcHAuanNcIl1cbiIsImFwcC5qcyI6ImNvbnN0IEtvYSA9IHJlcXVpcmUoJ2tvYScpO1xuY29uc3QgYm9keVBhcnNlciA9IHJlcXVpcmUoJ2tvYS1ib2R5cGFyc2VyJyk7XG5jb25zdCBmeCA9IHJlcXVpcmUoJy4vZngnKTtcblxuY29uc3QgYXBwID0gbmV3IEtvYSgpO1xuYXBwLnVzZShib2R5UGFyc2VyKCkpO1xuYXBwLnVzZShmeCk7XG5cbmFwcC5saXN0ZW4oMzAwMCk7XG4iLCJmeC5qcyI6IlxubW9kdWxlLmV4cG9ydHMgPSAoY3R4KSA9XHUwMDNlIHtcblx0Y3R4LmJvZHkgPSAnaGVsbG8gd29ybGQnXG59XG4ifQ==' app-hello
|
||||||
|
docker run --rm -d -p 3000:3000 --name test-app-hello-container app-hello
|
||||||
|
sleep 2
|
||||||
|
curl 127.0.0.1:3000
|
||||||
|
docker stop test-app-hello-container
|
||||||
80
contrib/docker_packer/main.go
Normal file
80
contrib/docker_packer/main.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
dockerTypes "github.com/docker/docker/api/types"
|
||||||
|
runtime "github.com/metrue/fx/container_runtimes/docker/sdk"
|
||||||
|
"github.com/metrue/fx/packer"
|
||||||
|
"github.com/metrue/fx/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Version binary version
|
||||||
|
var Version = "0.0.1"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// TODO clean it up
|
||||||
|
os.Setenv("DEBUG", "true")
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
args := os.Args
|
||||||
|
|
||||||
|
if len(args) != 3 {
|
||||||
|
fmt.Println(`Usage:
|
||||||
|
docker_packer <encrypt_docker_project_source_tree> <image_name>
|
||||||
|
`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
meta := args[1]
|
||||||
|
name := args[2]
|
||||||
|
|
||||||
|
str, err := base64.StdEncoding.WithPadding(base64.StdPadding).DecodeString(meta)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could decode meta: %s, %v", meta, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var tree map[string]string
|
||||||
|
if err := json.Unmarshal([]byte(str), &tree); err != nil {
|
||||||
|
log.Fatalf("could not unmarshal meta: %s", meta)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
workdir := "/tmp/fx"
|
||||||
|
if err := packer.TreeToDir(tree, workdir); err != nil {
|
||||||
|
log.Fatalf("could not restore to dir: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(workdir)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
dockerClient, err := runtime.CreateClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not create a docker client: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
if err := dockerClient.BuildImage(ctx, workdir, name); err != nil {
|
||||||
|
log.Fatalf("could not build image: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
nameWithTag := name + ":latest"
|
||||||
|
if err := dockerClient.ImageTag(ctx, name, nameWithTag); err != nil {
|
||||||
|
log.Fatalf("could tag image: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
var imgInfo dockerTypes.ImageInspect
|
||||||
|
if err := utils.RunWithRetry(func() error {
|
||||||
|
return dockerClient.InspectImage(context.Background(), name, &imgInfo)
|
||||||
|
}, time.Second*1, 5); err != nil {
|
||||||
|
fmt.Printf("inspect image failed: %s", err)
|
||||||
|
}
|
||||||
|
fmt.Println("image built succcessfully")
|
||||||
|
}
|
||||||
@@ -1,10 +1,14 @@
|
|||||||
package deploy
|
package deploy
|
||||||
|
|
||||||
import "context"
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
types "github.com/metrue/fx/types"
|
||||||
|
)
|
||||||
|
|
||||||
// Deployer make a image a service
|
// Deployer make a image a service
|
||||||
type Deployer interface {
|
type Deployer interface {
|
||||||
Deploy(ctx context.Context, workdir string, name string, ports []int32) error
|
Deploy(ctx context.Context, fn types.Func, name string, bindings []types.PortBinding) error
|
||||||
Destroy(ctx context.Context, name string) error
|
Destroy(ctx context.Context, name string) error
|
||||||
Update(ctx context.Context, name string) error
|
Update(ctx context.Context, name string) error
|
||||||
GetStatus(ctx context.Context, name string) error
|
GetStatus(ctx context.Context, name string) error
|
||||||
|
|||||||
@@ -2,11 +2,16 @@ package docker
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
dockerTypes "github.com/docker/docker/api/types"
|
dockerTypes "github.com/docker/docker/api/types"
|
||||||
runtime "github.com/metrue/fx/container_runtimes/docker/sdk"
|
runtime "github.com/metrue/fx/container_runtimes/docker/sdk"
|
||||||
"github.com/metrue/fx/deploy"
|
"github.com/metrue/fx/deploy"
|
||||||
|
"github.com/metrue/fx/packer"
|
||||||
|
"github.com/metrue/fx/types"
|
||||||
"github.com/metrue/fx/utils"
|
"github.com/metrue/fx/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -25,17 +30,24 @@ func CreateClient(ctx context.Context) (*Docker, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Deploy create a Docker container from given image, and bind the constants.FxContainerExposePort to given port
|
// Deploy create a Docker container from given image, and bind the constants.FxContainerExposePort to given port
|
||||||
func (d *Docker) Deploy(ctx context.Context, workdir string, name string, ports []int32) error {
|
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 {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if err := d.client.BuildImage(ctx, workdir, name); err != nil {
|
if err := d.client.BuildImage(ctx, workdir, name); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// config := &dockerTypesContainer.Config{
|
nameWithTag := name + ":latest"
|
||||||
// Image: image,
|
if err := d.client.ImageTag(ctx, name, nameWithTag); err != nil {
|
||||||
// ExposedPorts: nat.PortSet{
|
log.Fatalf("could tag image: %v", err)
|
||||||
// "3000/tcp": struct{}{},
|
return err
|
||||||
// },
|
}
|
||||||
// }
|
|
||||||
// when deploy a function on a bare Docker running without Kubernetes,
|
// 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
|
// image would be built on-demand on host locally, so there is no need to
|
||||||
// pull image from remote.
|
// pull image from remote.
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/metrue/fx/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDocker(t *testing.T) {
|
func TestDocker(t *testing.T) {
|
||||||
@@ -13,10 +15,27 @@ func TestDocker(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
workdir := "./fixture"
|
|
||||||
name := "helloworld"
|
name := "helloworld"
|
||||||
ports := []int32{12345, 12346}
|
bindings := []types.PortBinding{
|
||||||
if err := cli.Deploy(ctx, workdir, name, ports); err != nil {
|
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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
47
deploy/kubernetes/configmap.go
Normal file
47
deploy/kubernetes/configmap.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package kubernetes
|
||||||
|
|
||||||
|
import (
|
||||||
|
apiv1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateConfigMap create a config map with data
|
||||||
|
func (k *K8S) CreateConfigMap(namespace string, name string, data map[string]string) (*apiv1.ConfigMap, error) {
|
||||||
|
cm := &apiv1.ConfigMap{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
return k.CoreV1().ConfigMaps(namespace).Create(cm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteConfigMap delete a config map
|
||||||
|
func (k *K8S) DeleteConfigMap(namespace string, name string) error {
|
||||||
|
return k.CoreV1().ConfigMaps(namespace).Delete(name, &metav1.DeleteOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateConfigMap update a config map
|
||||||
|
func (k *K8S) UpdateConfigMap(namespace string, name string, data map[string]string) (*apiv1.ConfigMap, error) {
|
||||||
|
cm := &apiv1.ConfigMap{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
Data: data,
|
||||||
|
}
|
||||||
|
return k.CoreV1().ConfigMaps(namespace).Update(cm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfigMap get a config map
|
||||||
|
func (k *K8S) GetConfigMap(namespace string, name string) (*apiv1.ConfigMap, error) {
|
||||||
|
return k.CoreV1().ConfigMaps(namespace).Get(name, metav1.GetOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateOrUpdateConfigMap create or update a config map
|
||||||
|
func (k *K8S) CreateOrUpdateConfigMap(namespace string, name string, data map[string]string) (*apiv1.ConfigMap, error) {
|
||||||
|
_, err := k.GetConfigMap(namespace, name)
|
||||||
|
if err != nil {
|
||||||
|
return k.CreateConfigMap(namespace, name, data)
|
||||||
|
}
|
||||||
|
return k.UpdateConfigMap(namespace, name, data)
|
||||||
|
}
|
||||||
35
deploy/kubernetes/configmap_test.go
Normal file
35
deploy/kubernetes/configmap_test.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package kubernetes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConfigMap(t *testing.T) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace := "default"
|
||||||
|
name := "test-configmap"
|
||||||
|
data := map[string]string{
|
||||||
|
"message": "hello world",
|
||||||
|
}
|
||||||
|
cm, err := k8s.CreateConfigMap(namespace, name, data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if cm.Name != name {
|
||||||
|
t.Fatalf("should get %s but got %s", name, cm.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != k8s.DeleteConfigMap(namespace, name) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
8
deploy/kubernetes/constants.go
Normal file
8
deploy/kubernetes/constants.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package kubernetes
|
||||||
|
|
||||||
|
// ConfigMap is the key to function docker project source code in configmap
|
||||||
|
var ConfigMap = struct {
|
||||||
|
AppMetaEnvName string
|
||||||
|
}{
|
||||||
|
AppMetaEnvName: "APP_META",
|
||||||
|
}
|
||||||
@@ -1,27 +1,35 @@
|
|||||||
package kubernetes
|
package kubernetes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/metrue/fx/constants"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/metrue/fx/types"
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
apiv1 "k8s.io/api/core/v1"
|
apiv1 "k8s.io/api/core/v1"
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
func generateDeploymentSpec(
|
func generateDeploymentSpec(
|
||||||
name string,
|
name string,
|
||||||
image string,
|
image string,
|
||||||
|
bindPorts []types.PortBinding,
|
||||||
replicas int32,
|
replicas int32,
|
||||||
selector map[string]string,
|
selector map[string]string,
|
||||||
) *appsv1.Deployment {
|
) *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{
|
container := apiv1.Container{
|
||||||
Name: "fx-placeholder-container-name",
|
Name: "fx-placeholder-container-name",
|
||||||
Image: image,
|
Image: image,
|
||||||
Ports: []apiv1.ContainerPort{
|
Ports: ports,
|
||||||
apiv1.ContainerPort{
|
ImagePullPolicy: v1.PullNever,
|
||||||
Name: "fx-container",
|
|
||||||
ContainerPort: constants.FxContainerExposePort,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
return &appsv1.Deployment{
|
return &appsv1.Deployment{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
@@ -50,14 +58,28 @@ func (k *K8S) GetDeployment(namespace string, name string) (*appsv1.Deployment,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreateDeployment create a deployment
|
// CreateDeployment create a deployment
|
||||||
func (k *K8S) CreateDeployment(namespace string, name string, image string, replicas int32, selector map[string]string) (*appsv1.Deployment, error) {
|
func (k *K8S) CreateDeployment(
|
||||||
deployment := generateDeploymentSpec(name, image, replicas, selector)
|
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)
|
return k.AppsV1().Deployments(namespace).Create(deployment)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateDeployment update a deployment
|
// UpdateDeployment update a deployment
|
||||||
func (k *K8S) UpdateDeployment(namespace string, name string, image string, replicas int32, selector map[string]string) (*appsv1.Deployment, error) {
|
func (k *K8S) UpdateDeployment(
|
||||||
deployment := generateDeploymentSpec(name, image, replicas, selector)
|
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)
|
return k.AppsV1().Deployments(namespace).Update(deployment)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,3 +87,17 @@ func (k *K8S) UpdateDeployment(namespace string, name string, image string, repl
|
|||||||
func (k *K8S) DeleteDeployment(namespace string, name string) error {
|
func (k *K8S) DeleteDeployment(namespace string, name string) error {
|
||||||
return k.AppsV1().Deployments(namespace).Delete(name, &metav1.DeleteOptions{})
|
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 *K8S) 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)
|
||||||
|
fmt.Println(updatedDeployment)
|
||||||
|
return k.AppsV1().Deployments(namespace).Create(updatedDeployment)
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package kubernetes
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/metrue/fx/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDeployment(t *testing.T) {
|
func TestDeployment(t *testing.T) {
|
||||||
@@ -27,7 +29,17 @@ func TestDeployment(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
replicas := int32(2)
|
replicas := int32(2)
|
||||||
deployment, err := k8s.CreateDeployment(namespace, name, image, replicas, selector)
|
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 {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
70
deploy/kubernetes/init_container.go
Normal file
70
deploy/kubernetes/init_container.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package kubernetes
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -3,8 +3,9 @@ package kubernetes
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
runtime "github.com/metrue/fx/container_runtimes/docker/sdk"
|
|
||||||
"github.com/metrue/fx/deploy"
|
"github.com/metrue/fx/deploy"
|
||||||
|
"github.com/metrue/fx/packer"
|
||||||
|
"github.com/metrue/fx/types"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
)
|
)
|
||||||
@@ -33,25 +34,21 @@ func Create() (*K8S, error) {
|
|||||||
// Deploy a image to be a service
|
// Deploy a image to be a service
|
||||||
func (k *K8S) Deploy(
|
func (k *K8S) Deploy(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
workdir string,
|
fn types.Func,
|
||||||
name string,
|
name string,
|
||||||
ports []int32,
|
ports []types.PortBinding,
|
||||||
) error {
|
) error {
|
||||||
dockerClient, err := runtime.CreateClient(ctx)
|
// put source code of function docker project into k8s config map
|
||||||
|
tree, err := packer.PackIntoK8SConfigMapFile(fn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := dockerClient.BuildImage(ctx, workdir, name); err != nil {
|
data := map[string]string{}
|
||||||
return err
|
data[ConfigMap.AppMetaEnvName] = tree
|
||||||
}
|
if _, err := k.CreateOrUpdateConfigMap(namespace, name, data); err != nil {
|
||||||
image, err := dockerClient.PushImage(ctx, name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// By using a label selector between Pod and Service, we can link Service and Pod directly, it means a Endpoint will
|
|
||||||
// be created automatically, then incoming traffic to Service will be forward to Pod.
|
|
||||||
// Then we have no need to create Endpoint manually anymore.
|
|
||||||
selector := map[string]string{
|
selector := map[string]string{
|
||||||
"app": "fx-app-" + name,
|
"app": "fx-app-" + name,
|
||||||
}
|
}
|
||||||
@@ -59,17 +56,24 @@ func (k *K8S) Deploy(
|
|||||||
const replicas = int32(3)
|
const replicas = int32(3)
|
||||||
if _, err := k.GetDeployment(namespace, name); err != nil {
|
if _, err := k.GetDeployment(namespace, name); err != nil {
|
||||||
// TODO enable passing replica from fx CLI
|
// TODO enable passing replica from fx CLI
|
||||||
if _, err := k.CreateDeployment(
|
if _, err := k.CreateDeploymentWithInitContainer(
|
||||||
namespace,
|
namespace,
|
||||||
name,
|
name,
|
||||||
image,
|
ports,
|
||||||
replicas,
|
replicas,
|
||||||
selector,
|
selector,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if _, err := k.UpdateDeployment(namespace, name, image, replicas, selector); err != nil {
|
if _, err := k.UpdateDeployment(
|
||||||
|
namespace,
|
||||||
|
name,
|
||||||
|
name,
|
||||||
|
ports,
|
||||||
|
replicas,
|
||||||
|
selector,
|
||||||
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,22 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/metrue/fx/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestK8SDeployer(t *testing.T) {
|
func TestK8SDeployer(t *testing.T) {
|
||||||
workdir := "./fixture"
|
name := "hellohello"
|
||||||
name := "hello"
|
bindings := []types.PortBinding{
|
||||||
ports := []int32{32300}
|
types.PortBinding{
|
||||||
|
ServiceBindingPort: 80,
|
||||||
|
ContainerExposePort: 3000,
|
||||||
|
},
|
||||||
|
types.PortBinding{
|
||||||
|
ServiceBindingPort: 443,
|
||||||
|
ContainerExposePort: 3000,
|
||||||
|
},
|
||||||
|
}
|
||||||
kubeconfig := os.Getenv("KUBECONFIG")
|
kubeconfig := os.Getenv("KUBECONFIG")
|
||||||
username := os.Getenv("DOCKER_USERNAME")
|
username := os.Getenv("DOCKER_USERNAME")
|
||||||
password := os.Getenv("DOCKER_PASSWORD")
|
password := os.Getenv("DOCKER_PASSWORD")
|
||||||
@@ -21,8 +31,16 @@ func TestK8SDeployer(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn := types.Func{
|
||||||
|
Language: "node",
|
||||||
|
Source: `
|
||||||
|
module.exports = (ctx) => {
|
||||||
|
ctx.body = 'hello world'
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
if err := k8s.Deploy(ctx, workdir, name, ports); err != nil {
|
if err := k8s.Deploy(ctx, fn, name, bindings); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package kubernetes
|
|||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/metrue/fx/constants"
|
"github.com/metrue/fx/types"
|
||||||
apiv1 "k8s.io/api/core/v1"
|
apiv1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
intstr "k8s.io/apimachinery/pkg/util/intstr"
|
intstr "k8s.io/apimachinery/pkg/util/intstr"
|
||||||
@@ -13,30 +13,16 @@ func generateServiceSpec(
|
|||||||
namespace string,
|
namespace string,
|
||||||
name string,
|
name string,
|
||||||
typ string,
|
typ string,
|
||||||
ports []int32,
|
bindings []types.PortBinding,
|
||||||
selector map[string]string,
|
selector map[string]string,
|
||||||
) *apiv1.Service {
|
) *apiv1.Service {
|
||||||
servicePorts := []apiv1.ServicePort{
|
servicePorts := []apiv1.ServicePort{}
|
||||||
apiv1.ServicePort{
|
for index, binding := range bindings {
|
||||||
Name: "http",
|
|
||||||
Protocol: apiv1.ProtocolTCP,
|
|
||||||
Port: 80,
|
|
||||||
TargetPort: intstr.FromInt(int(constants.FxContainerExposePort)),
|
|
||||||
},
|
|
||||||
apiv1.ServicePort{
|
|
||||||
Name: "https",
|
|
||||||
Protocol: apiv1.ProtocolTCP,
|
|
||||||
Port: 443,
|
|
||||||
TargetPort: intstr.FromInt(int(constants.FxContainerExposePort)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
// Append custom Port
|
|
||||||
for index, port := range ports {
|
|
||||||
servicePorts = append(servicePorts, apiv1.ServicePort{
|
servicePorts = append(servicePorts, apiv1.ServicePort{
|
||||||
Name: "custom-port-" + strconv.Itoa(index),
|
Name: "port-" + strconv.Itoa(index),
|
||||||
Protocol: apiv1.ProtocolTCP,
|
Protocol: apiv1.ProtocolTCP,
|
||||||
Port: port,
|
Port: binding.ServiceBindingPort,
|
||||||
TargetPort: intstr.FromInt(int(3000)),
|
TargetPort: intstr.FromInt(int(binding.ContainerExposePort)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,10 +44,10 @@ func (k *K8S) CreateService(
|
|||||||
namespace string,
|
namespace string,
|
||||||
name string,
|
name string,
|
||||||
typ string,
|
typ string,
|
||||||
ports []int32,
|
bindings []types.PortBinding,
|
||||||
selector map[string]string,
|
selector map[string]string,
|
||||||
) (*apiv1.Service, error) {
|
) (*apiv1.Service, error) {
|
||||||
service := generateServiceSpec(namespace, name, typ, ports, selector)
|
service := generateServiceSpec(namespace, name, typ, bindings, selector)
|
||||||
createdService, err := k.CoreV1().Services(namespace).Create(service)
|
createdService, err := k.CoreV1().Services(namespace).Create(service)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -76,7 +62,7 @@ func (k *K8S) UpdateService(
|
|||||||
namespace string,
|
namespace string,
|
||||||
name string,
|
name string,
|
||||||
typ string,
|
typ string,
|
||||||
ports []int32,
|
bindings []types.PortBinding,
|
||||||
selector map[string]string,
|
selector map[string]string,
|
||||||
) (*apiv1.Service, error) {
|
) (*apiv1.Service, error) {
|
||||||
svc, err := k.GetService(namespace, name)
|
svc, err := k.GetService(namespace, name)
|
||||||
|
|||||||
@@ -4,13 +4,24 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/metrue/fx/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestK8S(t *testing.T) {
|
func TestK8S(t *testing.T) {
|
||||||
namespace := "default"
|
namespace := "default"
|
||||||
// TODO image is ready on hub.docker.com
|
// TODO image is ready on hub.docker.com
|
||||||
image := "metrue/kube-hello"
|
image := "metrue/kube-hello"
|
||||||
ports := []int32{32300}
|
bindings := []types.PortBinding{
|
||||||
|
types.PortBinding{
|
||||||
|
ServiceBindingPort: 80,
|
||||||
|
ContainerExposePort: 3000,
|
||||||
|
},
|
||||||
|
types.PortBinding{
|
||||||
|
ServiceBindingPort: 443,
|
||||||
|
ContainerExposePort: 3000,
|
||||||
|
},
|
||||||
|
}
|
||||||
podName := "test-fx-pod"
|
podName := "test-fx-pod"
|
||||||
kubeconfig := os.Getenv("KUBECONFIG")
|
kubeconfig := os.Getenv("KUBECONFIG")
|
||||||
if kubeconfig == "" {
|
if kubeconfig == "" {
|
||||||
@@ -54,7 +65,7 @@ func TestK8S(t *testing.T) {
|
|||||||
t.Fatalf("should get no service name %s", serviceName)
|
t.Fatalf("should get no service name %s", serviceName)
|
||||||
}
|
}
|
||||||
|
|
||||||
svc, err := k8s.CreateService(namespace, serviceName, "NodePort", ports, labels)
|
svc, err := k8s.CreateService(namespace, serviceName, "NodePort", bindings, labels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -70,7 +81,7 @@ func TestK8S(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
selector := map[string]string{"hello": "world"}
|
selector := map[string]string{"hello": "world"}
|
||||||
svc, err = k8s.UpdateService(namespace, serviceName, "NodePort", ports, selector)
|
svc, err = k8s.UpdateService(namespace, serviceName, "NodePort", bindings, selector)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
38
fx.go
38
fx.go
@@ -1,8 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
"github.com/apex/log"
|
"github.com/apex/log"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@@ -11,9 +15,12 @@ import (
|
|||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const version = "0.7.51"
|
||||||
|
|
||||||
var cfg *config.Config
|
var cfg *config.Config
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
go checkForUpdate()
|
||||||
configDir := path.Join(os.Getenv("HOME"), ".fx")
|
configDir := path.Join(os.Getenv("HOME"), ".fx")
|
||||||
cfg := config.New(configDir)
|
cfg := config.New(configDir)
|
||||||
|
|
||||||
@@ -23,11 +30,40 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkForUpdate() {
|
||||||
|
const releaseURL = "https://api.github.com/repos/metrue/fx/releases/latest"
|
||||||
|
resp, err := http.Get(releaseURL)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("Failed to fetch Github release page, error %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
decoder := json.NewDecoder(resp.Body)
|
||||||
|
var releaseJSON struct {
|
||||||
|
Tag string `json:"tag_name"`
|
||||||
|
URL string `json:"html_url"`
|
||||||
|
}
|
||||||
|
if err := decoder.Decode(&releaseJSON); err != nil {
|
||||||
|
log.Debugf("Failed to decode Github release page JSON, error %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if matched, err := regexp.MatchString(`^(\d+\.)(\d+\.)(\d+)$`, releaseJSON.Tag); err != nil || !matched {
|
||||||
|
log.Debugf("Unofficial release %s?", releaseJSON.Tag)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Debugf("Latest release tag is %s", releaseJSON.Tag)
|
||||||
|
if releaseJSON.Tag != version {
|
||||||
|
fmt.Fprintf(os.Stderr, "\nfx %s is available (you're using %s), get the latest release from: %s\n",
|
||||||
|
releaseJSON.Tag, version, releaseJSON.URL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
app := cli.NewApp()
|
app := cli.NewApp()
|
||||||
app.Name = "fx"
|
app.Name = "fx"
|
||||||
app.Usage = "makes function as a service"
|
app.Usage = "makes function as a service"
|
||||||
app.Version = "0.7.3"
|
app.Version = version
|
||||||
|
|
||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
{
|
{
|
||||||
|
|||||||
1
go.sum
1
go.sum
@@ -55,6 +55,7 @@ github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKoh
|
|||||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||||
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
|
github.com/docker/go-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
|
||||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||||
|
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg=
|
||||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||||
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
|
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ func Call(cfg config.Configer) HandleFunc {
|
|||||||
log.Info("Read Source: \u2713")
|
log.Info("Read Source: \u2713")
|
||||||
|
|
||||||
lang := utils.GetLangFromFileName(file)
|
lang := utils.GetLangFromFileName(file)
|
||||||
fn := types.ServiceFunctionSource{
|
fn := types.Func{
|
||||||
Language: lang,
|
Language: lang,
|
||||||
Source: string(src),
|
Source: string(src),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
"github.com/metrue/fx/deploy"
|
"github.com/metrue/fx/deploy"
|
||||||
dockerDeployer "github.com/metrue/fx/deploy/docker"
|
dockerDeployer "github.com/metrue/fx/deploy/docker"
|
||||||
k8sDeployer "github.com/metrue/fx/deploy/kubernetes"
|
k8sDeployer "github.com/metrue/fx/deploy/kubernetes"
|
||||||
"github.com/metrue/fx/packer"
|
"github.com/metrue/fx/types"
|
||||||
"github.com/metrue/fx/utils"
|
"github.com/metrue/fx/utils"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
@@ -69,33 +69,41 @@ func Deploy(cfg config.Configer) HandleFunc {
|
|||||||
}
|
}
|
||||||
lang := utils.GetLangFromFileName(funcFile)
|
lang := utils.GetLangFromFileName(funcFile)
|
||||||
|
|
||||||
workdir, err := ioutil.TempDir("/tmp", "fx-wd")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := packer.PackIntoDir(lang, string(body), workdir); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var deployer deploy.Deployer
|
var deployer deploy.Deployer
|
||||||
|
var bindings []types.PortBinding
|
||||||
if os.Getenv("KUBECONFIG") != "" {
|
if os.Getenv("KUBECONFIG") != "" {
|
||||||
deployer, err = k8sDeployer.Create()
|
deployer, err = k8sDeployer.Create()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
bindings = []types.PortBinding{
|
||||||
|
types.PortBinding{
|
||||||
|
ServiceBindingPort: 80,
|
||||||
|
ContainerExposePort: constants.FxContainerExposePort,
|
||||||
|
},
|
||||||
|
types.PortBinding{
|
||||||
|
ServiceBindingPort: 443,
|
||||||
|
ContainerExposePort: constants.FxContainerExposePort,
|
||||||
|
},
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
bctx := context.Background()
|
bctx := context.Background()
|
||||||
deployer, err = dockerDeployer.CreateClient(bctx)
|
deployer, err = dockerDeployer.CreateClient(bctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
bindings = []types.PortBinding{
|
||||||
|
types.PortBinding{
|
||||||
|
ServiceBindingPort: int32(port),
|
||||||
|
ContainerExposePort: constants.FxContainerExposePort,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// TODO multiple ports support
|
|
||||||
return deployer.Deploy(
|
return deployer.Deploy(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
workdir,
|
types.Func{Language: lang, Source: string(body)},
|
||||||
name,
|
name,
|
||||||
[]int32{int32(port)},
|
bindings,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
api "github.com/metrue/fx/container_runtimes/docker/http"
|
api "github.com/metrue/fx/container_runtimes/docker/http"
|
||||||
"github.com/metrue/fx/packer"
|
"github.com/metrue/fx/packer"
|
||||||
"github.com/metrue/fx/provision"
|
"github.com/metrue/fx/provision"
|
||||||
|
"github.com/metrue/fx/types"
|
||||||
"github.com/metrue/fx/utils"
|
"github.com/metrue/fx/utils"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
@@ -41,7 +42,7 @@ func BuildImage(cfg config.Configer) HandleFunc {
|
|||||||
tarFile := fmt.Sprintf("%s.%s.tar", pwd, tag)
|
tarFile := fmt.Sprintf("%s.%s.tar", pwd, tag)
|
||||||
defer os.RemoveAll(tarFile)
|
defer os.RemoveAll(tarFile)
|
||||||
|
|
||||||
if err := packer.PackIntoTar(lang, string(body), tarFile); err != nil {
|
if err := packer.PackIntoTar(types.Func{Language: lang, Source: string(body)}, tarFile); err != nil {
|
||||||
log.Fatalf("could not pack function: %v", err)
|
log.Fatalf("could not pack function: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -95,7 +96,7 @@ func ExportImage() HandleFunc {
|
|||||||
}
|
}
|
||||||
lang := utils.GetLangFromFileName(funcFile)
|
lang := utils.GetLangFromFileName(funcFile)
|
||||||
|
|
||||||
if err := packer.PackIntoDir(lang, string(body), outputDir); err != nil {
|
if err := packer.PackIntoDir(types.Func{Language: lang, Source: string(body)}, outputDir); err != nil {
|
||||||
log.Fatalf("write source code to file failed: %v", constants.UncheckedSymbol)
|
log.Fatalf("write source code to file failed: %v", constants.UncheckedSymbol)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ func Up(cfg config.Configer) HandleFunc {
|
|||||||
}
|
}
|
||||||
lang := utils.GetLangFromFileName(funcFile)
|
lang := utils.GetLangFromFileName(funcFile)
|
||||||
|
|
||||||
fn := types.ServiceFunctionSource{
|
fn := types.Func{
|
||||||
Language: lang,
|
Language: lang,
|
||||||
Source: string(body),
|
Source: string(body),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func NewDockerPacker(box packr.Box) *DockerPacker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pack pack a single function source code to be project
|
// Pack pack a single function source code to be project
|
||||||
func (p *DockerPacker) Pack(serviceName string, fn types.ServiceFunctionSource) (types.Project, error) {
|
func (p *DockerPacker) Pack(serviceName string, fn types.Func) (types.Project, error) {
|
||||||
var files []types.ProjectSourceFile
|
var files []types.ProjectSourceFile
|
||||||
for _, name := range p.box.List() {
|
for _, name := range p.box.List() {
|
||||||
prefix := fmt.Sprintf("%s/", fn.Language)
|
prefix := fmt.Sprintf("%s/", fn.Language)
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/gobuffalo/packr"
|
"github.com/gobuffalo/packr"
|
||||||
"github.com/metrue/fx/types"
|
"github.com/metrue/fx/types"
|
||||||
"github.com/metrue/fx/utils"
|
"github.com/metrue/fx/utils"
|
||||||
@@ -12,22 +15,18 @@ import (
|
|||||||
|
|
||||||
// Packer interface
|
// Packer interface
|
||||||
type Packer interface {
|
type Packer interface {
|
||||||
Pack(serviceName string, fn types.ServiceFunctionSource) (types.Project, error)
|
Pack(serviceName string, fn types.Func) (types.Project, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pack a function to be a docker project which is web service, handle the imcome request with given function
|
// Pack a function to be a docker project which is web service, handle the imcome request with given function
|
||||||
func Pack(svcName string, fn types.ServiceFunctionSource) (types.Project, error) {
|
func Pack(svcName string, fn types.Func) (types.Project, error) {
|
||||||
box := packr.NewBox("./images")
|
box := packr.NewBox("./images")
|
||||||
pkr := NewDockerPacker(box)
|
pkr := NewDockerPacker(box)
|
||||||
return pkr.Pack(svcName, fn)
|
return pkr.Pack(svcName, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PackIntoDir pack service code into directory
|
// PackIntoDir pack service code into directory
|
||||||
func PackIntoDir(lang string, source string, outputDir string) error {
|
func PackIntoDir(fn types.Func, outputDir string) error {
|
||||||
fn := types.ServiceFunctionSource{
|
|
||||||
Language: lang,
|
|
||||||
Source: source,
|
|
||||||
}
|
|
||||||
project, err := Pack("", fn)
|
project, err := Pack("", fn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -44,15 +43,47 @@ func PackIntoDir(lang string, source string, outputDir string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PackIntoK8SConfigMapFile pack function a K8S config map file
|
||||||
|
func PackIntoK8SConfigMapFile(fn types.Func) (string, error) {
|
||||||
|
project, err := Pack("", fn)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
tree := map[string]string{}
|
||||||
|
for _, file := range project.Files {
|
||||||
|
tree[file.Path] = file.Body
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(tree)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return base64.StdEncoding.WithPadding(base64.StdPadding).EncodeToString(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TreeToDir restore to docker project
|
||||||
|
func TreeToDir(tree map[string]string, outputDir string) error {
|
||||||
|
for k, v := range tree {
|
||||||
|
fn := filepath.Join(outputDir, k)
|
||||||
|
if err := utils.EnsureFile(fn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ioutil.WriteFile(fn, []byte(v), 0666); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// PackIntoTar pack service code into directory
|
// PackIntoTar pack service code into directory
|
||||||
func PackIntoTar(lang string, source string, path string) error {
|
func PackIntoTar(fn types.Func, path string) error {
|
||||||
tarDir, err := ioutil.TempDir("/tmp", "fx-tar")
|
tarDir, err := ioutil.TempDir("/tmp", "fx-tar")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer os.RemoveAll(tarDir)
|
defer os.RemoveAll(tarDir)
|
||||||
|
|
||||||
if err := PackIntoDir(lang, source, tarDir); err != nil {
|
if err := PackIntoDir(fn, tarDir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
package packer
|
package packer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
|
||||||
"github.com/metrue/fx/types"
|
"github.com/metrue/fx/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPack(t *testing.T) {
|
func TestPack(t *testing.T) {
|
||||||
ctrl := gomock.NewController(t)
|
|
||||||
defer ctrl.Finish()
|
|
||||||
|
|
||||||
mockSource := `
|
mockSource := `
|
||||||
module.exports = ({a, b}) => {
|
module.exports = ({a, b}) => {
|
||||||
return a + b
|
return a + b
|
||||||
@@ -58,3 +55,31 @@ module.exports = ({a, b}) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTreeAndUnTree(t *testing.T) {
|
||||||
|
mockSource := `
|
||||||
|
package fx;
|
||||||
|
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
public class Fx {
|
||||||
|
public int handle(JSONObject input) {
|
||||||
|
String a = input.get("a").toString();
|
||||||
|
String b = input.get("b").toString();
|
||||||
|
return Integer.parseInt(a) + Integer.parseInt(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
fn := types.ServiceFunctionSource{
|
||||||
|
Language: "java",
|
||||||
|
Source: mockSource,
|
||||||
|
}
|
||||||
|
tree, err := PackIntoK8SConfigMapFile(fn.Language, fn.Source)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
body := base64.StdEncoding.EncodeToString([]byte(mockSource))
|
||||||
|
if tree["src/main/java/fx/Fx.java"] != body {
|
||||||
|
t.Fatalf("should get %s but got %s", body, tree["src/main/java/fx/app.java"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
7
types/func.go
Normal file
7
types/func.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
// Func defines a function information
|
||||||
|
type Func struct {
|
||||||
|
Language string `json:"language"`
|
||||||
|
Source string `json:"source"`
|
||||||
|
}
|
||||||
9
types/port.go
Normal file
9
types/port.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
// PortBinding defines port binding
|
||||||
|
// ContainerExposePort the port target container exposes
|
||||||
|
// @ServiceBindingPort the port binding to the port container expose
|
||||||
|
type PortBinding struct {
|
||||||
|
ServiceBindingPort int32
|
||||||
|
ContainerExposePort int32
|
||||||
|
}
|
||||||
@@ -6,12 +6,6 @@ type ServiceRunOptions struct {
|
|||||||
Port int64
|
Port int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceFunctionSource source of service's function
|
|
||||||
type ServiceFunctionSource struct {
|
|
||||||
Language string `json:"language"`
|
|
||||||
Source string `json:"source"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultHost default host IP
|
// DefaultHost default host IP
|
||||||
const DefaultHost = "0.0.0.0"
|
const DefaultHost = "0.0.0.0"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user