Compare commits
12 Commits
0.7.2-alph
...
0.7.4-alph
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
91fd5dc59f | ||
|
|
184235acb2 | ||
|
|
aa49a59feb | ||
|
|
c9d382d903 | ||
|
|
81e18e5b0d | ||
|
|
3882f843bf | ||
|
|
293481f081 | ||
|
|
c12d967ced | ||
|
|
b2a62cbd94 | ||
|
|
536b757602 | ||
|
|
ddff53fff2 | ||
|
|
ae87215cfb |
42
.github/workflows/ci.yml
vendored
42
.github/workflows/ci.yml
vendored
@@ -1,4 +1,4 @@
|
|||||||
on: push
|
on: [push, pull_request]
|
||||||
name: ci
|
name: ci
|
||||||
jobs:
|
jobs:
|
||||||
Test:
|
Test:
|
||||||
@@ -7,7 +7,7 @@ jobs:
|
|||||||
- name: setup Go 1.12
|
- name: setup Go 1.12
|
||||||
uses: actions/setup-go@v1
|
uses: actions/setup-go@v1
|
||||||
with:
|
with:
|
||||||
version: 1.12
|
go-version: 1.12
|
||||||
id: go
|
id: go
|
||||||
|
|
||||||
- name: check out
|
- name: check out
|
||||||
@@ -61,10 +61,15 @@ 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
|
||||||
Release:
|
DEBUG=true ./build/fx deploy -n hello -p 12345 examples/functions/JavaScript/func.js
|
||||||
|
./build/fx destroy hello
|
||||||
|
rm ${KUBECONFIG}
|
||||||
|
fi
|
||||||
|
|
||||||
|
Installation:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
needs: [Test]
|
needs: [Test]
|
||||||
strategy:
|
strategy:
|
||||||
@@ -72,23 +77,20 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
os:
|
os:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
- macOS-latest
|
# TODO enable window and mac
|
||||||
- windows-latest
|
# - macOS-latest
|
||||||
|
# - windows-latest
|
||||||
version:
|
version:
|
||||||
- latest
|
- latest
|
||||||
- v0.117.0
|
- v0.117.0
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v1
|
uses: actions/checkout@v1
|
||||||
- name: Set up Go
|
- name: install fx
|
||||||
uses: actions/setup-go@v1
|
run: |
|
||||||
with:
|
# install with non-root user
|
||||||
version: '1.12'
|
bash ./scripts/install.sh
|
||||||
- name: GoReleaser
|
./fx -v
|
||||||
if: github.ref == 'production'
|
# install with root
|
||||||
env:
|
sudo bash ./scripts/install.sh
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
./fx -v
|
||||||
uses: goreleaser/goreleaser-action@master
|
|
||||||
with:
|
|
||||||
version: ${{ matrix.version }}
|
|
||||||
args: release --rm-dist
|
|
||||||
|
|||||||
117
.github/workflows/release.yml
vendored
Normal file
117
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '*--auto-release'
|
||||||
|
- master
|
||||||
|
- production
|
||||||
|
name: release
|
||||||
|
jobs:
|
||||||
|
Test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: setup Go 1.12
|
||||||
|
uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: 1.12
|
||||||
|
id: go
|
||||||
|
|
||||||
|
- name: check out
|
||||||
|
uses: actions/checkout@master
|
||||||
|
|
||||||
|
- name: setup docker
|
||||||
|
run: |
|
||||||
|
./scripts/provision.sh
|
||||||
|
|
||||||
|
- name: setup k8s and kind
|
||||||
|
run: |
|
||||||
|
export GOBIN=$(go env GOPATH)/bin
|
||||||
|
export PATH=$PATH:$GOBIN
|
||||||
|
mkdir -p $GOBIN
|
||||||
|
curl -LO https://storage.googleapis.com/kubernetes-release/release/`curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt`/bin/linux/amd64/kubectl
|
||||||
|
chmod +x kubectl && mv kubectl $GOBIN
|
||||||
|
wget https://github.com/kubernetes-sigs/kind/releases/download/v0.5.0/kind-linux-amd64 && chmod +x kind-linux-amd64 && mv kind-linux-amd64 $GOBIN/kind
|
||||||
|
./scripts/setup_kind.sh
|
||||||
|
|
||||||
|
- name: unit test
|
||||||
|
env:
|
||||||
|
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
run: |
|
||||||
|
export KUBECONFIG=/home/runner/.kube/kind-config-fx-test
|
||||||
|
DEBUG=true go test -v ./container_runtimes/... ./deploy/...
|
||||||
|
|
||||||
|
- name: build fx
|
||||||
|
run: |
|
||||||
|
make build
|
||||||
|
|
||||||
|
- name: lint
|
||||||
|
run: |
|
||||||
|
export GOBIN=$(go env GOPATH)/bin
|
||||||
|
export PATH=$PATH:$GOBIN
|
||||||
|
go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||||
|
golangci-lint run
|
||||||
|
|
||||||
|
|
||||||
|
- name: test fx cli
|
||||||
|
run: |
|
||||||
|
echo $KUBECONFIG
|
||||||
|
unset KUBECONFIG
|
||||||
|
make cli-test
|
||||||
|
|
||||||
|
- name: test AKS
|
||||||
|
env:
|
||||||
|
AKS_KUBECONFIG: ${{ secrets.AKS_KUBECONFIG }}
|
||||||
|
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
run: |
|
||||||
|
export KUBECONFIG=${HOME}/.kube/aks
|
||||||
|
echo ${AKS_KUBECONFIG} | base64 -d > $KUBECONFIG
|
||||||
|
DEBUG=true ./build/fx deploy -n hello -p 12345 examples/functions/JavaScript/func.js
|
||||||
|
./build/fx destroy hello
|
||||||
|
rm ${KUBECONFIG}
|
||||||
|
Release:
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
needs: [Test]
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os:
|
||||||
|
- ubuntu-latest
|
||||||
|
# - macOS-latest
|
||||||
|
# - windows-latest
|
||||||
|
version:
|
||||||
|
- latest
|
||||||
|
# - v0.117.0
|
||||||
|
steps:
|
||||||
|
- name: setup Go
|
||||||
|
uses: actions/setup-go@v1
|
||||||
|
with:
|
||||||
|
go-version: '1.12'
|
||||||
|
- name: checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
- name: release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GORELEASER_GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
git config --global user.email "h.minghe@gmail.com"
|
||||||
|
git config --global user.name "Minghe Huang"
|
||||||
|
|
||||||
|
commit=$(git rev-parse --short HEAD)
|
||||||
|
version=$(cat fx.go| grep Version | awk -F'"' '{print $2}')
|
||||||
|
|
||||||
|
echo "workflow is running on branch ${GITHUB_REF}"
|
||||||
|
|
||||||
|
if [[ ${GITHUB_REF} == "refs/heads/master" ]];then
|
||||||
|
version=${version}-alpha.${commit}
|
||||||
|
echo "alpha release $version"
|
||||||
|
elif [[ "${GITHUB_REF}" == *--auto-release ]];then
|
||||||
|
version=${version}-alpha.${commit}
|
||||||
|
echo "alpha release $version"
|
||||||
|
elif [[ ${GITHUB_REF} == "refs/heads/production" ]];then
|
||||||
|
echo "official release $version"
|
||||||
|
else
|
||||||
|
echo "skip release on $GITHUB_REF"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
git tag -a ${version} -m "auto release"
|
||||||
|
curl -sL https://git.io/goreleaser | bash -s -- --skip-validate
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ fx
|
|||||||
------
|
------
|
||||||
Poor man's function as a service.
|
Poor man's function as a service.
|
||||||
<br/>
|
<br/>
|
||||||
|

