Compare commits
8 Commits
0.7.2-alph
...
0.7.4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cb68766f7 | ||
|
|
91fd5dc59f | ||
|
|
184235acb2 | ||
|
|
aa49a59feb | ||
|
|
c9d382d903 | ||
|
|
81e18e5b0d | ||
|
|
3882f843bf | ||
|
|
293481f081 |
13
.github/workflows/ci.yml
vendored
13
.github/workflows/ci.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
on: push
|
on: [push, pull_request]
|
||||||
name: ci
|
name: ci
|
||||||
jobs:
|
jobs:
|
||||||
Test:
|
Test:
|
||||||
@@ -61,9 +61,14 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
export KUBECONFIG=${HOME}/.kube/aks
|
export KUBECONFIG=${HOME}/.kube/aks
|
||||||
echo ${AKS_KUBECONFIG} | base64 -d > $KUBECONFIG
|
echo ${AKS_KUBECONFIG} | base64 -d > $KUBECONFIG
|
||||||
DEBUG=true ./build/fx deploy -n hello -p 12345 examples/functions/JavaScript/func.js
|
if [[ -z "$DOCKER_USERNAME" || -z "$DOCKER_PASSWORD" || -z "$AKS_KUBECONFIG" ]];then
|
||||||
./build/fx destroy hello
|
echo "skip deploy test since no DOCKER_USERNAME and DOCKER_PASSWORD set"
|
||||||
rm ${KUBECONFIG}
|
else
|
||||||
|
DEBUG=true ./build/fx deploy -n hello -p 12345 examples/functions/JavaScript/func.js
|
||||||
|
./build/fx destroy hello
|
||||||
|
rm ${KUBECONFIG}
|
||||||
|
fi
|
||||||
|
|
||||||
Installation:
|
Installation:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
needs: [Test]
|
needs: [Test]
|
||||||
|
|||||||
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}"
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
run:
|
run:
|
||||||
concurrency: 4
|
|
||||||
deadline: 10m
|
deadline: 10m
|
||||||
timeout: 10m
|
timeout: 10m
|
||||||
issues-exit-code: 1
|
issues-exit-code: 1
|
||||||
@@ -7,21 +6,25 @@ run:
|
|||||||
skip-dirs:
|
skip-dirs:
|
||||||
- examples
|
- examples
|
||||||
- api/images
|
- api/images
|
||||||
- test
|
- test/functions
|
||||||
# skip-files:
|
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
enable:
|
enable:
|
||||||
- megacheck
|
- goimports
|
||||||
- govet
|
- stylecheck
|
||||||
- deadcode
|
- gosec
|
||||||
# - gocyclo
|
|
||||||
- golint
|
|
||||||
- varcheck
|
|
||||||
- structcheck
|
|
||||||
- errcheck
|
|
||||||
- dupl
|
|
||||||
- ineffassign
|
|
||||||
- interfacer
|
- interfacer
|
||||||
- unconvert
|
- unconvert
|
||||||
enable-all: false
|
- goconst
|
||||||
|
- gocyclo
|
||||||
|
- misspell
|
||||||
|
- unparam
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-rules:
|
||||||
|
- path: _test\.go
|
||||||
|
linters:
|
||||||
|
- gocyclo
|
||||||
|
- goconst
|
||||||
|
- errcheck
|
||||||
|
- dupl
|
||||||
|
- gosec
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ func (c *Config) Init() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := viper.ReadInConfig(); err != nil {
|
if err := viper.ReadInConfig(); err != nil {
|
||||||
return fmt.Errorf("Fatal error config file: %s", err)
|
return fmt.Errorf("fatal error config file: %s", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,10 +69,9 @@ func (d *Docker) BuildImage(ctx context.Context, workdir string, name string) er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if os.Getenv("DEBUG") != "" {
|
if os.Getenv("DEBUG") != "" {
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -88,10 +87,10 @@ func (d *Docker) PushImage(ctx context.Context, name string) (string, error) {
|
|||||||
username := os.Getenv("DOCKER_USERNAME")
|
username := os.Getenv("DOCKER_USERNAME")
|
||||||
password := os.Getenv("DOCKER_PASSWORD")
|
password := os.Getenv("DOCKER_PASSWORD")
|
||||||
if username == "" || password == "" {
|
if username == "" || password == "" {
|
||||||
return "", fmt.Errorf("DOCKER_USERNAME and DOCKER_PASSWORD required for push image to registy")
|
return "", fmt.Errorf("DOCKER_USERNAME and DOCKER_PASSWORD required for push image to registry")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO support private registy, like Azure Container registry
|
// TODO support private registry, like Azure Container registry
|
||||||
authConfig := dockerTypes.AuthConfig{
|
authConfig := dockerTypes.AuthConfig{
|
||||||
Username: username,
|
Username: username,
|
||||||
Password: password,
|
Password: password,
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func TestDocker(t *testing.T) {
|
|||||||
username := os.Getenv("DOCKER_USERNAME")
|
username := os.Getenv("DOCKER_USERNAME")
|
||||||
password := os.Getenv("DOCKER_PASSWORD")
|
password := os.Getenv("DOCKER_PASSWORD")
|
||||||
if username == "" || password == "" {
|
if username == "" || password == "" {
|
||||||
t.Skip("Skip push image test since DOCKER_USERNAME and DOCKER_PASSWORD not set in enviroment variable")
|
t.Skip("Skip push image test since DOCKER_USERNAME and DOCKER_PASSWORD not set in environment variable")
|
||||||
}
|
}
|
||||||
|
|
||||||
img, err := cli.PushImage(ctx, name)
|
img, err := cli.PushImage(ctx, name)
|
||||||
|
|||||||
@@ -2,10 +2,7 @@ package kubernetes
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
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"
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
@@ -17,14 +14,11 @@ type K8S struct {
|
|||||||
*kubernetes.Clientset
|
*kubernetes.Clientset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const namespace = "default"
|
||||||
|
|
||||||
// Create a k8s cluster client
|
// Create a k8s cluster client
|
||||||
func Create() (*K8S, error) {
|
func Create() (*K8S, error) {
|
||||||
kubeconfig := os.Getenv("KUBECONFIG")
|
config, err := clientcmd.BuildConfigFromKubeconfigGetter("", clientcmd.NewDefaultClientConfigLoadingRules().Load)
|
||||||
if kubeconfig == "" {
|
|
||||||
return nil, fmt.Errorf("KUBECONFIG not given")
|
|
||||||
}
|
|
||||||
|
|
||||||
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -43,8 +37,6 @@ func (k *K8S) Deploy(
|
|||||||
name string,
|
name string,
|
||||||
ports []int32,
|
ports []int32,
|
||||||
) error {
|
) error {
|
||||||
namespace := "default"
|
|
||||||
|
|
||||||
dockerClient, err := runtime.CreateClient(ctx)
|
dockerClient, err := runtime.CreateClient(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -61,7 +53,7 @@ func (k *K8S) Deploy(
|
|||||||
// be created automatically, then incoming traffic to Service will be forward to Pod.
|
// be created automatically, then incoming traffic to Service will be forward to Pod.
|
||||||
// Then we have no need to create Endpoint manually anymore.
|
// Then we have no need to create Endpoint manually anymore.
|
||||||
selector := map[string]string{
|
selector := map[string]string{
|
||||||
"app": "fx-app-" + uuid.New().String(),
|
"app": "fx-app-" + name,
|
||||||
}
|
}
|
||||||
|
|
||||||
const replicas = int32(3)
|
const replicas = int32(3)
|
||||||
@@ -121,7 +113,6 @@ func (k *K8S) Update(ctx context.Context, name string) error {
|
|||||||
|
|
||||||
// Destroy a service
|
// Destroy a service
|
||||||
func (k *K8S) Destroy(ctx context.Context, name string) error {
|
func (k *K8S) Destroy(ctx context.Context, name string) error {
|
||||||
const namespace = "default"
|
|
||||||
if err := k.DeleteService(namespace, name); err != nil {
|
if err := k.DeleteService(namespace, name); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,15 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestK8SRunner(t *testing.T) {
|
func TestK8SDeployer(t *testing.T) {
|
||||||
workdir := "./fixture"
|
workdir := "./fixture"
|
||||||
name := "hello"
|
name := "hello"
|
||||||
ports := []int32{32300}
|
ports := []int32{32300}
|
||||||
kubeconfig := os.Getenv("KUBECONFIG")
|
kubeconfig := os.Getenv("KUBECONFIG")
|
||||||
if kubeconfig == "" {
|
username := os.Getenv("DOCKER_USERNAME")
|
||||||
t.Skip("skip test since no KUBECONFIG given in environment variable")
|
password := os.Getenv("DOCKER_PASSWORD")
|
||||||
|
if kubeconfig == "" || username == "" || password == "" {
|
||||||
|
t.Skip("skip test since no KUBECONFIG, DOCKER_USERNAME and DOCKER_PASSWORD given in environment variable")
|
||||||
}
|
}
|
||||||
k8s, err := Create()
|
k8s, err := Create()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
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.4"
|
||||||
|
|
||||||
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.2"
|
app.Version = version
|
||||||
|
|
||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ func Deploy(cfg config.Configer) HandleFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("deploy function %s (%s) failed: %v", err)
|
log.Fatalf("deploy function %s (%s) failed: %v", name, funcFile, err)
|
||||||
}
|
}
|
||||||
log.Infof("function %s (%s) deployed successfully", name, funcFile)
|
log.Infof("function %s (%s) deployed successfully", name, funcFile)
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ type DockerPacker struct {
|
|||||||
box packr.Box
|
box packr.Box
|
||||||
}
|
}
|
||||||
|
|
||||||
func isHandler(lang string, name string) bool {
|
func isHandler(name string) bool {
|
||||||
basename := filepath.Base(name)
|
basename := filepath.Base(name)
|
||||||
nameWithoutExt := strings.TrimSuffix(basename, filepath.Ext(basename))
|
nameWithoutExt := strings.TrimSuffix(basename, filepath.Ext(basename))
|
||||||
return nameWithoutExt == "fx" ||
|
return nameWithoutExt == "fx" ||
|
||||||
@@ -39,7 +39,7 @@ func (p *DockerPacker) Pack(serviceName string, fn types.ServiceFunctionSource)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if preset's file is handler function of project, replace it with give one
|
// if preset's file is handler function of project, replace it with give one
|
||||||
if isHandler(fn.Language, name) {
|
if isHandler(name) {
|
||||||
files = append(files, types.ProjectSourceFile{
|
files = append(files, types.ProjectSourceFile{
|
||||||
Path: strings.Replace(name, prefix, "", 1),
|
Path: strings.Replace(name, prefix, "", 1),
|
||||||
Body: fn.Source,
|
Body: fn.Source,
|
||||||
|
|||||||
@@ -42,8 +42,10 @@ func (l *LocalRunner) Run(script string) ([]byte, error) {
|
|||||||
params := strings.Split(script, " ")
|
params := strings.Split(script, " ")
|
||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
if len(params) > 1 {
|
if len(params) > 1 {
|
||||||
|
// nolint: gosec
|
||||||
cmd = exec.Command(params[0], params[1:]...)
|
cmd = exec.Command(params[0], params[1:]...)
|
||||||
} else {
|
} else {
|
||||||
|
// nolint: gosec
|
||||||
cmd = exec.Command(params[0])
|
cmd = exec.Command(params[0])
|
||||||
}
|
}
|
||||||
return cmd.CombinedOutput()
|
return cmd.CombinedOutput()
|
||||||
|
|||||||
@@ -16,9 +16,13 @@ run() {
|
|||||||
deploy() {
|
deploy() {
|
||||||
local lang=$1
|
local lang=$1
|
||||||
local port=$2
|
local port=$2
|
||||||
$fx deploy --name ${service}_${lang} --port ${port} test/functions/func.${lang}
|
if [[ -z "$DOCKER_USERNAME" || -z "$DOCKER_PASSWORD" ]];then
|
||||||
docker ps
|
echo "skip deploy test since no DOCKER_USERNAME and DOCKER_PASSWORD set"
|
||||||
$fx destroy ${service}_${lang}
|
else
|
||||||
|
$fx deploy --name ${service}-${lang} --port ${port} test/functions/func.${lang}
|
||||||
|
docker ps
|
||||||
|
$fx destroy ${service}-${lang}
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
build_image() {
|
build_image() {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ func Download(filepath string, url string) (err error) {
|
|||||||
}
|
}
|
||||||
defer out.Close()
|
defer out.Close()
|
||||||
|
|
||||||
|
// nolint: gosec
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -48,6 +49,7 @@ func Unzip(source string, target string) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range reader.File {
|
for _, file := range reader.File {
|
||||||
|
//nolint: gosec
|
||||||
path := filepath.Join(target, file.Name)
|
path := filepath.Join(target, file.Name)
|
||||||
if file.FileInfo().IsDir() {
|
if file.FileInfo().IsDir() {
|
||||||
if err := os.MkdirAll(path, file.Mode()); err != nil {
|
if err := os.MkdirAll(path, file.Mode()); err != nil {
|
||||||
@@ -262,7 +264,7 @@ func PairsToParams(pairs []string) map[string]string {
|
|||||||
func OutputJSON(v interface{}) error {
|
func OutputJSON(v interface{}) error {
|
||||||
bytes, err := json.MarshalIndent(v, "", "\t")
|
bytes, err := json.MarshalIndent(v, "", "\t")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Could marshal %v : %v", v, err)
|
return fmt.Errorf("could marshal %v : %v", v, err)
|
||||||
}
|
}
|
||||||
fmt.Println(string(bytes))
|
fmt.Println(string(bytes))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user