|
||||||

|

|
||||||
[](https://codecov.io/gh/metrue/fx)
|
[](https://codecov.io/gh/metrue/fx)
|
||||||
[](https://goreportcard.com/report/github.com/metrue/fx)
|
[](https://goreportcard.com/report/github.com/metrue/fx)
|
||||||
@@ -50,13 +51,11 @@ brew install metrue/fx/fx
|
|||||||
via cURL
|
via cURL
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
# Install to local directory
|
||||||
curl -o- https://raw.githubusercontent.com/metrue/fx/master/scripts/install.sh | bash
|
curl -o- https://raw.githubusercontent.com/metrue/fx/master/scripts/install.sh | bash
|
||||||
```
|
|
||||||
|
|
||||||
or Wget
|
# Install to /usr/local/bin/
|
||||||
|
curl -o- https://raw.githubusercontent.com/metrue/fx/master/scripts/install.sh | sudo bash
|
||||||
```shell
|
|
||||||
wget -qO- https://raw.githubusercontent.com/metrue/fx/master/scripts/install.sh | 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 `$PAHT`.
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
67
deploy/kubernetes/deployment.go
Normal file
67
deploy/kubernetes/deployment.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package kubernetes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/metrue/fx/constants"
|
||||||
|
appsv1 "k8s.io/api/apps/v1"
|
||||||
|
apiv1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
func generateDeploymentSpec(
|
||||||
|
name string,
|
||||||
|
image string,
|
||||||
|
replicas int32,
|
||||||
|
selector map[string]string,
|
||||||
|
) *appsv1.Deployment {
|
||||||
|
container := apiv1.Container{
|
||||||
|
Name: "fx-placeholder-container-name",
|
||||||
|
Image: image,
|
||||||
|
Ports: []apiv1.ContainerPort{
|
||||||
|
apiv1.ContainerPort{
|
||||||
|
Name: "fx-container",
|
||||||
|
ContainerPort: constants.FxContainerExposePort,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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 *K8S) GetDeployment(namespace string, name string) (*appsv1.Deployment, error) {
|
||||||
|
return k.AppsV1().Deployments(namespace).Get(name, metav1.GetOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDeployment create a deployment
|
||||||
|
func (k *K8S) CreateDeployment(namespace string, name string, image string, replicas int32, selector map[string]string) (*appsv1.Deployment, error) {
|
||||||
|
deployment := generateDeploymentSpec(name, image, replicas, selector)
|
||||||
|
return k.AppsV1().Deployments(namespace).Create(deployment)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateDeployment update a deployment
|
||||||
|
func (k *K8S) UpdateDeployment(namespace string, name string, image string, replicas int32, selector map[string]string) (*appsv1.Deployment, error) {
|
||||||
|
deployment := generateDeploymentSpec(name, image, replicas, selector)
|
||||||
|
return k.AppsV1().Deployments(namespace).Update(deployment)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteDeployment delete a deployment
|
||||||
|
func (k *K8S) DeleteDeployment(namespace string, name string) error {
|
||||||
|
return k.AppsV1().Deployments(namespace).Delete(name, &metav1.DeleteOptions{})
|
||||||
|
}
|
||||||
49
deploy/kubernetes/dpeloyment_test.go
Normal file
49
deploy/kubernetes/dpeloyment_test.go
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
package kubernetes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
deployment, err := k8s.CreateDeployment(namespace, name, image, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -60,16 +52,26 @@ func (k *K8S) Deploy(
|
|||||||
// By using a label selector between Pod and Service, we can link Service and Pod directly, it means a Endpoint will
|
// 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.
|
// 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.
|
||||||
labels := map[string]string{
|
selector := map[string]string{
|
||||||
"fx-app": "fx-app-" + uuid.New().String(),
|
"app": "fx-app-" + name,
|
||||||
}
|
}
|
||||||
if _, err := k.CreatePod(
|
|
||||||
namespace,
|
const replicas = int32(3)
|
||||||
name,
|
if _, err := k.GetDeployment(namespace, name); err != nil {
|
||||||
image,
|
// TODO enable passing replica from fx CLI
|
||||||
labels,
|
if _, err := k.CreateDeployment(
|
||||||
); err != nil {
|
namespace,
|
||||||
return err
|
name,
|
||||||
|
image,
|
||||||
|
replicas,
|
||||||
|
selector,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, err := k.UpdateDeployment(namespace, name, image, replicas, selector); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO fx should be able to know what's the target Kubernetes service platform
|
// TODO fx should be able to know what's the target Kubernetes service platform
|
||||||
@@ -79,14 +81,27 @@ func (k *K8S) Deploy(
|
|||||||
if !isOnPublicCloud {
|
if !isOnPublicCloud {
|
||||||
typ = "NodePort"
|
typ = "NodePort"
|
||||||
}
|
}
|
||||||
if _, err := k.CreateService(
|
|
||||||
namespace,
|
if _, err := k.GetService(namespace, name); err != nil {
|
||||||
name,
|
if _, err := k.CreateService(
|
||||||
typ,
|
namespace,
|
||||||
ports,
|
name,
|
||||||
labels,
|
typ,
|
||||||
); err != nil {
|
ports,
|
||||||
return err
|
selector,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, err := k.UpdateService(
|
||||||
|
namespace,
|
||||||
|
name,
|
||||||
|
typ,
|
||||||
|
ports,
|
||||||
|
selector,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -98,11 +113,10 @@ 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
|
||||||
}
|
}
|
||||||
if err := k.DeletePod(namespace, name); err != nil {
|
if err := k.DeleteDeployment(namespace, name); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -1,56 +1,67 @@
|
|||||||
package kubernetes
|
package kubernetes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/metrue/fx/constants"
|
"github.com/metrue/fx/constants"
|
||||||
v1 "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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func generateServiceSpec(
|
||||||
|
namespace string,
|
||||||
|
name string,
|
||||||
|
typ string,
|
||||||
|
ports []int32,
|
||||||
|
selector map[string]string,
|
||||||
|
) *apiv1.Service {
|
||||||
|
servicePorts := []apiv1.ServicePort{
|
||||||
|
apiv1.ServicePort{
|
||||||
|
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{
|
||||||
|
Name: "custom-port-" + strconv.Itoa(index),
|
||||||
|
Protocol: apiv1.ProtocolTCP,
|
||||||
|
Port: port,
|
||||||
|
TargetPort: intstr.FromInt(int(3000)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
// CreateService create a service
|
||||||
func (k *K8S) CreateService(
|
func (k *K8S) CreateService(
|
||||||
namespace string,
|
namespace string,
|
||||||
name string,
|
name string,
|
||||||
typ string,
|
typ string,
|
||||||
ports []int32,
|
ports []int32,
|
||||||
podsLabels map[string]string,
|
selector map[string]string,
|
||||||
) (*v1.Service, error) {
|
) (*apiv1.Service, error) {
|
||||||
servicePorts := []v1.ServicePort{
|
service := generateServiceSpec(namespace, name, typ, ports, selector)
|
||||||
v1.ServicePort{
|
|
||||||
Name: "http",
|
|
||||||
Protocol: v1.ProtocolTCP,
|
|
||||||
Port: 80,
|
|
||||||
TargetPort: intstr.FromInt(int(constants.FxContainerExposePort)),
|
|
||||||
},
|
|
||||||
v1.ServicePort{
|
|
||||||
Name: "https",
|
|
||||||
Protocol: v1.ProtocolTCP,
|
|
||||||
Port: 443,
|
|
||||||
TargetPort: intstr.FromInt(int(constants.FxContainerExposePort)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
// Append custom Port
|
|
||||||
for _, port := range ports {
|
|
||||||
servicePorts = append(servicePorts, v1.ServicePort{
|
|
||||||
Name: "custom",
|
|
||||||
Protocol: v1.ProtocolTCP,
|
|
||||||
Port: port,
|
|
||||||
TargetPort: intstr.FromInt(int(3000)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
service := &v1.Service{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: name,
|
|
||||||
ClusterName: namespace,
|
|
||||||
},
|
|
||||||
Spec: v1.ServiceSpec{
|
|
||||||
Ports: servicePorts,
|
|
||||||
Type: v1.ServiceType(typ),
|
|
||||||
Selector: podsLabels,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
@@ -59,9 +70,32 @@ func (k *K8S) CreateService(
|
|||||||
return createdService, nil
|
return createdService, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateService update a service
|
||||||
|
// TODO this method is not perfect yet, should refactor later
|
||||||
|
func (k *K8S) UpdateService(
|
||||||
|
namespace string,
|
||||||
|
name string,
|
||||||
|
typ string,
|
||||||
|
ports []int32,
|
||||||
|
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
|
// DeleteService a service
|
||||||
func (k *K8S) DeleteService(namespace string, name string) error {
|
func (k *K8S) DeleteService(namespace string, name string) error {
|
||||||
// TODO figure out the elegant way to delete a service
|
// TODO figure out the elegant way to delete a service
|
||||||
options := &metav1.DeleteOptions{}
|
options := &metav1.DeleteOptions{}
|
||||||
return k.CoreV1().Services(namespace).Delete(name, options)
|
return k.CoreV1().Services(namespace).Delete(name, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetService get a service
|
||||||
|
func (k *K8S) GetService(namespace string, name string) (*apiv1.Service, error) {
|
||||||
|
return k.CoreV1().Services(namespace).Get(name, metav1.GetOptions{})
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package kubernetes
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -49,6 +50,10 @@ func TestK8S(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
serviceName := podName + "-svc"
|
serviceName := podName + "-svc"
|
||||||
|
if _, err := k8s.GetService(namespace, serviceName); err == nil {
|
||||||
|
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", ports, labels)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -56,6 +61,26 @@ func TestK8S(t *testing.T) {
|
|||||||
if svc.Name != serviceName {
|
if svc.Name != serviceName {
|
||||||
t.Fatalf("should get %s but got %s", serviceName, svc.Name)
|
t.Fatalf("should get %s but got %s", serviceName, svc.Name)
|
||||||
}
|
}
|
||||||
|
svc, err = k8s.GetService(namespace, serviceName)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if svc.Name != serviceName {
|
||||||
|
t.Fatalf("should get %s but got %v", serviceName, svc.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
selector := map[string]string{"hello": "world"}
|
||||||
|
svc, err = k8s.UpdateService(namespace, serviceName, "NodePort", ports, selector)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if svc.Name != serviceName {
|
||||||
|
t.Fatalf("should get %s but got %v", serviceName, svc.Name)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(svc.Spec.Selector, selector) {
|
||||||
|
t.Fatalf("should get %v but got %v", selector, svc.Spec.Selector)
|
||||||
|
}
|
||||||
|
|
||||||
// TODO check service status
|
// TODO check service status
|
||||||
if err := k8s.DeleteService(namespace, serviceName); err != nil {
|
if err := k8s.DeleteService(namespace, serviceName); 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.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.1"
|
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()
|
||||||
|
|||||||
@@ -1,41 +1,64 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
fx_has() {
|
set -e
|
||||||
|
|
||||||
|
has() {
|
||||||
type "$1" > /dev/null 2>&1
|
type "$1" > /dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
get_package_url() {
|
get_package_url() {
|
||||||
label=""
|
platform=""
|
||||||
if [ "$(uname)" == "Darwin" ]; then
|
if [ "$(uname)" == "Darwin" ]; then
|
||||||
label="macOS"
|
platform="macOS"
|
||||||
elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then
|
elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then
|
||||||
label="Tux"
|
platform="Tux"
|
||||||
elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" ]; then
|
elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" ]; then
|
||||||
label="windows"
|
platform="windows"
|
||||||
elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]; then
|
elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]; then
|
||||||
label="windows"
|
platform="windows"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
curl -s https://api.github.com/repos/metrue/fx/releases/latest | grep browser_download_url | awk -F'"' '{print $4}' | grep ${label}
|
curl https://api.github.com/repos/metrue/fx/releases/latest | grep browser_download_url | awk -F'"' '{print $4}' | grep ${platform}
|
||||||
}
|
}
|
||||||
|
|
||||||
download_and_install() {
|
download_and_install() {
|
||||||
local url=$1
|
url=$(get_package_url)
|
||||||
# TODO we can do it on one line
|
tarFile="fx.tar.gz"
|
||||||
rm -rf fx.tar.gz
|
targetFile=$(pwd)
|
||||||
curl -o fx.tar.gz -L -O ${url} && tar -xvzf ./fx.tar.gz --exclude=*.md -C /usr/local/bin
|
|
||||||
rm -rf ./fx.tar.gz
|
userid=$(id -u)
|
||||||
|
if [ "$userid" != "0" ]; then
|
||||||
|
tarFile="$(pwd)/${tarFile}"
|
||||||
|
else
|
||||||
|
tarFile="/tmp/${tarFile}"
|
||||||
|
targetFile="/usr/local/bin"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -e $tarFile ]; then
|
||||||
|
rm -rf $tarFile
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Downloading fx from $url"
|
||||||
|
curl -sSLf $url --output $tarFile
|
||||||
|
if [ "$?" == "0" ]; then
|
||||||
|
echo "Download complete, saved to $tarFile"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Installing fx to ${targetFile}"
|
||||||
|
tar -xvzf ${tarFile} --exclude=*.md -C ${targetFile}
|
||||||
|
echo "fx installed successfully at ${targetFile}"
|
||||||
|
${targetFile}/fx -v
|
||||||
|
|
||||||
|
echo "Cleaning up ${tarFile}"
|
||||||
|
rm -rf ${tarFile}
|
||||||
}
|
}
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
if fx_has "docker"; then
|
if has "curl";then
|
||||||
url=$(get_package_url)
|
download_and_install
|
||||||
if [ ${url}"X" != "X" ];then
|
|
||||||
download_and_install ${url}
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
echo "No Docker found on this host"
|
echo "You need cURL to use this script"
|
||||||
echo " - Docker installation: https://docs.docker.com/engine/installation"
|
exit 1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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