Compare commits
	
		
			41 Commits
		
	
	
		
			v0.7.0
			...
			0.8.3-alph
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					41bc98ab64 | ||
| 
						 | 
					b007ac315a | ||
| 
						 | 
					940f6b8f72 | ||
| 
						 | 
					f9690b74a5 | ||
| 
						 | 
					f2c58d545a | ||
| 
						 | 
					4732426629 | ||
| 
						 | 
					d4af4f67b2 | ||
| 
						 | 
					6420e8b6c6 | ||
| 
						 | 
					15c59fa31f | ||
| 
						 | 
					294131b48f | ||
| 
						 | 
					48413abaa1 | ||
| 
						 | 
					d36b2b935b | ||
| 
						 | 
					f493749689 | ||
| 
						 | 
					9de10bc885 | ||
| 
						 | 
					2d5446686a | ||
| 
						 | 
					0d7d4f4a6a | ||
| 
						 | 
					23c4171ece | ||
| 
						 | 
					d8a1868fce | ||
| 
						 | 
					d91a9959a8 | ||
| 
						 | 
					87e7c7d6ae | ||
| 
						 | 
					89c94daebc | ||
| 
						 | 
					047fac2a0a | ||
| 
						 | 
					1cb68766f7 | ||
| 
						 | 
					91fd5dc59f | ||
| 
						 | 
					184235acb2 | ||
| 
						 | 
					aa49a59feb | ||
| 
						 | 
					c9d382d903 | ||
| 
						 | 
					81e18e5b0d | ||
| 
						 | 
					3882f843bf | ||
| 
						 | 
					293481f081 | ||
| 
						 | 
					c12d967ced | ||
| 
						 | 
					b2a62cbd94 | ||
| 
						 | 
					536b757602 | ||
| 
						 | 
					ddff53fff2 | ||
| 
						 | 
					ae87215cfb | ||
| 
						 | 
					90d7ec88f0 | ||
| 
						 | 
					697277e1bb | ||
| 
						 | 
					a17126c713 | ||
| 
						 | 
					2c35262b9e | ||
| 
						 | 
					017a34c13c | ||
| 
						 | 
					bf8885e91a | 
@@ -1,73 +0,0 @@
 | 
			
		||||
defaults: &defaults
 | 
			
		||||
  machine: true
 | 
			
		||||
  environment:
 | 
			
		||||
    IMPORT_PATH: "github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME"
 | 
			
		||||
    OUTPUT_DIR: "./build"
 | 
			
		||||
    DIST_DIR: "./dist"
 | 
			
		||||
 | 
			
		||||
install_golang: &install_golang
 | 
			
		||||
  run:
 | 
			
		||||
    name: install Golang 1.11
 | 
			
		||||
    command: |
 | 
			
		||||
      sudo add-apt-repository ppa:gophers/archive
 | 
			
		||||
      sudo apt-get update
 | 
			
		||||
      sudo apt-get install golang-1.11-go
 | 
			
		||||
      alias go="/usr/lib/go-1.11/bin/go"
 | 
			
		||||
      go version
 | 
			
		||||
 | 
			
		||||
install_deps: &install_deps
 | 
			
		||||
  run:
 | 
			
		||||
    name: Install deps
 | 
			
		||||
    command: |
 | 
			
		||||
      /usr/lib/go-1.11/bin/go mod vendor
 | 
			
		||||
      /usr/lib/go-1.11/bin/go get -u github.com/gobuffalo/packr/packr
 | 
			
		||||
 | 
			
		||||
install_httpie: &install_httpie
 | 
			
		||||
  run:
 | 
			
		||||
    name: install httpie
 | 
			
		||||
    command: |
 | 
			
		||||
      sudo apt-get -y update && sudo apt-get -y install httpie
 | 
			
		||||
 | 
			
		||||
install_jq: &install_jq
 | 
			
		||||
  run:
 | 
			
		||||
    name: install jq
 | 
			
		||||
    command: |
 | 
			
		||||
      sudo apt-get update && sudo apt-get -y install jq
 | 
			
		||||
 | 
			
		||||
build_binary: &build_binary
 | 
			
		||||
  run:
 | 
			
		||||
    name: build binary
 | 
			
		||||
    command: |
 | 
			
		||||
      /usr/lib/go-1.11/bin/go build -o ${OUTPUT_DIR}/fx fx.go
 | 
			
		||||
 | 
			
		||||
unit_test: &unit_test
 | 
			
		||||
  run:
 | 
			
		||||
    name: unit test
 | 
			
		||||
    command: |
 | 
			
		||||
      make unit-test
 | 
			
		||||
      bash <(curl -s https://codecov.io/bash) -t ${CODECOV_TOKEN}
 | 
			
		||||
cli_test: &cli_test
 | 
			
		||||
  run:
 | 
			
		||||
    name: cli test
 | 
			
		||||
    command: make cli-test
 | 
			
		||||
 | 
			
		||||
version: 2
 | 
			
		||||
jobs:
 | 
			
		||||
  test:
 | 
			
		||||
    <<: *defaults
 | 
			
		||||
    steps:
 | 
			
		||||
      - checkout
 | 
			
		||||
      - *install_golang
 | 
			
		||||
      - *install_deps
 | 
			
		||||
      - *unit_test
 | 
			
		||||
      - *build_binary
 | 
			
		||||
      - run:
 | 
			
		||||
          name: Pull images
 | 
			
		||||
          command: make pull
 | 
			
		||||
      - *cli_test
 | 
			
		||||
 | 
			
		||||
workflows:
 | 
			
		||||
  version: 2
 | 
			
		||||
  workflow:
 | 
			
		||||
    jobs:
 | 
			
		||||
      - test
 | 
			
		||||
							
								
								
									
										78
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										78
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,4 +1,4 @@
 | 
			
		||||
on: push
 | 
			
		||||
on: [push, pull_request]
 | 
			
		||||
name: ci
 | 
			
		||||
jobs:
 | 
			
		||||
  Test:
 | 
			
		||||
@@ -7,7 +7,7 @@ jobs:
 | 
			
		||||
      - name: setup Go 1.12
 | 
			
		||||
        uses: actions/setup-go@v1
 | 
			
		||||
        with:
 | 
			
		||||
          version: 1.12
 | 
			
		||||
          go-version: 1.12
 | 
			
		||||
        id: go
 | 
			
		||||
 | 
			
		||||
      - name: check out
 | 
			
		||||
@@ -17,6 +17,11 @@ jobs:
 | 
			
		||||
        run: |
 | 
			
		||||
          ./scripts/provision.sh
 | 
			
		||||
 | 
			
		||||
      - name: lint
 | 
			
		||||
        run: |
 | 
			
		||||
          docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint \
 | 
			
		||||
          golangci-lint run -v
 | 
			
		||||
 | 
			
		||||
      - name: setup k8s and kind
 | 
			
		||||
        run: |
 | 
			
		||||
          export GOBIN=$(go env GOPATH)/bin
 | 
			
		||||
@@ -33,21 +38,31 @@ jobs:
 | 
			
		||||
          DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
 | 
			
		||||
        run: |
 | 
			
		||||
          export KUBECONFIG=/home/runner/.kube/kind-config-fx-test
 | 
			
		||||
          DEBUG=true go test -v ./container_runtimes/... ./deploy/...
 | 
			
		||||
          DEBUG=true go test -v ./...
 | 
			
		||||
      - name: code cov
 | 
			
		||||
        env:
 | 
			
		||||
          CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
 | 
			
		||||
        run: |
 | 
			
		||||
          ./scripts/coverage.sh
 | 
			
		||||
          bash <(curl -s https://codecov.io/bash) -t ${CODECOV_TOKEN}
 | 
			
		||||
 | 
			
		||||
      - name: build fx
 | 
			
		||||
        run: |
 | 
			
		||||
          make build
 | 
			
		||||
 | 
			
		||||
      - name: lint
 | 
			
		||||
      - name: test fx-docker
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
          cd ./contrib/docker_packer
 | 
			
		||||
          make linux-build
 | 
			
		||||
          make docker-build
 | 
			
		||||
          make test
 | 
			
		||||
          # make docker-publish #TODO in release workflow
 | 
			
		||||
 | 
			
		||||
      - name: test fx cli
 | 
			
		||||
        env:
 | 
			
		||||
          REMOTE_HOST_ADDR: ${{secrets.DOCKER_REMOTE_HOST_ADDR}}
 | 
			
		||||
          REMOTE_HOST_USER: ${{secrets.DOCKER_REMOTE_HOST_USER}}
 | 
			
		||||
          REMOTE_HOST_PASSWORD: ${{secrets.DOCKER_REMOTE_HOST_PASSWORD}}
 | 
			
		||||
        run: |
 | 
			
		||||
          echo $KUBECONFIG
 | 
			
		||||
          unset KUBECONFIG
 | 
			
		||||
@@ -56,20 +71,39 @@ jobs:
 | 
			
		||||
      - 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}
 | 
			
		||||
          if [[ -z "$AKS_KUBECONFIG" ]];then
 | 
			
		||||
            echo "skip deploy test since no valid KUBECONFIG"
 | 
			
		||||
          else
 | 
			
		||||
            DEBUG=true ./build/fx up -n hello -p 12345 examples/functions/JavaScript/func.js
 | 
			
		||||
            ./build/fx down hello
 | 
			
		||||
            rm ${KUBECONFIG}
 | 
			
		||||
          fi
 | 
			
		||||
 | 
			
		||||
      # TODO enable when GITHUB fix the localhost network access issue
 | 
			
		||||
      # - name: Unit Test
 | 
			
		||||
      #   working-directory:
 | 
			
		||||
      #   run: |
 | 
			
		||||
      #     docker ps
 | 
			
		||||
      #     curl http://127.0.0.1:8866/version
 | 
			
		||||
      #     curl http://localhost:8866/version
 | 
			
		||||
      #     go test -v ./...
 | 
			
		||||
  Installation:
 | 
			
		||||
    runs-on: ${{ matrix.os }}
 | 
			
		||||
    needs: [Test]
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: true
 | 
			
		||||
      matrix:
 | 
			
		||||
        os:
 | 
			
		||||
          - ubuntu-latest
 | 
			
		||||
          # TODO enable window and mac
 | 
			
		||||
          # - macOS-latest
 | 
			
		||||
          # - windows-latest
 | 
			
		||||
        version:
 | 
			
		||||
          - latest
 | 
			
		||||
          - v0.117.0
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout
 | 
			
		||||
        uses: actions/checkout@v1
 | 
			
		||||
      - name: install fx
 | 
			
		||||
        run: |
 | 
			
		||||
          # install with non-root user
 | 
			
		||||
          bash ./scripts/install.sh
 | 
			
		||||
          ./fx -v
 | 
			
		||||
          # install with root
 | 
			
		||||
          sudo bash ./scripts/install.sh
 | 
			
		||||
          ./fx -v
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										93
									
								
								.github/workflows/docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										93
									
								
								.github/workflows/docker.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,57 +1,70 @@
 | 
			
		||||
on:
 | 
			
		||||
  schedule:
 | 
			
		||||
    - cron: '0 12 * * *'
 | 
			
		||||
on: [push]
 | 
			
		||||
  # schedule:
 | 
			
		||||
  #   - cron: '0 12 * * *'
 | 
			
		||||
name: docker
 | 
			
		||||
jobs:
 | 
			
		||||
  Docker:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    strategy:
 | 
			
		||||
      matrix:
 | 
			
		||||
        docker_version:
 | 
			
		||||
          - 18.09
 | 
			
		||||
          # - 19.03
 | 
			
		||||
          # - 19.09
 | 
			
		||||
        docker_channel:
 | 
			
		||||
          - stable
 | 
			
		||||
          # - test
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@master
 | 
			
		||||
      - name: setup docker
 | 
			
		||||
        uses: docker-practice/actions-setup-docker@master
 | 
			
		||||
        with:
 | 
			
		||||
          docker_version: ${{ matrix.docker_version }}
 | 
			
		||||
          docker_channel: ${{ matrix.docker_channel }}
 | 
			
		||||
 | 
			
		||||
      - name: login
 | 
			
		||||
        uses: actions/docker/login@8cdf801b322af5f369e00d85e9cf3a7122f49108
 | 
			
		||||
      - name: login docker hub
 | 
			
		||||
        env:
 | 
			
		||||
          DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
          DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
 | 
			
		||||
        run: |
 | 
			
		||||
          docker login --username $DOCKER_USERNAME --password $DOCKER_PASSWORD
 | 
			
		||||
 | 
			
		||||
      - name: build-fx-go-image
 | 
			
		||||
        uses: actions/docker/cli@master
 | 
			
		||||
        with:
 | 
			
		||||
          args: build -t metrue/fx-go-base:latest -f api/asserts/dockerfiles/base/go/Dockerfile
 | 
			
		||||
            api/asserts/dockerfiles/base/go
 | 
			
		||||
      - name: build and publish fx d image
 | 
			
		||||
        if: always()
 | 
			
		||||
        run: |
 | 
			
		||||
          docker build -t metrue/fx-d-base:latest -f ./assets/dockerfiles/base/d/Dockerfile ./assets/dockerfiles/base/d
 | 
			
		||||
          docker push metrue/fx-d-base:latest
 | 
			
		||||
 | 
			
		||||
      - name: push-fx-go-image
 | 
			
		||||
        uses: actions/docker/cli@master
 | 
			
		||||
      # - name: build and publish fx java image
 | 
			
		||||
      #   run: |
 | 
			
		||||
      #     docker build -t metrue/fx-go-base:latest -f ./assets/dockerfiles/base/java/Dockerfile ./assets/dockerfiles/base/java
 | 
			
		||||
      #     docker push metrue/fx-java-base:latest
 | 
			
		||||
 | 
			
		||||
      - name: build and publish fx node image
 | 
			
		||||
        if: always()
 | 
			
		||||
        run: |
 | 
			
		||||
          docker build -t metrue/fx-node-base:latest -f ./assets/dockerfiles/base/node/Dockerfile ./assets/dockerfiles/base/node
 | 
			
		||||
          docker push metrue/fx-node-base:latest
 | 
			
		||||
 | 
			
		||||
      - name: build and publish fx python image
 | 
			
		||||
        if: always()
 | 
			
		||||
        run: |
 | 
			
		||||
          docker build -t metrue/fx-python-base:latest -f ./assets/dockerfiles/base/python/Dockerfile ./assets/dockerfiles/base/python
 | 
			
		||||
      - name: publish fx python image
 | 
			
		||||
        env:
 | 
			
		||||
          DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
          DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
 | 
			
		||||
        with:
 | 
			
		||||
          args: push metrue/fx-go-base:latest
 | 
			
		||||
        run: |
 | 
			
		||||
          docker push metrue/fx-python-base:latest
 | 
			
		||||
 | 
			
		||||
      - name: build-fx-rust-image
 | 
			
		||||
        uses: actions/docker/cli@master
 | 
			
		||||
        with:
 | 
			
		||||
          args: build -t metrue/fx-rust-base:latest -f api/asserts/dockerfiles/base/rust/Dockerfile
 | 
			
		||||
            api/asserts/dockerfiles/base/rust
 | 
			
		||||
      # - name: build and publish fx rust image
 | 
			
		||||
      #   if: always()
 | 
			
		||||
      #   run: |
 | 
			
		||||
      #     docker build -t metrue/fx-rust-base:latest -f ./assets/dockerfiles/base/rust/Dockerfile ./assets/dockerfiles/base/python
 | 
			
		||||
      #     docker push metrue/fx-rust-base:latest
 | 
			
		||||
 | 
			
		||||
      - name: push-fx-rust-image
 | 
			
		||||
        uses: actions/docker/cli@master
 | 
			
		||||
        env:
 | 
			
		||||
          DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
          DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
 | 
			
		||||
        with:
 | 
			
		||||
          args: push metrue/fx-rust-base:latest
 | 
			
		||||
 | 
			
		||||
      - name: build-fx-node-image
 | 
			
		||||
        uses: actions/docker/cli@master
 | 
			
		||||
        with:
 | 
			
		||||
          args: build -t metrue/fx-node-base:latest -f api/asserts/dockerfiles/base/node/Dockerfile
 | 
			
		||||
            api/asserts/dockerfiles/base/node
 | 
			
		||||
 | 
			
		||||
      - name: push-fx-node-image
 | 
			
		||||
        uses: actions/docker/cli@master
 | 
			
		||||
        env:
 | 
			
		||||
          DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
 | 
			
		||||
          DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
 | 
			
		||||
        with:
 | 
			
		||||
          args: push metrue/fx-node-base:latest
 | 
			
		||||
      - name: build and publish fx julia image
 | 
			
		||||
        if: always()
 | 
			
		||||
        run: |
 | 
			
		||||
          docker build -t metrue/fx-julia-base:latest -f ./assets/dockerfiles/base/julia/Dockerfile ./assets/dockerfiles/base/julia
 | 
			
		||||
          docker push metrue/fx-julia-base:latest
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										114
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
			
		||||
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: lint
 | 
			
		||||
        run: |
 | 
			
		||||
          docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint \
 | 
			
		||||
          golangci-lint run -v
 | 
			
		||||
 | 
			
		||||
      - 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: 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 up -n hello -p 12345 examples/functions/JavaScript/func.js
 | 
			
		||||
          ./build/fx down 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 'const 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
 | 
			
		||||
							
								
								
									
										34
									
								
								.github/workflows/releaser.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										34
									
								
								.github/workflows/releaser.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,34 +0,0 @@
 | 
			
		||||
name: releaser
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  create:
 | 
			
		||||
    tags:
 | 
			
		||||
      - v*.*.*
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  ci:
 | 
			
		||||
    runs-on: ${{ matrix.os }}
 | 
			
		||||
    strategy:
 | 
			
		||||
      fail-fast: false
 | 
			
		||||
      matrix:
 | 
			
		||||
        os:
 | 
			
		||||
          - ubuntu-latest
 | 
			
		||||
          - macOS-latest
 | 
			
		||||
          - windows-latest
 | 
			
		||||
        version:
 | 
			
		||||
          - latest
 | 
			
		||||
          - v0.117.0
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout
 | 
			
		||||
        uses: actions/checkout@v1
 | 
			
		||||
      - name: Set up Go
 | 
			
		||||
        uses: actions/setup-go@v1
 | 
			
		||||
        with:
 | 
			
		||||
          version: '1.12'
 | 
			
		||||
      - name: GoReleaser
 | 
			
		||||
        env:
 | 
			
		||||
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
        uses: goreleaser/goreleaser-action@master
 | 
			
		||||
        with:
 | 
			
		||||
          version: ${{ matrix.version }}
 | 
			
		||||
          args: release --rm-dist
 | 
			
		||||
@@ -1,26 +1,30 @@
 | 
			
		||||
run:
 | 
			
		||||
  concurrency: 4
 | 
			
		||||
  deadline: 10m
 | 
			
		||||
  timeout: 10m
 | 
			
		||||
  issues-exit-code: 1
 | 
			
		||||
  tests: true
 | 
			
		||||
  skip-dirs:
 | 
			
		||||
    - examples
 | 
			
		||||
    - api/images
 | 
			
		||||
    - test
 | 
			
		||||
  # skip-files:
 | 
			
		||||
 | 
			
		||||
    - test/functions
 | 
			
		||||
linters:
 | 
			
		||||
  enable:
 | 
			
		||||
    - megacheck
 | 
			
		||||
    - govet
 | 
			
		||||
    - deadcode
 | 
			
		||||
    # - gocyclo
 | 
			
		||||
    - golint
 | 
			
		||||
    - varcheck
 | 
			
		||||
    - structcheck
 | 
			
		||||
    - errcheck
 | 
			
		||||
    - dupl
 | 
			
		||||
    - ineffassign
 | 
			
		||||
    - goimports
 | 
			
		||||
    - stylecheck
 | 
			
		||||
    - gosec
 | 
			
		||||
    - interfacer
 | 
			
		||||
    - unconvert
 | 
			
		||||
  enable-all: false
 | 
			
		||||
    - goconst
 | 
			
		||||
    - gocyclo
 | 
			
		||||
    - misspell
 | 
			
		||||
    - unparam
 | 
			
		||||
 | 
			
		||||
issues:
 | 
			
		||||
  exclude-rules:
 | 
			
		||||
    - path: _test\.go
 | 
			
		||||
      linters:
 | 
			
		||||
        - gocyclo
 | 
			
		||||
        - goconst
 | 
			
		||||
        - errcheck
 | 
			
		||||
        - dupl
 | 
			
		||||
        - gosec
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								Makefile
									
									
									
									
									
								
							@@ -7,6 +7,9 @@ lint:
 | 
			
		||||
generate:
 | 
			
		||||
	packr
 | 
			
		||||
 | 
			
		||||
b:
 | 
			
		||||
	go build -o ${OUTPUT_DIR}/fx fx.go
 | 
			
		||||
 | 
			
		||||
build:
 | 
			
		||||
	go build -o ${OUTPUT_DIR}/fx fx.go
 | 
			
		||||
 | 
			
		||||
@@ -24,7 +27,11 @@ unit-test:
 | 
			
		||||
	./scripts/coverage.sh
 | 
			
		||||
 | 
			
		||||
cli-test:
 | 
			
		||||
	echo 'run testing on localhost'
 | 
			
		||||
	./scripts/test_cli.sh
 | 
			
		||||
	# TODO enable remote test
 | 
			
		||||
	echo 'run testing on remote host'
 | 
			
		||||
	DOCKER_REMOTE_HOST_ADDR=${REMOTE_HOST_ADDR} DOCKER_REMOTE_HOST_USER=${REMOTE_HOST_USER} DOCKER_REMOTE_HOST_PASSWORD=${REMOTE_HOST_PASSWORD} ./scripts/test_cli.sh
 | 
			
		||||
 | 
			
		||||
http-test:
 | 
			
		||||
	./scripts/http_test.sh
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										152
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										152
									
								
								README.md
									
									
									
									
									
								
							@@ -2,8 +2,8 @@ fx
 | 
			
		||||
------
 | 
			
		||||
Poor man's function as a service.
 | 
			
		||||
<br/>
 | 
			
		||||

 | 
			
		||||
[](https://codecov.io/gh/metrue/fx)
 | 
			
		||||

 | 
			
		||||
[](https://codecov.io/gh/metrue/fx)
 | 
			
		||||
[](https://goreportcard.com/report/github.com/metrue/fx)
 | 
			
		||||
[](http://godoc.org/github.com/metrue/fx)
 | 
			
		||||

 | 
			
		||||
@@ -18,7 +18,9 @@ Poor man's function as a service.
 | 
			
		||||
 | 
			
		||||
## Introduction
 | 
			
		||||
 | 
			
		||||
fx is a tool to help you do Function as a Service on your own server. fx can make your stateless function a service in seconds. The most exciting thing is that you can write your functions with most programming languages.
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
fx is a tool to help you do Function as a Service on your own server, fx can make your stateless function a service in seconds, both Docker host and Kubernetes cluster supported. The most exciting thing is that you can write your functions with most programming languages.
 | 
			
		||||
 | 
			
		||||
Feel free hacking fx to support the languages not listed. Welcome to tweet me [@_metrue](https://twitter.com/_metrue) on Twitter, [@metrue](https://www.weibo.com/u/2165714507) on Weibo.
 | 
			
		||||
 | 
			
		||||
@@ -32,12 +34,14 @@ Feel free hacking fx to support the languages not listed. Welcome to tweet me [@
 | 
			
		||||
| Ruby          | Supported     | fx            | [/examples/Ruby](https://github.com/metrue/fx/tree/master/examples/functions/Ruby) |
 | 
			
		||||
| Java          | Supported     | fx            | [/examples/Java](https://github.com/metrue/fx/tree/master/examples/functions/Java) |
 | 
			
		||||
| PHP           | Supported     | [@chlins](https://github.com/chlins)| [/examples/PHP](https://github.com/metrue/fx/tree/master/examples/functions/PHP) |
 | 
			
		||||
| Julia         | Supported     | [@mbesancon](https://github.com/mbesancon)| [/examples/Julia](https://github.com/metrue/fx/tree/master/examples/functions/Julia) |
 | 
			
		||||
| Julia         | Supported     | [@matbesancon](https://github.com/matbesancon)| [/examples/Julia](https://github.com/metrue/fx/tree/master/examples/functions/Julia) |
 | 
			
		||||
| D             | Supported     | [@andre2007](https://github.com/andre2007)| [/examples/D](https://github.com/metrue/fx/tree/master/examples/functions/D) |
 | 
			
		||||
| R             | Working on [need your help](https://github.com/metrue/fx/issues/31)   | ||
 | 
			
		||||
 | 
			
		||||
# Installation
 | 
			
		||||
 | 
			
		||||
Binaries are available for Windows, MacOS and Linux/Unix on x86. For other architectures and platforms, follow instructions to [build fx from source](#buildtest).
 | 
			
		||||
 | 
			
		||||
* MacOS
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
@@ -50,18 +54,16 @@ brew install metrue/fx/fx
 | 
			
		||||
via cURL
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
# Install to local directory
 | 
			
		||||
curl -o- https://raw.githubusercontent.com/metrue/fx/master/scripts/install.sh | bash
 | 
			
		||||
 | 
			
		||||
# Install to /usr/local/bin/
 | 
			
		||||
curl -o- https://raw.githubusercontent.com/metrue/fx/master/scripts/install.sh | sudo bash
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
or Wget
 | 
			
		||||
fx will be installed into /usr/local/bin, sometimes you may need `source ~/.zshrc` or `source ~/.bashrc` to make fx available in `$PATH`.
 | 
			
		||||
 | 
			
		||||
```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`.
 | 
			
		||||
 | 
			
		||||
* Window
 | 
			
		||||
* Windows
 | 
			
		||||
 | 
			
		||||
You can go the release page to [download](https://github.com/metrue/fx/releases) fx manually;
 | 
			
		||||
 | 
			
		||||
@@ -77,16 +79,16 @@ USAGE:
 | 
			
		||||
   fx [global options] command [command options] [arguments...]
 | 
			
		||||
 | 
			
		||||
VERSION:
 | 
			
		||||
   0.6.0
 | 
			
		||||
   0.8.1
 | 
			
		||||
 | 
			
		||||
COMMANDS:
 | 
			
		||||
   infra     manage infrastructure of fx
 | 
			
		||||
   image     manage image of service
 | 
			
		||||
   doctor    health check for fx
 | 
			
		||||
   up        deploy a function or a group of functions
 | 
			
		||||
   init      start fx agent on host
 | 
			
		||||
   up        deploy a function
 | 
			
		||||
   down      destroy a service
 | 
			
		||||
   list, ls  list deployed services
 | 
			
		||||
   call      run a function instantly
 | 
			
		||||
   image     manage image of service
 | 
			
		||||
   doctor    health check for fx
 | 
			
		||||
   help, h   Shows a list of commands or help for one command
 | 
			
		||||
 | 
			
		||||
GLOBAL OPTIONS:
 | 
			
		||||
@@ -94,62 +96,7 @@ GLOBAL OPTIONS:
 | 
			
		||||
   --version, -v  print the version
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
1. List your current machines and activate you machine
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
$ fx infra ls     # list machines
 | 
			
		||||
 | 
			
		||||
{
 | 
			
		||||
	"localhost": {
 | 
			
		||||
		"Host": "localhost",
 | 
			
		||||
		"User": "",
 | 
			
		||||
		"Password": "",
 | 
			
		||||
		"Enabled": true,
 | 
			
		||||
		"Provisioned": false
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$ fx infra activate localhost    # activate 'localhost'
 | 
			
		||||
 | 
			
		||||
2019/08/10 13:21:20  info Provision:pull python Docker base iamge: ✓
 | 
			
		||||
2019/08/10 13:21:21  info Provision:pull d Docker base image: ✓
 | 
			
		||||
2019/08/10 13:21:23  info Provision:pull java Docker base image: ✓
 | 
			
		||||
2019/08/10 13:21:28  info Provision:pull julia Docker base image: ✓
 | 
			
		||||
2019/08/10 13:21:31  info Provision:pull node Docker base image: ✓
 | 
			
		||||
2019/08/10 13:22:09  info Provision:pull go Docker base image: ✓
 | 
			
		||||
2019/08/10 13:22:09  info provision machine localhost: ✓
 | 
			
		||||
2019/08/10 13:22:09  info enble machine localhost: ✓
 | 
			
		||||
```
 | 
			
		||||
It may take seconds since `fx` needs to download some basic resources
 | 
			
		||||
 | 
			
		||||
*Note* you can add a remote host as fx machine also,
 | 
			
		||||
```
 | 
			
		||||
$ fx infra add --name my_aws_vm --host 13.121.202.227 --user root --password yourpassword
 | 
			
		||||
 | 
			
		||||
$ fx infra list
 | 
			
		||||
{
 | 
			
		||||
	"my_aws_vm": {
 | 
			
		||||
		"Host": "13.121.202.227",
 | 
			
		||||
		"User": "root",
 | 
			
		||||
		"Password": "yourpassword",
 | 
			
		||||
		"Enabled": false,
 | 
			
		||||
		"Provisioned": false
 | 
			
		||||
	},
 | 
			
		||||
	"localhost": {
 | 
			
		||||
		"Host": "localhost",
 | 
			
		||||
		"User": "",
 | 
			
		||||
		"Password": "",
 | 
			
		||||
		"Enabled": true,
 | 
			
		||||
		"Provisioned": true
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$ fx infra activate my_aws_vm
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
then your function will be deployed onto remote host also.
 | 
			
		||||
 | 
			
		||||
2. Write a function
 | 
			
		||||
1. Write a function
 | 
			
		||||
 | 
			
		||||
You can check out [examples](https://github.com/metrue/fx/tree/master/examples/functions) for reference. Let's write a function as an example,  it calculates the sum of two numbers then returns:
 | 
			
		||||
 | 
			
		||||
@@ -160,7 +107,7 @@ module.exports = (ctx) => {
 | 
			
		||||
```
 | 
			
		||||
Then save it to a file `func.js`.
 | 
			
		||||
 | 
			
		||||
3. Deploy your function as a service
 | 
			
		||||
2. Deploy your function as a service
 | 
			
		||||
 | 
			
		||||
Give your service a port with `--port`, and name with `--name`, heath checking with `--healthcheck` if you want.
 | 
			
		||||
 | 
			
		||||
@@ -181,7 +128,7 @@ $ fx image export -o <path of dir> func.js
 | 
			
		||||
2019/09/25 19:31:19  info exported to <path of dir>: ✓
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
4. Test your service
 | 
			
		||||
3. Test your service
 | 
			
		||||
 | 
			
		||||
then you can test your service:
 | 
			
		||||
 | 
			
		||||
@@ -210,7 +157,12 @@ hello world
 | 
			
		||||
 | 
			
		||||
## Docker
 | 
			
		||||
 | 
			
		||||
  TODO
 | 
			
		||||
**fx** is originally designed to turn a function into a runnable Docker container in a easiest way, on a host with Docker running, you can just deploy your function with `fx up` command,
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
fx up --name hello-svc --port 7777 hello.js # onto localhost
 | 
			
		||||
DOCKER_REMOTE_HOST_ADDR=xx.xx.xx.xx DOCKER_REMOTE_HOST_USER=xxxx DOCKER_REMOTE_HOST_PASSWORD=xxxx fx up --name hello-svc --port 7777 hello.js # onto remote host
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Kubernetes
 | 
			
		||||
 | 
			
		||||
@@ -279,6 +231,7 @@ fx uses [Project](https://github.com/metrue/fx/projects/4) to manage the develop
 | 
			
		||||
Docker: make sure [Docker](https://docs.docker.com/engine/installation/) installed and running on your server.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<a name="buildtest"></a>
 | 
			
		||||
#### Build & Test
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
@@ -305,33 +258,54 @@ Thank you to all the people who already contributed to fx!
 | 
			
		||||
        <a href="https://github.com/metrue" target="_blank">
 | 
			
		||||
            <img alt="metrue" src="https://avatars2.githubusercontent.com/u/1001246?v=4&s=50" width="50">
 | 
			
		||||
        </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">
 | 
			
		||||
            <img alt="muka" src="https://avatars2.githubusercontent.com/u/1021269?v=4&s=50" width="50">
 | 
			
		||||
        </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 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/mbesancon" target="_blank">
 | 
			
		||||
            <img alt="mbesancon" src="https://avatars2.githubusercontent.com/u/7623090?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/DaidoujiChen" target="_blank">
 | 
			
		||||
            <img alt="DaidoujiChen" src="https://avatars0.githubusercontent.com/u/670441?v=4&s=50" width="50">
 | 
			
		||||
        <a href="https://github.com/matbesancon" target="_blank">
 | 
			
		||||
            <img alt="mbesancon" src="https://avatars2.githubusercontent.com/u/7623090?s=60&v=4" width="50">
 | 
			
		||||
        </a>
 | 
			
		||||
        <a href="https://github.com/chlins" target="_blank">
 | 
			
		||||
            <img alt="chlins" src="https://avatars2.githubusercontent.com/u/31262637?v=4&s=50" width="50">
 | 
			
		||||
        </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">
 | 
			
		||||
            <img alt="andre2007" src="https://avatars1.githubusercontent.com/u/1451047?s=50&v=4" width="50">
 | 
			
		||||
        </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">
 | 
			
		||||
            <img alt="andre2007" src="https://avatars1.githubusercontent.com/u/2541678?s=50&v=4" width="50">
 | 
			
		||||
        </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>
 | 
			
		||||
  </tbody>
 | 
			
		||||
</table>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										213
									
								
								config/config.go
									
									
									
									
									
								
							
							
						
						
									
										213
									
								
								config/config.go
									
									
									
									
									
								
							@@ -1,213 +0,0 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
 | 
			
		||||
	"github.com/spf13/viper"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Configer interface
 | 
			
		||||
type Configer interface {
 | 
			
		||||
	GetMachine(name string) (Host, error)
 | 
			
		||||
	AddMachine(name string, host Host) error
 | 
			
		||||
	RemoveHost(name string) error
 | 
			
		||||
	ListActiveMachines() (map[string]Host, error)
 | 
			
		||||
	ListMachines() (map[string]Host, error)
 | 
			
		||||
	EnableMachine(name string) error
 | 
			
		||||
	DisableMachine(name string) error
 | 
			
		||||
	UpdateProvisionedStatus(name string, ok bool) error
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Config config of fx
 | 
			
		||||
type Config struct {
 | 
			
		||||
	dir string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New create a config
 | 
			
		||||
func New(dir string) *Config {
 | 
			
		||||
	return &Config{dir: dir}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Init config
 | 
			
		||||
func (c *Config) Init() error {
 | 
			
		||||
	if err := os.MkdirAll(c.dir, os.ModePerm); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ext := "yaml"
 | 
			
		||||
	name := "config"
 | 
			
		||||
	viper.SetConfigType(ext)
 | 
			
		||||
	viper.SetConfigName(name)
 | 
			
		||||
	viper.AddConfigPath(c.dir)
 | 
			
		||||
 | 
			
		||||
	// detect if file exists
 | 
			
		||||
	configFilePath := path.Join(c.dir, name+"."+ext)
 | 
			
		||||
	if _, err := os.Stat(configFilePath); os.IsNotExist(err) {
 | 
			
		||||
		fd, err := os.Create(configFilePath)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		fd.Close()
 | 
			
		||||
 | 
			
		||||
		localhost := Host{
 | 
			
		||||
			Host:        "localhost",
 | 
			
		||||
			Password:    "",
 | 
			
		||||
			User:        "",
 | 
			
		||||
			Enabled:     true,
 | 
			
		||||
			Provisioned: false,
 | 
			
		||||
		}
 | 
			
		||||
		viper.Set("hosts", map[string]Host{"localhost": localhost})
 | 
			
		||||
		return viper.WriteConfig()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := viper.ReadInConfig(); err != nil {
 | 
			
		||||
		return fmt.Errorf("Fatal error config file: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetMachine get host by name
 | 
			
		||||
func (c *Config) GetMachine(name string) (Host, error) {
 | 
			
		||||
	var hosts map[string]Host
 | 
			
		||||
	if err := viper.UnmarshalKey("hosts", &hosts); err != nil {
 | 
			
		||||
		return Host{}, err
 | 
			
		||||
	}
 | 
			
		||||
	host, ok := hosts[name]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return Host{}, fmt.Errorf("no such host %v", name)
 | 
			
		||||
	}
 | 
			
		||||
	return host, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListActiveMachines list enabled machines
 | 
			
		||||
func (c *Config) ListActiveMachines() (map[string]Host, error) {
 | 
			
		||||
	hosts, err := c.ListMachines()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return map[string]Host{}, err
 | 
			
		||||
	}
 | 
			
		||||
	lst := map[string]Host{}
 | 
			
		||||
	for name, h := range hosts {
 | 
			
		||||
		if h.Enabled {
 | 
			
		||||
			lst[name] = h
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return lst, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AddMachine add host
 | 
			
		||||
func (c *Config) AddMachine(name string, host Host) error {
 | 
			
		||||
	if !viper.IsSet("hosts") {
 | 
			
		||||
		viper.Set("hosts", map[string]Host{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hosts, err := c.ListMachines()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	hosts[name] = host
 | 
			
		||||
	viper.Set("hosts", hosts)
 | 
			
		||||
	return viper.WriteConfig()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RemoveHost remote a host
 | 
			
		||||
func (c *Config) RemoveHost(name string) error {
 | 
			
		||||
	hosts, err := c.ListMachines()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(hosts) == 1 {
 | 
			
		||||
		return fmt.Errorf("only one host left now, at least one host required by fx")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, ok := hosts[name]; ok {
 | 
			
		||||
		delete(hosts, name)
 | 
			
		||||
 | 
			
		||||
		viper.Set("hosts", hosts)
 | 
			
		||||
		return viper.WriteConfig()
 | 
			
		||||
	}
 | 
			
		||||
	return fmt.Errorf("no such host %s", name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListMachines list hosts
 | 
			
		||||
func (c *Config) ListMachines() (map[string]Host, error) {
 | 
			
		||||
	var hosts map[string]Host
 | 
			
		||||
	if err := viper.UnmarshalKey("hosts", &hosts); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return hosts, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnableMachine enable a machine, after machine enabled, function will be deployed onto it when ever `fx up` invoked
 | 
			
		||||
func (c *Config) EnableMachine(name string) error {
 | 
			
		||||
	host, err := c.GetMachine(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	host.Enabled = true
 | 
			
		||||
 | 
			
		||||
	if !viper.IsSet("hosts") {
 | 
			
		||||
		viper.Set("hosts", map[string]Host{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hosts, err := c.ListMachines()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	hosts[name] = host
 | 
			
		||||
	viper.Set("hosts", hosts)
 | 
			
		||||
	return viper.WriteConfig()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DisableMachine disable a machine, after machine disabled, function will not be deployed onto it
 | 
			
		||||
func (c *Config) DisableMachine(name string) error {
 | 
			
		||||
	host, err := c.GetMachine(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	host.Enabled = false
 | 
			
		||||
 | 
			
		||||
	if !viper.IsSet("hosts") {
 | 
			
		||||
		viper.Set("hosts", map[string]Host{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hosts, err := c.ListMachines()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	hosts[name] = host
 | 
			
		||||
	viper.Set("hosts", hosts)
 | 
			
		||||
	return viper.WriteConfig()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateProvisionedStatus update provisioned status
 | 
			
		||||
func (c *Config) UpdateProvisionedStatus(name string, ok bool) error {
 | 
			
		||||
	host, err := c.GetMachine(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	host.Provisioned = ok
 | 
			
		||||
 | 
			
		||||
	if !viper.IsSet("hosts") {
 | 
			
		||||
		viper.Set("hosts", map[string]Host{})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hosts, err := c.ListMachines()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	hosts[name] = host
 | 
			
		||||
	viper.Set("hosts", hosts)
 | 
			
		||||
	return viper.WriteConfig()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsMachineProvisioned check if machine provisioned
 | 
			
		||||
func (c *Config) IsMachineProvisioned(name string) bool {
 | 
			
		||||
	host, err := c.GetMachine(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return host.Provisioned
 | 
			
		||||
}
 | 
			
		||||
@@ -1,98 +0,0 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestConfig(t *testing.T) {
 | 
			
		||||
	configPath := "/tmp/.fx"
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if err := os.RemoveAll(configPath); err != nil {
 | 
			
		||||
			t.Fatal(err)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	c := New(configPath)
 | 
			
		||||
	if err := c.Init(); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hosts, err := c.ListMachines()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(hosts) != 1 {
 | 
			
		||||
		t.Fatalf("should have localhost as default machine")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	host := hosts["localhost"]
 | 
			
		||||
	if !reflect.DeepEqual(host, Host{Host: "localhost", Enabled: true}) {
 | 
			
		||||
		t.Fatalf("should get %v but got %v", Host{Host: "localhost"}, host)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	name := "remote-a"
 | 
			
		||||
	h := Host{
 | 
			
		||||
		Host:     "192.168.1.1",
 | 
			
		||||
		User:     "user-a",
 | 
			
		||||
		Password: "password-a",
 | 
			
		||||
		Enabled:  false,
 | 
			
		||||
	}
 | 
			
		||||
	if err := c.AddMachine(name, h); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hosts, err = c.ListMachines()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(hosts) != 2 {
 | 
			
		||||
		t.Fatalf("should have %d machines now, but got %d", 2, len(hosts))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	lst, err := c.ListActiveMachines()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(lst) != 1 {
 | 
			
		||||
		t.Fatalf("should only have %d machine enabled, but got %d", 1, len(lst))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := c.EnableMachine(name); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	lst, err = c.ListActiveMachines()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(lst) != 2 {
 | 
			
		||||
		t.Fatalf("should only have %d machine enabled, but got %d", 2, len(lst))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	h.Enabled = true
 | 
			
		||||
	if !reflect.DeepEqual(lst[name], h) {
 | 
			
		||||
		t.Fatalf("should get %v but got %v", h, lst[name])
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if lst[name].Provisioned != false {
 | 
			
		||||
		t.Fatalf("should get %v but got %v", false, lst[name].Provisioned)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := c.UpdateProvisionedStatus(name, true); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	updatedHost, err := c.GetMachine(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if updatedHost.Provisioned != true {
 | 
			
		||||
		t.Fatalf("should get %v but got %v", true, updatedHost.Provisioned)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,40 +0,0 @@
 | 
			
		||||
package config
 | 
			
		||||
 | 
			
		||||
// Host host entity
 | 
			
		||||
type Host struct {
 | 
			
		||||
	Host        string
 | 
			
		||||
	User        string
 | 
			
		||||
	Password    string
 | 
			
		||||
	Enabled     bool
 | 
			
		||||
	Provisioned bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewHost new a host
 | 
			
		||||
func NewHost(addr, user, password string) Host {
 | 
			
		||||
	return Host{
 | 
			
		||||
		Host:        addr,
 | 
			
		||||
		User:        user,
 | 
			
		||||
		Password:    password,
 | 
			
		||||
		Enabled:     false,
 | 
			
		||||
		Provisioned: false,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Valid if host is valid
 | 
			
		||||
func (h Host) Valid() bool {
 | 
			
		||||
	// TODO stronger check
 | 
			
		||||
	return h.Host != ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsLocal if host is localhost
 | 
			
		||||
func (h Host) IsLocal() bool {
 | 
			
		||||
	if !h.Valid() {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return h.Host == "127.0.0.1" || h.Host == "localhost"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsRemote is host is remote
 | 
			
		||||
func (h Host) IsRemote() bool {
 | 
			
		||||
	return !h.IsLocal()
 | 
			
		||||
}
 | 
			
		||||
@@ -1,149 +0,0 @@
 | 
			
		||||
// Code generated by MockGen. DO NOT EDIT.
 | 
			
		||||
// Source: ./config.go
 | 
			
		||||
 | 
			
		||||
// Package mock_config is a generated GoMock package.
 | 
			
		||||
package mock_config
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	gomock "github.com/golang/mock/gomock"
 | 
			
		||||
	config "github.com/metrue/fx/config"
 | 
			
		||||
	reflect "reflect"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// MockConfiger is a mock of Configer interface
 | 
			
		||||
type MockConfiger struct {
 | 
			
		||||
	ctrl     *gomock.Controller
 | 
			
		||||
	recorder *MockConfigerMockRecorder
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// MockConfigerMockRecorder is the mock recorder for MockConfiger
 | 
			
		||||
type MockConfigerMockRecorder struct {
 | 
			
		||||
	mock *MockConfiger
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewMockConfiger creates a new mock instance
 | 
			
		||||
func NewMockConfiger(ctrl *gomock.Controller) *MockConfiger {
 | 
			
		||||
	mock := &MockConfiger{ctrl: ctrl}
 | 
			
		||||
	mock.recorder = &MockConfigerMockRecorder{mock}
 | 
			
		||||
	return mock
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EXPECT returns an object that allows the caller to indicate expected use
 | 
			
		||||
func (m *MockConfiger) EXPECT() *MockConfigerMockRecorder {
 | 
			
		||||
	return m.recorder
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetMachine mocks base method
 | 
			
		||||
func (m *MockConfiger) GetMachine(name string) (config.Host, error) {
 | 
			
		||||
	m.ctrl.T.Helper()
 | 
			
		||||
	ret := m.ctrl.Call(m, "GetMachine", name)
 | 
			
		||||
	ret0, _ := ret[0].(config.Host)
 | 
			
		||||
	ret1, _ := ret[1].(error)
 | 
			
		||||
	return ret0, ret1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetMachine indicates an expected call of GetMachine
 | 
			
		||||
func (mr *MockConfigerMockRecorder) GetMachine(name interface{}) *gomock.Call {
 | 
			
		||||
	mr.mock.ctrl.T.Helper()
 | 
			
		||||
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMachine", reflect.TypeOf((*MockConfiger)(nil).GetMachine), name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AddMachine mocks base method
 | 
			
		||||
func (m *MockConfiger) AddMachine(name string, host config.Host) error {
 | 
			
		||||
	m.ctrl.T.Helper()
 | 
			
		||||
	ret := m.ctrl.Call(m, "AddMachine", name, host)
 | 
			
		||||
	ret0, _ := ret[0].(error)
 | 
			
		||||
	return ret0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// AddMachine indicates an expected call of AddMachine
 | 
			
		||||
func (mr *MockConfigerMockRecorder) AddMachine(name, host interface{}) *gomock.Call {
 | 
			
		||||
	mr.mock.ctrl.T.Helper()
 | 
			
		||||
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddMachine", reflect.TypeOf((*MockConfiger)(nil).AddMachine), name, host)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RemoveHost mocks base method
 | 
			
		||||
func (m *MockConfiger) RemoveHost(name string) error {
 | 
			
		||||
	m.ctrl.T.Helper()
 | 
			
		||||
	ret := m.ctrl.Call(m, "RemoveHost", name)
 | 
			
		||||
	ret0, _ := ret[0].(error)
 | 
			
		||||
	return ret0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RemoveHost indicates an expected call of RemoveHost
 | 
			
		||||
func (mr *MockConfigerMockRecorder) RemoveHost(name interface{}) *gomock.Call {
 | 
			
		||||
	mr.mock.ctrl.T.Helper()
 | 
			
		||||
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveHost", reflect.TypeOf((*MockConfiger)(nil).RemoveHost), name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListActiveMachines mocks base method
 | 
			
		||||
func (m *MockConfiger) ListActiveMachines() (map[string]config.Host, error) {
 | 
			
		||||
	m.ctrl.T.Helper()
 | 
			
		||||
	ret := m.ctrl.Call(m, "ListActiveMachines")
 | 
			
		||||
	ret0, _ := ret[0].(map[string]config.Host)
 | 
			
		||||
	ret1, _ := ret[1].(error)
 | 
			
		||||
	return ret0, ret1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListActiveMachines indicates an expected call of ListActiveMachines
 | 
			
		||||
func (mr *MockConfigerMockRecorder) ListActiveMachines() *gomock.Call {
 | 
			
		||||
	mr.mock.ctrl.T.Helper()
 | 
			
		||||
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListActiveMachines", reflect.TypeOf((*MockConfiger)(nil).ListActiveMachines))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListMachines mocks base method
 | 
			
		||||
func (m *MockConfiger) ListMachines() (map[string]config.Host, error) {
 | 
			
		||||
	m.ctrl.T.Helper()
 | 
			
		||||
	ret := m.ctrl.Call(m, "ListMachines")
 | 
			
		||||
	ret0, _ := ret[0].(map[string]config.Host)
 | 
			
		||||
	ret1, _ := ret[1].(error)
 | 
			
		||||
	return ret0, ret1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListMachines indicates an expected call of ListMachines
 | 
			
		||||
func (mr *MockConfigerMockRecorder) ListMachines() *gomock.Call {
 | 
			
		||||
	mr.mock.ctrl.T.Helper()
 | 
			
		||||
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListMachines", reflect.TypeOf((*MockConfiger)(nil).ListMachines))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnableMachine mocks base method
 | 
			
		||||
func (m *MockConfiger) EnableMachine(name string) error {
 | 
			
		||||
	m.ctrl.T.Helper()
 | 
			
		||||
	ret := m.ctrl.Call(m, "EnableMachine", name)
 | 
			
		||||
	ret0, _ := ret[0].(error)
 | 
			
		||||
	return ret0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnableMachine indicates an expected call of EnableMachine
 | 
			
		||||
func (mr *MockConfigerMockRecorder) EnableMachine(name interface{}) *gomock.Call {
 | 
			
		||||
	mr.mock.ctrl.T.Helper()
 | 
			
		||||
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnableMachine", reflect.TypeOf((*MockConfiger)(nil).EnableMachine), name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DisableMachine mocks base method
 | 
			
		||||
func (m *MockConfiger) DisableMachine(name string) error {
 | 
			
		||||
	m.ctrl.T.Helper()
 | 
			
		||||
	ret := m.ctrl.Call(m, "DisableMachine", name)
 | 
			
		||||
	ret0, _ := ret[0].(error)
 | 
			
		||||
	return ret0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DisableMachine indicates an expected call of DisableMachine
 | 
			
		||||
func (mr *MockConfigerMockRecorder) DisableMachine(name interface{}) *gomock.Call {
 | 
			
		||||
	mr.mock.ctrl.T.Helper()
 | 
			
		||||
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DisableMachine", reflect.TypeOf((*MockConfiger)(nil).DisableMachine), name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateProvisionedStatus mocks base method
 | 
			
		||||
func (m *MockConfiger) UpdateProvisionedStatus(name string, ok bool) error {
 | 
			
		||||
	m.ctrl.T.Helper()
 | 
			
		||||
	ret := m.ctrl.Call(m, "UpdateProvisionedStatus", name, ok)
 | 
			
		||||
	ret0, _ := ret[0].(error)
 | 
			
		||||
	return ret0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateProvisionedStatus indicates an expected call of UpdateProvisionedStatus
 | 
			
		||||
func (mr *MockConfigerMockRecorder) UpdateProvisionedStatus(name, ok interface{}) *gomock.Call {
 | 
			
		||||
	mr.mock.ctrl.T.Helper()
 | 
			
		||||
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateProvisionedStatus", reflect.TypeOf((*MockConfiger)(nil).UpdateProvisionedStatus), name, ok)
 | 
			
		||||
}
 | 
			
		||||
@@ -1,19 +1,32 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/apex/log"
 | 
			
		||||
	dockerTypes "github.com/docker/docker/api/types"
 | 
			
		||||
	"github.com/docker/docker/api/types/container"
 | 
			
		||||
	dockerTypesContainer "github.com/docker/docker/api/types/container"
 | 
			
		||||
	"github.com/docker/docker/api/types/network"
 | 
			
		||||
	"github.com/docker/go-connections/nat"
 | 
			
		||||
	"github.com/google/go-querystring/query"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
	containerruntimes "github.com/metrue/fx/container_runtimes"
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
	"github.com/metrue/fx/utils"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// API interact with dockerd http api
 | 
			
		||||
@@ -116,8 +129,8 @@ func (api *API) post(path string, body []byte, expectStatus int, v interface{})
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// List list service
 | 
			
		||||
func (api *API) list(name string) ([]types.Service, error) {
 | 
			
		||||
// ListContainer list service
 | 
			
		||||
func (api *API) ListContainer(ctx context.Context, name string) ([]types.Service, error) {
 | 
			
		||||
	if name != "" {
 | 
			
		||||
		info, err := api.inspect(name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
@@ -141,7 +154,7 @@ func (api *API) list(name string) ([]types.Service, error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type filterItem struct {
 | 
			
		||||
		Status []string `json:"url,omitempty"`
 | 
			
		||||
		Status []string `json:"status,omitempty"`
 | 
			
		||||
		Label  []string `json:"label,omitempty"`
 | 
			
		||||
		Name   []string `json:"name,omitempty"`
 | 
			
		||||
	}
 | 
			
		||||
@@ -193,3 +206,219 @@ func (api *API) list(name string) ([]types.Service, error) {
 | 
			
		||||
 | 
			
		||||
	return services, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BuildImage build image
 | 
			
		||||
func (api *API) BuildImage(ctx context.Context, workdir string, name string) error {
 | 
			
		||||
	tarDir, err := ioutil.TempDir("/tmp", "fx-tar")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer os.RemoveAll(tarDir)
 | 
			
		||||
 | 
			
		||||
	imageID := uuid.New().String()
 | 
			
		||||
	tarFilePath := filepath.Join(tarDir, fmt.Sprintf("%s.tar", imageID))
 | 
			
		||||
 | 
			
		||||
	if err := utils.TarDir(workdir, tarFilePath); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dockerBuildContext, err := os.Open(tarFilePath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer dockerBuildContext.Close()
 | 
			
		||||
 | 
			
		||||
	type buildQuery struct {
 | 
			
		||||
		Labels     string `url:"labels,omitempty"`
 | 
			
		||||
		Tags       string `url:"t,omitempty"`
 | 
			
		||||
		Dockerfile string `url:"dockerfile,omitempty"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Apply default labels
 | 
			
		||||
	labelsJSON, _ := json.Marshal(map[string]string{
 | 
			
		||||
		"belong-to": "fx",
 | 
			
		||||
	})
 | 
			
		||||
	q := buildQuery{
 | 
			
		||||
		Labels:     string(labelsJSON),
 | 
			
		||||
		Dockerfile: "Dockerfile",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	qs, err := query.Values(q)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	qs.Add("t", name)
 | 
			
		||||
	qs.Add("t", imageID)
 | 
			
		||||
 | 
			
		||||
	path := "/build"
 | 
			
		||||
	url := fmt.Sprintf("%s%s?%s", api.endpoint, path, qs.Encode())
 | 
			
		||||
	req, err := http.NewRequest("POST", url, dockerBuildContext)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req.Header.Set("Content-Type", "application/x-tar")
 | 
			
		||||
	client := &http.Client{Timeout: 600 * time.Second}
 | 
			
		||||
	resp, err := client.Do(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
	scanner := bufio.NewScanner(resp.Body)
 | 
			
		||||
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		if os.Getenv("DEBUG") != "" {
 | 
			
		||||
			log.Infof(scanner.Text())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err := scanner.Err(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PushImage push a image
 | 
			
		||||
func (api *API) PushImage(ctx context.Context, name string) (string, error) {
 | 
			
		||||
	return "", nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InspectImage inspect image
 | 
			
		||||
func (api *API) InspectImage(ctx context.Context, name string, image interface{}) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TagImage tag image
 | 
			
		||||
func (api *API) TagImage(ctx context.Context, name string, tag string) error {
 | 
			
		||||
	query := url.Values{}
 | 
			
		||||
	query.Set("repo", name)
 | 
			
		||||
	query.Set("tag", tag)
 | 
			
		||||
	path := fmt.Sprintf("/images/%s/tag?%s", name, query.Encode())
 | 
			
		||||
 | 
			
		||||
	url := fmt.Sprintf("%s%s", api.endpoint, path)
 | 
			
		||||
	req, err := http.NewRequest("POST", url, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client := &http.Client{Timeout: 10 * time.Second}
 | 
			
		||||
	if _, err = client.Do(req); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StartContainer start container
 | 
			
		||||
func (api *API) StartContainer(ctx context.Context, name string, image string, bindings []types.PortBinding) error {
 | 
			
		||||
	networks, err := api.GetNetwork(fxNetworkName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrapf(err, "get network failed: %s", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(networks) == 0 {
 | 
			
		||||
		if err := api.CreateNetwork(fxNetworkName); err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "error create network: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	networks, _ = api.GetNetwork(fxNetworkName)
 | 
			
		||||
 | 
			
		||||
	endpoint := &network.EndpointSettings{
 | 
			
		||||
		NetworkID: networks[0].ID,
 | 
			
		||||
	}
 | 
			
		||||
	networkConfig := &network.NetworkingConfig{
 | 
			
		||||
		EndpointsConfig: map[string]*network.EndpointSettings{
 | 
			
		||||
			"fx-net": endpoint,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	portSet := nat.PortSet{}
 | 
			
		||||
	portMap := nat.PortMap{}
 | 
			
		||||
	for _, binding := range bindings {
 | 
			
		||||
		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{
 | 
			
		||||
		Image:        image,
 | 
			
		||||
		ExposedPorts: portSet,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hostConfig := &dockerTypesContainer.HostConfig{
 | 
			
		||||
		AutoRemove:   true,
 | 
			
		||||
		PortBindings: portMap,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req := ContainerCreateRequestPayload{
 | 
			
		||||
		Config:           config,
 | 
			
		||||
		HostConfig:       hostConfig,
 | 
			
		||||
		NetworkingConfig: networkConfig,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	body, err := json.Marshal(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "error mashal container create req")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// create container
 | 
			
		||||
	path := fmt.Sprintf("/containers/create?name=%s", name)
 | 
			
		||||
	var createRes container.ContainerCreateCreatedBody
 | 
			
		||||
	if err := api.post(path, body, 201, &createRes); err != nil {
 | 
			
		||||
		return errors.Wrap(err, "create container request failed")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if createRes.ID == "" {
 | 
			
		||||
		return fmt.Errorf("container id is missing")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// start container
 | 
			
		||||
	path = fmt.Sprintf("/containers/%s/start", createRes.ID)
 | 
			
		||||
	url := fmt.Sprintf("%s%s", api.endpoint, path)
 | 
			
		||||
	request, err := http.NewRequest("POST", url, nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "error new container create request")
 | 
			
		||||
	}
 | 
			
		||||
	client := &http.Client{Timeout: 20 * time.Second}
 | 
			
		||||
	resp, err := client.Do(request)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "error do start container request")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
	b, err := ioutil.ReadAll(resp.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(b) != 0 {
 | 
			
		||||
		msg := fmt.Sprintf("start container met issue: %s", string(b))
 | 
			
		||||
		return errors.New(msg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err = api.inspect(createRes.ID); err != nil {
 | 
			
		||||
		msg := fmt.Sprintf("inspect container %s error", name)
 | 
			
		||||
		return errors.Wrap(err, msg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StopContainer stop a container
 | 
			
		||||
func (api *API) StopContainer(ctx context.Context, name string) error {
 | 
			
		||||
	return api.Stop(name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// InspectContainer inspect container
 | 
			
		||||
func (api *API) InspectContainer(ctx context.Context, name string, container interface{}) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	_ containerruntimes.ContainerRuntime = &API{}
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,89 +1,111 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/metrue/fx/config"
 | 
			
		||||
	"github.com/metrue/fx/constants"
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestDockerHTTP(t *testing.T) {
 | 
			
		||||
	host := config.Host{Host: "127.0.0.1"}
 | 
			
		||||
	api, err := Create(host.Host, constants.AgentPort)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serviceName := "a-test-service"
 | 
			
		||||
	project := types.Project{
 | 
			
		||||
		Name:     serviceName,
 | 
			
		||||
		Language: "node",
 | 
			
		||||
		Files: []types.ProjectSourceFile{
 | 
			
		||||
			types.ProjectSourceFile{
 | 
			
		||||
				Path: "Dockerfile",
 | 
			
		||||
				Body: `
 | 
			
		||||
FROM metrue/fx-node-base
 | 
			
		||||
 | 
			
		||||
COPY . .
 | 
			
		||||
EXPOSE 3000
 | 
			
		||||
CMD ["node", "app.js"]`,
 | 
			
		||||
				IsHandler: false,
 | 
			
		||||
			},
 | 
			
		||||
			types.ProjectSourceFile{
 | 
			
		||||
				Path: "app.js",
 | 
			
		||||
				Body: `
 | 
			
		||||
const Koa = require('koa');
 | 
			
		||||
const bodyParser = require('koa-bodyparser');
 | 
			
		||||
const func = require('./fx');
 | 
			
		||||
 | 
			
		||||
const app = new Koa();
 | 
			
		||||
app.use(bodyParser());
 | 
			
		||||
app.use(ctx => {
 | 
			
		||||
  const msg = func(ctx.request.body);
 | 
			
		||||
  ctx.body = msg;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.listen(3000);`,
 | 
			
		||||
				IsHandler: false,
 | 
			
		||||
			},
 | 
			
		||||
			types.ProjectSourceFile{
 | 
			
		||||
				Path: "fx.js",
 | 
			
		||||
				Body: `
 | 
			
		||||
module.exports = (input) => {
 | 
			
		||||
    return input.a + input.b
 | 
			
		||||
}
 | 
			
		||||
					`,
 | 
			
		||||
				IsHandler: true,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	service, err := api.Build(project)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if service.Name != serviceName {
 | 
			
		||||
		t.Fatalf("should get %s but got %s", serviceName, service.Name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := api.Run(9999, &service); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	services, err := api.list(serviceName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if len(services) != 1 {
 | 
			
		||||
		t.Fatal("service number should be 1")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := api.Stop(serviceName); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
// func TestDockerHTTP(t *testing.T) {
 | 
			
		||||
// 	const addr = "127.0.0.1"
 | 
			
		||||
// 	const user = ""
 | 
			
		||||
// 	const passord = ""
 | 
			
		||||
// 	provisioner := provision.NewWithHost(addr, user, passord)
 | 
			
		||||
// 	if err := utils.RunWithRetry(func() error {
 | 
			
		||||
// 		if !provisioner.IsFxAgentRunning() {
 | 
			
		||||
// 			if err := provisioner.StartFxAgent(); err != nil {
 | 
			
		||||
// 				log.Infof("could not start fx agent on host: %s", err)
 | 
			
		||||
// 				return err
 | 
			
		||||
// 			}
 | 
			
		||||
// 			log.Infof("fx agent started")
 | 
			
		||||
// 		} else {
 | 
			
		||||
// 			log.Infof("fx agent is running")
 | 
			
		||||
// 		}
 | 
			
		||||
// 		return nil
 | 
			
		||||
// 	}, 2*time.Second, 10); err != nil {
 | 
			
		||||
// 		t.Fatal(err)
 | 
			
		||||
// 	} else {
 | 
			
		||||
// 		defer provisioner.StopFxAgent()
 | 
			
		||||
// 	}
 | 
			
		||||
//
 | 
			
		||||
// 	host := config.Host{Host: "127.0.0.1"}
 | 
			
		||||
// 	api, err := Create(host.Host, constants.AgentPort)
 | 
			
		||||
// 	if err != nil {
 | 
			
		||||
// 		t.Fatal(err)
 | 
			
		||||
// 	}
 | 
			
		||||
//
 | 
			
		||||
// 	serviceName := "a-test-service"
 | 
			
		||||
// 	project := types.Project{
 | 
			
		||||
// 		Name:     serviceName,
 | 
			
		||||
// 		Language: "node",
 | 
			
		||||
// 		Files: []types.ProjectSourceFile{
 | 
			
		||||
// 			types.ProjectSourceFile{
 | 
			
		||||
// 				Path: "Dockerfile",
 | 
			
		||||
// 				Body: `
 | 
			
		||||
// FROM metrue/fx-node-base
 | 
			
		||||
//
 | 
			
		||||
// COPY . .
 | 
			
		||||
// EXPOSE 3000
 | 
			
		||||
// CMD ["node", "app.js"]`,
 | 
			
		||||
// 				IsHandler: false,
 | 
			
		||||
// 			},
 | 
			
		||||
// 			types.ProjectSourceFile{
 | 
			
		||||
// 				Path: "app.js",
 | 
			
		||||
// 				Body: `
 | 
			
		||||
// const Koa = require('koa');
 | 
			
		||||
// const bodyParser = require('koa-bodyparser');
 | 
			
		||||
// const func = require('./fx');
 | 
			
		||||
//
 | 
			
		||||
// const app = new Koa();
 | 
			
		||||
// app.use(bodyParser());
 | 
			
		||||
// app.use(ctx => {
 | 
			
		||||
//   const msg = func(ctx.request.body);
 | 
			
		||||
//   ctx.body = msg;
 | 
			
		||||
// });
 | 
			
		||||
//
 | 
			
		||||
// app.listen(3000);`,
 | 
			
		||||
// 				IsHandler: false,
 | 
			
		||||
// 			},
 | 
			
		||||
// 			types.ProjectSourceFile{
 | 
			
		||||
// 				Path: "fx.js",
 | 
			
		||||
// 				Body: `
 | 
			
		||||
// module.exports = (input) => {
 | 
			
		||||
//     return input.a + input.b
 | 
			
		||||
// }
 | 
			
		||||
// 					`,
 | 
			
		||||
// 				IsHandler: true,
 | 
			
		||||
// 			},
 | 
			
		||||
// 		},
 | 
			
		||||
// 	}
 | 
			
		||||
//
 | 
			
		||||
// 	service, err := api.Build(project)
 | 
			
		||||
// 	if err != nil {
 | 
			
		||||
// 		t.Fatal(err)
 | 
			
		||||
// 	}
 | 
			
		||||
// 	if service.Name != serviceName {
 | 
			
		||||
// 		t.Fatalf("should get %s but got %s", serviceName, service.Name)
 | 
			
		||||
// 	}
 | 
			
		||||
//
 | 
			
		||||
// 	if err := api.Run(9999, &service); err != nil {
 | 
			
		||||
// 		t.Fatal(err)
 | 
			
		||||
// 	}
 | 
			
		||||
//
 | 
			
		||||
// 	services, err := api.ListContainer(serviceName)
 | 
			
		||||
// 	if err != nil {
 | 
			
		||||
// 		t.Fatal(err)
 | 
			
		||||
// 	}
 | 
			
		||||
// 	if len(services) != 1 {
 | 
			
		||||
// 		t.Fatal("service number should be 1")
 | 
			
		||||
// 	}
 | 
			
		||||
//
 | 
			
		||||
// 	if err := api.Stop(serviceName); err != nil {
 | 
			
		||||
// 		t.Fatal(err)
 | 
			
		||||
// 	}
 | 
			
		||||
//
 | 
			
		||||
// 	const network = "fx-net"
 | 
			
		||||
// 	if err := api.CreateNetwork(network); err != nil {
 | 
			
		||||
// 		t.Fatal(err)
 | 
			
		||||
// 	}
 | 
			
		||||
//
 | 
			
		||||
// 	nws, err := api.GetNetwork(network)
 | 
			
		||||
// 	if err != nil {
 | 
			
		||||
// 		t.Fatal(err)
 | 
			
		||||
// 	}
 | 
			
		||||
// 	if nws[0].Name != network {
 | 
			
		||||
// 		t.Fatalf("should get %s but got %s", network, nws[0].Name)
 | 
			
		||||
// 	}
 | 
			
		||||
// }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,60 +1,51 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/apex/log"
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
	"github.com/metrue/fx/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Call function directly with given params
 | 
			
		||||
func (api *API) Call(file string, param string, project types.Project) error {
 | 
			
		||||
	service, err := api.Build(project)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatalf("Build Service: %v", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	log.Info("Build Service: \u2713")
 | 
			
		||||
 | 
			
		||||
	if err := api.Run(9999, &service); err != nil {
 | 
			
		||||
		log.Fatalf("Run Service: %v", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	log.Info("Run Service: \u2713")
 | 
			
		||||
 | 
			
		||||
	params := utils.PairsToParams(strings.Fields(param))
 | 
			
		||||
	body, err := json.Marshal(params)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Wait 2 seconds for service startup
 | 
			
		||||
	time.Sleep(time.Second * 2)
 | 
			
		||||
 | 
			
		||||
	url := fmt.Sprintf("http://%s:%d", service.Host, service.Port)
 | 
			
		||||
	r, err := http.NewRequest("POST", url, bytes.NewReader(body))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	r.Header.Set("Content-Type", "application/json")
 | 
			
		||||
	client := &http.Client{Timeout: 20 * time.Second}
 | 
			
		||||
	resp, err := client.Do(r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatalf("Call Service: %v", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	buf, err := ioutil.ReadAll(resp.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatalf("Call Service: %v", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	log.Info("Call Service: \u2713")
 | 
			
		||||
	return utils.OutputJSON(string(buf))
 | 
			
		||||
	return nil
 | 
			
		||||
	// service, err := api.Build(project)
 | 
			
		||||
	// if err != nil {
 | 
			
		||||
	// 	log.Fatalf("Build Service: %v", err)
 | 
			
		||||
	// 	return err
 | 
			
		||||
	// }
 | 
			
		||||
	// log.Info("Build Service: \u2713")
 | 
			
		||||
	//
 | 
			
		||||
	// if err := api.Run(9999, &service); err != nil {
 | 
			
		||||
	// 	log.Fatalf("Run Service: %v", err)
 | 
			
		||||
	// 	return err
 | 
			
		||||
	// }
 | 
			
		||||
	// log.Info("Run Service: \u2713")
 | 
			
		||||
	//
 | 
			
		||||
	// params := utils.PairsToParams(strings.Fields(param))
 | 
			
		||||
	// body, err := json.Marshal(params)
 | 
			
		||||
	// if err != nil {
 | 
			
		||||
	// 	return err
 | 
			
		||||
	// }
 | 
			
		||||
	//
 | 
			
		||||
	// // Wait 2 seconds for service startup
 | 
			
		||||
	// time.Sleep(time.Second * 2)
 | 
			
		||||
	//
 | 
			
		||||
	// url := fmt.Sprintf("http://%s:%d", service.Host, service.Port)
 | 
			
		||||
	// r, err := http.NewRequest("POST", url, bytes.NewReader(body))
 | 
			
		||||
	// if err != nil {
 | 
			
		||||
	// 	return err
 | 
			
		||||
	// }
 | 
			
		||||
	// r.Header.Set("Content-Type", "application/json")
 | 
			
		||||
	// client := &http.Client{Timeout: 20 * time.Second}
 | 
			
		||||
	// resp, err := client.Do(r)
 | 
			
		||||
	// if err != nil {
 | 
			
		||||
	// 	log.Fatalf("Call Service: %v", err)
 | 
			
		||||
	// 	return err
 | 
			
		||||
	// }
 | 
			
		||||
	// buf, err := ioutil.ReadAll(resp.Body)
 | 
			
		||||
	// if err != nil {
 | 
			
		||||
	// 	log.Fatalf("Call Service: %v", err)
 | 
			
		||||
	// 	return err
 | 
			
		||||
	// }
 | 
			
		||||
	// log.Info("Call Service: \u2713")
 | 
			
		||||
	// return utils.OutputJSON(string(buf))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +0,0 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/apex/log"
 | 
			
		||||
	"github.com/metrue/fx/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// List services
 | 
			
		||||
func (api *API) List(name string) error {
 | 
			
		||||
	services, err := api.list(name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatalf("List Services: %v", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, service := range services {
 | 
			
		||||
		if err := utils.OutputJSON(service); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -1,35 +0,0 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/mock/gomock"
 | 
			
		||||
	"github.com/metrue/fx/config"
 | 
			
		||||
	"github.com/metrue/fx/constants"
 | 
			
		||||
	gock "gopkg.in/h2non/gock.v1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestNetwork(t *testing.T) {
 | 
			
		||||
	defer gock.Off()
 | 
			
		||||
	ctrl := gomock.NewController(t)
 | 
			
		||||
	defer ctrl.Finish()
 | 
			
		||||
 | 
			
		||||
	host := config.Host{Host: "127.0.0.1"}
 | 
			
		||||
	api, err := Create(host.Host, constants.AgentPort)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const network = "fx-net"
 | 
			
		||||
	if err := api.CreateNetwork(network); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	nws, err := api.GetNetwork(network)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if nws[0].Name != network {
 | 
			
		||||
		t.Fatalf("should get %s but got %s", network, nws[0].Name)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,119 +1,59 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/apex/log"
 | 
			
		||||
	"github.com/google/go-querystring/query"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
	"github.com/metrue/fx/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func makeTar(project types.Project, tarFilePath string) error {
 | 
			
		||||
	dir, err := ioutil.TempDir("/tmp", "fx-build-dir")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer os.RemoveAll(dir)
 | 
			
		||||
 | 
			
		||||
	for _, file := range project.Files {
 | 
			
		||||
		tmpfn := filepath.Join(dir, file.Path)
 | 
			
		||||
		if err := utils.EnsureFile(tmpfn); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if err := ioutil.WriteFile(tmpfn, []byte(file.Body), 0666); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return utils.TarDir(dir, tarFilePath)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Build build a project
 | 
			
		||||
func (api *API) Build(project types.Project) (types.Service, error) {
 | 
			
		||||
	tarDir, err := ioutil.TempDir("/tmp", "fx-tar")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return types.Service{}, err
 | 
			
		||||
	}
 | 
			
		||||
	defer os.RemoveAll(tarDir)
 | 
			
		||||
 | 
			
		||||
	imageID := uuid.New().String()
 | 
			
		||||
	tarFilePath := filepath.Join(tarDir, fmt.Sprintf("%s.tar", imageID))
 | 
			
		||||
	if err := makeTar(project, tarFilePath); err != nil {
 | 
			
		||||
		return types.Service{}, err
 | 
			
		||||
	}
 | 
			
		||||
	labels := map[string]string{
 | 
			
		||||
		"belong-to": "fx",
 | 
			
		||||
	}
 | 
			
		||||
	if err := api.BuildImage(tarFilePath, imageID, labels); err != nil {
 | 
			
		||||
		return types.Service{}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return types.Service{
 | 
			
		||||
		Name:  project.Name,
 | 
			
		||||
		Image: imageID,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BuildImage build docker image
 | 
			
		||||
func (api *API) BuildImage(tarFile string, tag string, labels map[string]string) error {
 | 
			
		||||
	dockerBuildContext, err := os.Open(tarFile)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer dockerBuildContext.Close()
 | 
			
		||||
 | 
			
		||||
	type buildQuery struct {
 | 
			
		||||
		Labels     string `url:"labels,omitempty"`
 | 
			
		||||
		Tags       string `url:"t,omitempty"`
 | 
			
		||||
		Dockerfile string `url:"dockerfile,omitempty"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Apply default labels
 | 
			
		||||
	labelsJSON, _ := json.Marshal(labels)
 | 
			
		||||
	q := buildQuery{
 | 
			
		||||
		Tags:       tag,
 | 
			
		||||
		Labels:     string(labelsJSON),
 | 
			
		||||
		Dockerfile: "Dockerfile",
 | 
			
		||||
	}
 | 
			
		||||
	qs, err := query.Values(q)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	path := "/build"
 | 
			
		||||
	url := fmt.Sprintf("%s%s?%s", api.endpoint, path, qs.Encode())
 | 
			
		||||
	req, err := http.NewRequest("POST", url, dockerBuildContext)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req.Header.Set("Content-Type", "application/x-tar")
 | 
			
		||||
	client := &http.Client{Timeout: 600 * time.Second}
 | 
			
		||||
	resp, err := client.Do(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
	scanner := bufio.NewScanner(resp.Body)
 | 
			
		||||
 | 
			
		||||
	for scanner.Scan() {
 | 
			
		||||
		if os.Getenv("DEBUG") != "" {
 | 
			
		||||
			log.Infof(scanner.Text())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if err := scanner.Err(); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
// import (
 | 
			
		||||
// 	"fmt"
 | 
			
		||||
// 	"io/ioutil"
 | 
			
		||||
// 	"os"
 | 
			
		||||
// 	"path/filepath"
 | 
			
		||||
//
 | 
			
		||||
// 	"github.com/google/uuid"
 | 
			
		||||
// 	"github.com/metrue/fx/types"
 | 
			
		||||
// 	"github.com/metrue/fx/utils"
 | 
			
		||||
// )
 | 
			
		||||
//
 | 
			
		||||
// func makeTar(project types.Project, tarFilePath string) error {
 | 
			
		||||
// 	dir, err := ioutil.TempDir("/tmp", "fx-build-dir")
 | 
			
		||||
// 	if err != nil {
 | 
			
		||||
// 		return err
 | 
			
		||||
// 	}
 | 
			
		||||
//
 | 
			
		||||
// 	defer os.RemoveAll(dir)
 | 
			
		||||
//
 | 
			
		||||
// 	for _, file := range project.Files {
 | 
			
		||||
// 		tmpfn := filepath.Join(dir, file.Path)
 | 
			
		||||
// 		if err := utils.EnsureFile(tmpfn); err != nil {
 | 
			
		||||
// 			return err
 | 
			
		||||
// 		}
 | 
			
		||||
// 		if err := ioutil.WriteFile(tmpfn, []byte(file.Body), 0666); err != nil {
 | 
			
		||||
// 			return err
 | 
			
		||||
// 		}
 | 
			
		||||
// 	}
 | 
			
		||||
//
 | 
			
		||||
// 	return utils.TarDir(dir, tarFilePath)
 | 
			
		||||
// }
 | 
			
		||||
//
 | 
			
		||||
// // Build build a project
 | 
			
		||||
// func (api *API) Build(project types.Project) (types.Service, error) {
 | 
			
		||||
// 	tarDir, err := ioutil.TempDir("/tmp", "fx-tar")
 | 
			
		||||
// 	if err != nil {
 | 
			
		||||
// 		return types.Service{}, err
 | 
			
		||||
// 	}
 | 
			
		||||
// 	defer os.RemoveAll(tarDir)
 | 
			
		||||
//
 | 
			
		||||
// 	imageID := uuid.New().String()
 | 
			
		||||
// 	tarFilePath := filepath.Join(tarDir, fmt.Sprintf("%s.tar", imageID))
 | 
			
		||||
// 	if err := makeTar(project, tarFilePath); err != nil {
 | 
			
		||||
// 		return types.Service{}, err
 | 
			
		||||
// 	}
 | 
			
		||||
// 	labels := map[string]string{
 | 
			
		||||
// 		"belong-to": "fx",
 | 
			
		||||
// 	}
 | 
			
		||||
// 	if err := api.BuildImage(tarFilePath, imageID, labels); err != nil {
 | 
			
		||||
// 		return types.Service{}, err
 | 
			
		||||
// 	}
 | 
			
		||||
//
 | 
			
		||||
// 	return types.Service{
 | 
			
		||||
// 		Name:  project.Name,
 | 
			
		||||
// 		Image: imageID,
 | 
			
		||||
// 	}, nil
 | 
			
		||||
// }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,166 +1,82 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/metrue/fx/config"
 | 
			
		||||
	"github.com/metrue/fx/constants"
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
	gock "gopkg.in/h2non/gock.v1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestMakeTar(t *testing.T) {
 | 
			
		||||
	serviceName := "mock-service-abc"
 | 
			
		||||
	project := types.Project{
 | 
			
		||||
		Name:     serviceName,
 | 
			
		||||
		Language: "node",
 | 
			
		||||
		Files: []types.ProjectSourceFile{
 | 
			
		||||
			types.ProjectSourceFile{
 | 
			
		||||
				Path: "Dockerfile",
 | 
			
		||||
				Body: `
 | 
			
		||||
FROM metrue/fx-node-base
 | 
			
		||||
 | 
			
		||||
COPY . .
 | 
			
		||||
EXPOSE 3000
 | 
			
		||||
CMD ["node", "app.js"]`,
 | 
			
		||||
				IsHandler: false,
 | 
			
		||||
			},
 | 
			
		||||
			types.ProjectSourceFile{
 | 
			
		||||
				Path: "app.js",
 | 
			
		||||
				Body: `
 | 
			
		||||
const Koa = require('koa');
 | 
			
		||||
const bodyParser = require('koa-bodyparser');
 | 
			
		||||
const func = require('./fx');
 | 
			
		||||
 | 
			
		||||
const app = new Koa();
 | 
			
		||||
app.use(bodyParser());
 | 
			
		||||
app.use(ctx => {
 | 
			
		||||
  const msg = func(ctx.request.body);
 | 
			
		||||
  ctx.body = msg;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.listen(3000);`,
 | 
			
		||||
				IsHandler: false,
 | 
			
		||||
			},
 | 
			
		||||
			types.ProjectSourceFile{
 | 
			
		||||
				Path: "fx.js",
 | 
			
		||||
				Body: `
 | 
			
		||||
module.exports = (input) => {
 | 
			
		||||
    return input.a + input.b
 | 
			
		||||
}
 | 
			
		||||
					`,
 | 
			
		||||
				IsHandler: true,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	tarDir, err := ioutil.TempDir("/tmp", "fx-tar")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	defer os.RemoveAll(tarDir)
 | 
			
		||||
 | 
			
		||||
	tarFilePath := filepath.Join(tarDir, fmt.Sprintf("%s.tar", serviceName))
 | 
			
		||||
	if err := makeTar(project, tarFilePath); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	file, err := os.Open(tarFilePath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	stat, err := file.Stat()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if stat.Name() != serviceName+".tar" {
 | 
			
		||||
		t.Fatalf("should get %s but got %s", serviceName+".tar", stat.Name())
 | 
			
		||||
	}
 | 
			
		||||
	if stat.Size() <= 0 {
 | 
			
		||||
		t.Fatalf("tarfile invalid: size %d", stat.Size())
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestBuild(t *testing.T) {
 | 
			
		||||
	defer gock.Off()
 | 
			
		||||
 | 
			
		||||
	host := config.Host{Host: "127.0.0.1"}
 | 
			
		||||
	api, err := Create(host.Host, constants.AgentPort)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	url := "http://" + host.Host + ":" + constants.AgentPort
 | 
			
		||||
	gock.New(url).
 | 
			
		||||
		Post("/v" + api.version + "/build").
 | 
			
		||||
		AddMatcher(func(req *http.Request, ereq *gock.Request) (bool, error) {
 | 
			
		||||
			if strings.Contains(req.URL.String(), "/v"+api.version+"/build") {
 | 
			
		||||
				return true, nil
 | 
			
		||||
			}
 | 
			
		||||
			return false, nil
 | 
			
		||||
		}).
 | 
			
		||||
		Reply(200).
 | 
			
		||||
		JSON(map[string]string{
 | 
			
		||||
			"stream": "Step 1/5...",
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
	serviceName := "mock-service-abc"
 | 
			
		||||
	project := types.Project{
 | 
			
		||||
		Name:     serviceName,
 | 
			
		||||
		Language: "node",
 | 
			
		||||
		Files: []types.ProjectSourceFile{
 | 
			
		||||
			types.ProjectSourceFile{
 | 
			
		||||
				Path: "Dockerfile",
 | 
			
		||||
				Body: `
 | 
			
		||||
FROM metrue/fx-node-base
 | 
			
		||||
 | 
			
		||||
COPY . .
 | 
			
		||||
EXPOSE 3000
 | 
			
		||||
CMD ["node", "app.js"]`,
 | 
			
		||||
				IsHandler: false,
 | 
			
		||||
			},
 | 
			
		||||
			types.ProjectSourceFile{
 | 
			
		||||
				Path: "app.js",
 | 
			
		||||
				Body: `
 | 
			
		||||
const Koa = require('koa');
 | 
			
		||||
const bodyParser = require('koa-bodyparser');
 | 
			
		||||
const func = require('./fx');
 | 
			
		||||
 | 
			
		||||
const app = new Koa();
 | 
			
		||||
app.use(bodyParser());
 | 
			
		||||
app.use(ctx => {
 | 
			
		||||
  const msg = func(ctx.request.body);
 | 
			
		||||
  ctx.body = msg;
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.listen(3000);`,
 | 
			
		||||
				IsHandler: false,
 | 
			
		||||
			},
 | 
			
		||||
			types.ProjectSourceFile{
 | 
			
		||||
				Path: "fx.js",
 | 
			
		||||
				Body: `
 | 
			
		||||
module.exports = (input) => {
 | 
			
		||||
    return input.a + input.b
 | 
			
		||||
}
 | 
			
		||||
					`,
 | 
			
		||||
				IsHandler: true,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	service, err := api.Build(project)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if service.Name != serviceName {
 | 
			
		||||
		t.Fatalf("should get %s but got %s", serviceName, service.Name)
 | 
			
		||||
	}
 | 
			
		||||
	if service.Image == "" {
 | 
			
		||||
		t.Fatal("service image should not be empty")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
// import (
 | 
			
		||||
// 	"fmt"
 | 
			
		||||
// 	"io/ioutil"
 | 
			
		||||
// 	"os"
 | 
			
		||||
// 	"path/filepath"
 | 
			
		||||
// 	"testing"
 | 
			
		||||
//
 | 
			
		||||
// 	"github.com/metrue/fx/types"
 | 
			
		||||
// )
 | 
			
		||||
//
 | 
			
		||||
// func TestMakeTar(t *testing.T) {
 | 
			
		||||
// 	serviceName := "mock-service-abc"
 | 
			
		||||
// 	project := types.Project{
 | 
			
		||||
// 		Name:     serviceName,
 | 
			
		||||
// 		Language: "node",
 | 
			
		||||
// 		Files: []types.ProjectSourceFile{
 | 
			
		||||
// 			types.ProjectSourceFile{
 | 
			
		||||
// 				Path: "Dockerfile",
 | 
			
		||||
// 				Body: `
 | 
			
		||||
// FROM metrue/fx-node-base
 | 
			
		||||
//
 | 
			
		||||
// COPY . .
 | 
			
		||||
// EXPOSE 3000
 | 
			
		||||
// CMD ["node", "app.js"]`,
 | 
			
		||||
// 				IsHandler: false,
 | 
			
		||||
// 			},
 | 
			
		||||
// 			types.ProjectSourceFile{
 | 
			
		||||
// 				Path: "app.js",
 | 
			
		||||
// 				Body: `
 | 
			
		||||
// const Koa = require('koa');
 | 
			
		||||
// const bodyParser = require('koa-bodyparser');
 | 
			
		||||
// const func = require('./fx');
 | 
			
		||||
//
 | 
			
		||||
// const app = new Koa();
 | 
			
		||||
// app.use(bodyParser());
 | 
			
		||||
// app.use(ctx => {
 | 
			
		||||
//   const msg = func(ctx.request.body);
 | 
			
		||||
//   ctx.body = msg;
 | 
			
		||||
// });
 | 
			
		||||
//
 | 
			
		||||
// app.listen(3000);`,
 | 
			
		||||
// 				IsHandler: false,
 | 
			
		||||
// 			},
 | 
			
		||||
// 			types.ProjectSourceFile{
 | 
			
		||||
// 				Path: "fx.js",
 | 
			
		||||
// 				Body: `
 | 
			
		||||
// module.exports = (input) => {
 | 
			
		||||
//     return input.a + input.b
 | 
			
		||||
// }
 | 
			
		||||
// 					`,
 | 
			
		||||
// 				IsHandler: true,
 | 
			
		||||
// 			},
 | 
			
		||||
// 		},
 | 
			
		||||
// 	}
 | 
			
		||||
// 	tarDir, err := ioutil.TempDir("/tmp", "fx-tar")
 | 
			
		||||
// 	if err != nil {
 | 
			
		||||
// 		t.Fatal(err)
 | 
			
		||||
// 	}
 | 
			
		||||
// 	defer os.RemoveAll(tarDir)
 | 
			
		||||
//
 | 
			
		||||
// 	tarFilePath := filepath.Join(tarDir, fmt.Sprintf("%s.tar", serviceName))
 | 
			
		||||
// 	if err := makeTar(project, tarFilePath); err != nil {
 | 
			
		||||
// 		t.Fatal(err)
 | 
			
		||||
// 	}
 | 
			
		||||
//
 | 
			
		||||
// 	file, err := os.Open(tarFilePath)
 | 
			
		||||
// 	if err != nil {
 | 
			
		||||
// 		t.Fatal(err)
 | 
			
		||||
// 	}
 | 
			
		||||
// 	stat, err := file.Stat()
 | 
			
		||||
// 	if err != nil {
 | 
			
		||||
// 		t.Fatal(err)
 | 
			
		||||
// 	}
 | 
			
		||||
// 	if stat.Name() != serviceName+".tar" {
 | 
			
		||||
// 		t.Fatalf("should get %s but got %s", serviceName+".tar", stat.Name())
 | 
			
		||||
// 	}
 | 
			
		||||
// 	if stat.Size() <= 0 {
 | 
			
		||||
// 		t.Fatalf("tarfile invalid: size %d", stat.Size())
 | 
			
		||||
// 	}
 | 
			
		||||
// }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,55 +0,0 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/mock/gomock"
 | 
			
		||||
	"github.com/metrue/fx/config"
 | 
			
		||||
	"github.com/metrue/fx/constants"
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
 | 
			
		||||
	gock "gopkg.in/h2non/gock.v1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestServiceRun(t *testing.T) {
 | 
			
		||||
	defer gock.Off()
 | 
			
		||||
	ctrl := gomock.NewController(t)
 | 
			
		||||
	defer ctrl.Finish()
 | 
			
		||||
 | 
			
		||||
	host := config.Host{Host: "127.0.0.1"}
 | 
			
		||||
	api, err := Create(host.Host, constants.AgentPort)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	service := types.Service{
 | 
			
		||||
		Name:  "a-mock-service",
 | 
			
		||||
		Image: "a-mock-image-id",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mockContainerID := "mock-container-id"
 | 
			
		||||
	url := "http://" + host.Host + ":" + constants.AgentPort
 | 
			
		||||
	gock.New(url).
 | 
			
		||||
		Post("/v0.2.1/containers").
 | 
			
		||||
		AddMatcher(func(req *http.Request, ereq *gock.Request) (m bool, e error) {
 | 
			
		||||
			// TODO multiple matching not supported by gock
 | 
			
		||||
			if req.URL.String() == url+"/v0.2.1/containers/"+mockContainerID+"/start" {
 | 
			
		||||
				return true, nil
 | 
			
		||||
			} else if req.URL.String() == url+"/v0.2.1/containers/create?name="+service.Name {
 | 
			
		||||
				return true, nil
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			return false, nil
 | 
			
		||||
		}).
 | 
			
		||||
		Reply(201).
 | 
			
		||||
		JSON(map[string]interface{}{
 | 
			
		||||
			"Id":       mockContainerID,
 | 
			
		||||
			"Warnings": []string{},
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
	// FIXME
 | 
			
		||||
	if err := api.Run(9999, &service); err == nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,39 +0,0 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/mock/gomock"
 | 
			
		||||
	"github.com/metrue/fx/config"
 | 
			
		||||
	"github.com/metrue/fx/constants"
 | 
			
		||||
	gock "gopkg.in/h2non/gock.v1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestStop(t *testing.T) {
 | 
			
		||||
	defer gock.Off()
 | 
			
		||||
	ctrl := gomock.NewController(t)
 | 
			
		||||
	defer ctrl.Finish()
 | 
			
		||||
 | 
			
		||||
	host := config.Host{Host: "127.0.0.1"}
 | 
			
		||||
	api, err := Create(host.Host, constants.AgentPort)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	mockServiceName := "mock-service-name"
 | 
			
		||||
	url := "http://" + host.Host + ":" + constants.AgentPort
 | 
			
		||||
	gock.New(url).
 | 
			
		||||
		Post("/v" + api.version + "/containers/" + mockServiceName + "/stop").
 | 
			
		||||
		AddMatcher(func(req *http.Request, ereq *gock.Request) (m bool, e error) {
 | 
			
		||||
			if strings.Contains(req.URL.String(), "/v"+api.version+"/containers/"+mockServiceName+"/stop") {
 | 
			
		||||
				return true, nil
 | 
			
		||||
			}
 | 
			
		||||
			return false, nil
 | 
			
		||||
		}).
 | 
			
		||||
		Reply(204)
 | 
			
		||||
	if err := api.Stop(mockServiceName); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,81 +1,71 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/apex/log"
 | 
			
		||||
	"github.com/docker/docker/api/types/container"
 | 
			
		||||
	"github.com/metrue/fx/constants"
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// UpOptions options for up
 | 
			
		||||
type UpOptions struct {
 | 
			
		||||
	Body       []byte
 | 
			
		||||
	Lang       string
 | 
			
		||||
	Name       string
 | 
			
		||||
	Port       int
 | 
			
		||||
	HealtCheck bool
 | 
			
		||||
	Project    types.Project
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Up up a source code of function to be a service
 | 
			
		||||
func (api *API) Up(opt UpOptions) error {
 | 
			
		||||
	service, err := api.Build(opt.Project)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		log.Fatalf("Build Service %s: %v", opt.Name, err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	log.Infof("Build Service %s: %s", opt.Name, constants.CheckedSymbol)
 | 
			
		||||
 | 
			
		||||
	if err := api.Run(opt.Port, &service); err != nil {
 | 
			
		||||
		log.Fatalf("Run Service: %v", err)
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	log.Infof("Run Service: %s", constants.CheckedSymbol)
 | 
			
		||||
	log.Infof("Service (%s) is running on: %s:%d", service.Name, service.Host, service.Port)
 | 
			
		||||
 | 
			
		||||
	if opt.HealtCheck {
 | 
			
		||||
		go func() {
 | 
			
		||||
			resultC, errC := api.ContainerWait(
 | 
			
		||||
				context.Background(),
 | 
			
		||||
				service.ID,
 | 
			
		||||
				container.WaitConditionNextExit,
 | 
			
		||||
				20*time.Second,
 | 
			
		||||
			)
 | 
			
		||||
			for {
 | 
			
		||||
				select {
 | 
			
		||||
				case res := <-resultC:
 | 
			
		||||
					var msg string
 | 
			
		||||
					if res.Error != nil {
 | 
			
		||||
						msg = res.Error.Message
 | 
			
		||||
					}
 | 
			
		||||
					log.Warnf("container exited: Code(%d) %s %s", res.StatusCode, msg, constants.UncheckedSymbol)
 | 
			
		||||
				case err := <-errC:
 | 
			
		||||
					log.Fatalf("wait container status exit: %s, %v", constants.UncheckedSymbol, err)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}()
 | 
			
		||||
 | 
			
		||||
		trys := 0
 | 
			
		||||
		for {
 | 
			
		||||
			if trys > 2 {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
			info, err := api.inspect(service.ID)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Fatalf("healt checking failed: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
			if info.State.Running {
 | 
			
		||||
				log.Info("service is running")
 | 
			
		||||
			} else {
 | 
			
		||||
				log.Warnf("service is %s", info.State.Status)
 | 
			
		||||
			}
 | 
			
		||||
			time.Sleep(1 * time.Second)
 | 
			
		||||
			trys++
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
// // UpOptions options for up
 | 
			
		||||
// type UpOptions struct {
 | 
			
		||||
// 	Body       []byte
 | 
			
		||||
// 	Lang       string
 | 
			
		||||
// 	Name       string
 | 
			
		||||
// 	Port       int
 | 
			
		||||
// 	HealtCheck bool
 | 
			
		||||
// 	Project    types.Project
 | 
			
		||||
// }
 | 
			
		||||
//
 | 
			
		||||
// // Up up a source code of function to be a service
 | 
			
		||||
// func (api *API) Up(opt UpOptions) error {
 | 
			
		||||
// 	service, err := api.Build(opt.Project)
 | 
			
		||||
// 	if err != nil {
 | 
			
		||||
// 		log.Fatalf("Build Service %s: %v", opt.Name, err)
 | 
			
		||||
// 		return err
 | 
			
		||||
// 	}
 | 
			
		||||
// 	log.Infof("Build Service %s: %s", opt.Name, constants.CheckedSymbol)
 | 
			
		||||
//
 | 
			
		||||
// 	if err := api.Run(opt.Port, &service); err != nil {
 | 
			
		||||
// 		log.Fatalf("Run Service: %v", err)
 | 
			
		||||
// 		return err
 | 
			
		||||
// 	}
 | 
			
		||||
// 	log.Infof("Run Service: %s", constants.CheckedSymbol)
 | 
			
		||||
// 	log.Infof("Service (%s) is running on: %s:%d", service.Name, service.Host, service.Port)
 | 
			
		||||
//
 | 
			
		||||
// 	if opt.HealtCheck {
 | 
			
		||||
// 		go func() {
 | 
			
		||||
// 			resultC, errC := api.ContainerWait(
 | 
			
		||||
// 				context.Background(),
 | 
			
		||||
// 				service.ID,
 | 
			
		||||
// 				container.WaitConditionNextExit,
 | 
			
		||||
// 				20*time.Second,
 | 
			
		||||
// 			)
 | 
			
		||||
// 			for {
 | 
			
		||||
// 				select {
 | 
			
		||||
// 				case res := <-resultC:
 | 
			
		||||
// 					var msg string
 | 
			
		||||
// 					if res.Error != nil {
 | 
			
		||||
// 						msg = res.Error.Message
 | 
			
		||||
// 					}
 | 
			
		||||
// 					log.Warnf("container exited: Code(%d) %s %s", res.StatusCode, msg, constants.UncheckedSymbol)
 | 
			
		||||
// 				case err := <-errC:
 | 
			
		||||
// 					log.Fatalf("wait container status exit: %s, %v", constants.UncheckedSymbol, err)
 | 
			
		||||
// 				}
 | 
			
		||||
// 			}
 | 
			
		||||
// 		}()
 | 
			
		||||
//
 | 
			
		||||
// 		trys := 0
 | 
			
		||||
// 		for {
 | 
			
		||||
// 			if trys > 2 {
 | 
			
		||||
// 				break
 | 
			
		||||
// 			}
 | 
			
		||||
// 			info, err := api.inspect(service.ID)
 | 
			
		||||
// 			if err != nil {
 | 
			
		||||
// 				log.Fatalf("healt checking failed: %v", err)
 | 
			
		||||
// 			}
 | 
			
		||||
// 			if info.State.Running {
 | 
			
		||||
// 				log.Info("service is running")
 | 
			
		||||
// 			} else {
 | 
			
		||||
// 				log.Warnf("service is %s", info.State.Status)
 | 
			
		||||
// 			}
 | 
			
		||||
// 			time.Sleep(1 * time.Second)
 | 
			
		||||
// 			trys++
 | 
			
		||||
// 		}
 | 
			
		||||
// 	}
 | 
			
		||||
//
 | 
			
		||||
// 	return nil
 | 
			
		||||
// }
 | 
			
		||||
 
 | 
			
		||||
@@ -9,10 +9,12 @@ import (
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/apex/log"
 | 
			
		||||
	dockerTypes "github.com/docker/docker/api/types"
 | 
			
		||||
	dockerTypesContainer "github.com/docker/docker/api/types/container"
 | 
			
		||||
	dockerFilters "github.com/docker/docker/api/types/filters"
 | 
			
		||||
	"github.com/docker/docker/client"
 | 
			
		||||
	"github.com/docker/go-connections/nat"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
@@ -69,14 +71,12 @@ func (d *Docker) BuildImage(ctx context.Context, workdir string, name string) er
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
	body, err := ioutil.ReadAll(resp.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if os.Getenv("DEBUG") != "" {
 | 
			
		||||
		defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
		body, err := ioutil.ReadAll(resp.Body)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		log.Info(string(body))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -88,10 +88,10 @@ func (d *Docker) PushImage(ctx context.Context, name string) (string, error) {
 | 
			
		||||
	username := os.Getenv("DOCKER_USERNAME")
 | 
			
		||||
	password := os.Getenv("DOCKER_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{
 | 
			
		||||
		Username: username,
 | 
			
		||||
		Password: password,
 | 
			
		||||
@@ -135,28 +135,34 @@ func (d *Docker) InspectImage(ctx context.Context, name string, img interface{})
 | 
			
		||||
	return json.NewDecoder(rdr).Decode(&img)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StartContainer create and start a container from given image
 | 
			
		||||
func (d *Docker) StartContainer(ctx context.Context, name string, image string, ports []int32) error {
 | 
			
		||||
	config := &dockerTypesContainer.Config{
 | 
			
		||||
		Image: image,
 | 
			
		||||
		ExposedPorts: nat.PortSet{
 | 
			
		||||
			"3000/tcp": struct{}{},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
// TagImage tag image
 | 
			
		||||
func (d *Docker) TagImage(ctx context.Context, name string, tag string) error {
 | 
			
		||||
	return d.ImageTag(ctx, name, tag)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	bindings := []nat.PortBinding{}
 | 
			
		||||
	for _, port := range ports {
 | 
			
		||||
		bindings = append(bindings, nat.PortBinding{
 | 
			
		||||
			HostIP:   types.DefaultHost,
 | 
			
		||||
			HostPort: fmt.Sprintf("%d", port),
 | 
			
		||||
		})
 | 
			
		||||
// StartContainer create and start a container from given image
 | 
			
		||||
func (d *Docker) StartContainer(ctx context.Context, name string, image string, ports []types.PortBinding) error {
 | 
			
		||||
	portSet := nat.PortSet{}
 | 
			
		||||
	portMap := nat.PortMap{}
 | 
			
		||||
	for _, binding := range ports {
 | 
			
		||||
		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{
 | 
			
		||||
		Image:        image,
 | 
			
		||||
		ExposedPorts: portSet,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hostConfig := &dockerTypesContainer.HostConfig{
 | 
			
		||||
		AutoRemove: true,
 | 
			
		||||
		PortBindings: nat.PortMap{
 | 
			
		||||
			"3000/tcp": bindings,
 | 
			
		||||
		},
 | 
			
		||||
		AutoRemove:   true,
 | 
			
		||||
		PortBindings: portMap,
 | 
			
		||||
	}
 | 
			
		||||
	resp, err := d.ContainerCreate(ctx, config, hostConfig, nil, name)
 | 
			
		||||
	if os.Getenv("DEBUG") != "" {
 | 
			
		||||
@@ -186,6 +192,40 @@ func (d *Docker) InspectContainer(ctx context.Context, name string, container in
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListContainer list containers
 | 
			
		||||
func (d *Docker) ListContainer(ctx context.Context, name string) ([]types.Service, error) {
 | 
			
		||||
	args := dockerFilters.NewArgs(
 | 
			
		||||
		dockerFilters.Arg("label", "belong-to=fx"),
 | 
			
		||||
	)
 | 
			
		||||
	containers, err := d.ContainerList(ctx, dockerTypes.ContainerListOptions{
 | 
			
		||||
		Filters: args,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return []types.Service{}, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	svs := make(map[string]types.Service)
 | 
			
		||||
	for _, container := range containers {
 | 
			
		||||
		// container name have extra forward slash
 | 
			
		||||
		// https://github.com/moby/moby/issues/6705
 | 
			
		||||
		if strings.HasPrefix(container.Names[0], fmt.Sprintf("/%s", name)) {
 | 
			
		||||
			svs[container.Image] = types.Service{
 | 
			
		||||
				Name:  container.Names[0],
 | 
			
		||||
				Image: container.Image,
 | 
			
		||||
				ID:    container.ID,
 | 
			
		||||
				Host:  container.Ports[0].IP,
 | 
			
		||||
				Port:  int(container.Ports[0].PublicPort),
 | 
			
		||||
				State: container.State,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	services := []types.Service{}
 | 
			
		||||
	for _, s := range svs {
 | 
			
		||||
		services = append(services, s)
 | 
			
		||||
	}
 | 
			
		||||
	return services, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	_ containerruntimes.ContainerRuntime = &Docker{}
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -45,7 +45,7 @@ func TestDocker(t *testing.T) {
 | 
			
		||||
	username := os.Getenv("DOCKER_USERNAME")
 | 
			
		||||
	password := os.Getenv("DOCKER_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)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,19 @@
 | 
			
		||||
package containerruntimes
 | 
			
		||||
 | 
			
		||||
import "context"
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ContainerRuntime interface
 | 
			
		||||
type ContainerRuntime interface {
 | 
			
		||||
	BuildImage(ctx context.Context, workdir string, name string) error
 | 
			
		||||
	PushImage(ctx context.Context, name string) (string, error)
 | 
			
		||||
	InspectImage(ct context.Context, name string, img interface{}) error
 | 
			
		||||
	StartContainer(ctx context.Context, name string, image string, ports []int32) error
 | 
			
		||||
	InspectImage(ctx context.Context, name string, img interface{}) error
 | 
			
		||||
	TagImage(ctx context.Context, name string, tag string) error
 | 
			
		||||
	StartContainer(ctx context.Context, name string, image string, bindings []types.PortBinding) error
 | 
			
		||||
	StopContainer(ctx context.Context, name string) error
 | 
			
		||||
	InspectContainer(ctx context.Context, name string, container interface{}) error
 | 
			
		||||
	ListContainer(ctx context.Context, filter string) ([]types.Service, error)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										58
									
								
								context/context.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								context/context.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
package context
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type key string
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	keyCliCtx = key("cmd_cli")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Context fx context
 | 
			
		||||
type Context struct {
 | 
			
		||||
	context.Context
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewContext new a context
 | 
			
		||||
func NewContext() *Context {
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	return &Context{ctx}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FromCliContext create context from cli.Context
 | 
			
		||||
func FromCliContext(c *cli.Context) *Context {
 | 
			
		||||
	ctx := NewContext()
 | 
			
		||||
	ctx.WithCliContext(c)
 | 
			
		||||
	return ctx
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// WithCliContext set cli.Context
 | 
			
		||||
func (ctx *Context) WithCliContext(c *cli.Context) {
 | 
			
		||||
	newCtx := context.WithValue(ctx.Context, keyCliCtx, c)
 | 
			
		||||
	ctx.Context = newCtx
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetCliContext get cli.Context
 | 
			
		||||
func (ctx *Context) GetCliContext() *cli.Context {
 | 
			
		||||
	return ctx.Value(keyCliCtx).(*cli.Context)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Set a value with name
 | 
			
		||||
func (ctx *Context) Set(name string, value interface{}) {
 | 
			
		||||
	newCtx := context.WithValue(ctx.Context, name, value)
 | 
			
		||||
	ctx.Context = newCtx
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Get a value
 | 
			
		||||
func (ctx *Context) Get(name string) interface{} {
 | 
			
		||||
	return ctx.Context.Value(name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Use invole a middle
 | 
			
		||||
func (ctx *Context) Use(fn func(ctx *Context) error) error {
 | 
			
		||||
	return fn(ctx)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								context/context_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								context/context_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
package context
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestContext(t *testing.T) {
 | 
			
		||||
	ctx := NewContext()
 | 
			
		||||
	cli := cli.NewContext(nil, nil, nil)
 | 
			
		||||
	ctx.WithCliContext(cli)
 | 
			
		||||
	c := ctx.GetCliContext()
 | 
			
		||||
	if c != cli {
 | 
			
		||||
		t.Fatalf("should get %v but got %v", cli, c)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	key := "k_1"
 | 
			
		||||
	value := "hello"
 | 
			
		||||
	ctx.Set(key, "hello")
 | 
			
		||||
	v := ctx.Get(key).(string)
 | 
			
		||||
	if v != value {
 | 
			
		||||
		t.Fatalf("should get %v but %v", value, v)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
							
								
								
									
										78
									
								
								contrib/docker_packer/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								contrib/docker_packer/main.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
	//nolint
 | 
			
		||||
	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,11 +1,16 @@
 | 
			
		||||
package deploy
 | 
			
		||||
 | 
			
		||||
import "context"
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	types "github.com/metrue/fx/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Deployer make a image a service
 | 
			
		||||
type Deployer interface {
 | 
			
		||||
	Deploy(ctx context.Context, workdir string, name string, ports []int32) error
 | 
			
		||||
	Deploy(ctx context.Context, fn types.Func, name string, image string, bindings []types.PortBinding) error
 | 
			
		||||
	Destroy(ctx context.Context, name string) error
 | 
			
		||||
	Update(ctx context.Context, name string) error
 | 
			
		||||
	GetStatus(ctx context.Context, name string) error
 | 
			
		||||
	List(ctx context.Context, name string) ([]types.Service, error)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,52 +2,44 @@ package docker
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"time"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	dockerTypes "github.com/docker/docker/api/types"
 | 
			
		||||
	runtime "github.com/metrue/fx/container_runtimes/docker/sdk"
 | 
			
		||||
	"github.com/metrue/fx/constants"
 | 
			
		||||
	containerruntimes "github.com/metrue/fx/container_runtimes"
 | 
			
		||||
	dockerHTTP "github.com/metrue/fx/container_runtimes/docker/http"
 | 
			
		||||
	dockerSDK "github.com/metrue/fx/container_runtimes/docker/sdk"
 | 
			
		||||
	"github.com/metrue/fx/deploy"
 | 
			
		||||
	"github.com/metrue/fx/utils"
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Docker manage container
 | 
			
		||||
type Docker struct {
 | 
			
		||||
	client *runtime.Docker
 | 
			
		||||
	cli containerruntimes.ContainerRuntime
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateClient create a docker instance
 | 
			
		||||
func CreateClient(ctx context.Context) (*Docker, error) {
 | 
			
		||||
	cli, err := runtime.CreateClient(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
func CreateClient(ctx context.Context) (d *Docker, err error) {
 | 
			
		||||
	var cli containerruntimes.ContainerRuntime
 | 
			
		||||
	host := os.Getenv("DOCKER_REMOTE_HOST_ADDR")
 | 
			
		||||
	user := os.Getenv("DOCKER_REMOTE_HOST_USER")
 | 
			
		||||
	if host != "" && user != "" {
 | 
			
		||||
		cli, err = dockerHTTP.Create(host, constants.AgentPort)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		cli, err = dockerSDK.CreateClient(ctx)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return &Docker{client: cli}, nil
 | 
			
		||||
 | 
			
		||||
	return &Docker{cli: cli}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 {
 | 
			
		||||
	if err := d.client.BuildImage(ctx, workdir, name); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// config := &dockerTypesContainer.Config{
 | 
			
		||||
	// 	Image: image,
 | 
			
		||||
	// 	ExposedPorts: nat.PortSet{
 | 
			
		||||
	// 		"3000/tcp": struct{}{},
 | 
			
		||||
	// 	},
 | 
			
		||||
	// }
 | 
			
		||||
	// when deploy a function on a bare Docker running without Kubernetes,
 | 
			
		||||
	// image would be built on-demand on host locally, so there is no need to
 | 
			
		||||
	// pull image from remote.
 | 
			
		||||
	// But it takes some times waiting image ready after image built, we retry to make sure it ready here
 | 
			
		||||
	var imgInfo dockerTypes.ImageInspect
 | 
			
		||||
	if err := utils.RunWithRetry(func() error {
 | 
			
		||||
		return d.client.InspectImage(ctx, name, &imgInfo)
 | 
			
		||||
	}, time.Second*1, 5); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return d.client.StartContainer(ctx, name, name, ports)
 | 
			
		||||
func (d *Docker) Deploy(ctx context.Context, fn types.Func, name string, image string, ports []types.PortBinding) error {
 | 
			
		||||
	return d.cli.StartContainer(ctx, name, image, ports)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Update a container
 | 
			
		||||
@@ -57,7 +49,7 @@ func (d *Docker) Update(ctx context.Context, name string) error {
 | 
			
		||||
 | 
			
		||||
// Destroy stop and remove container
 | 
			
		||||
func (d *Docker) Destroy(ctx context.Context, name string) error {
 | 
			
		||||
	return d.client.ContainerStop(ctx, name, nil)
 | 
			
		||||
	return d.cli.StopContainer(ctx, name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetStatus get status of container
 | 
			
		||||
@@ -65,6 +57,12 @@ func (d *Docker) GetStatus(ctx context.Context, name string) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// List services
 | 
			
		||||
func (d *Docker) List(ctx context.Context, name string) ([]types.Service, error) {
 | 
			
		||||
	// FIXME support remote host
 | 
			
		||||
	return d.cli.ListContainer(ctx, name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	_ deploy.Deployer = &Docker{}
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +1,43 @@
 | 
			
		||||
package docker
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestDocker(t *testing.T) {
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	cli, err := CreateClient(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	workdir := "./fixture"
 | 
			
		||||
	name := "helloworld"
 | 
			
		||||
	ports := []int32{12345, 12346}
 | 
			
		||||
	if err := cli.Deploy(ctx, workdir, name, ports); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	time.Sleep(1 * time.Second)
 | 
			
		||||
 | 
			
		||||
	if err := cli.Destroy(ctx, name); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	// 	ctx := context.Background()
 | 
			
		||||
	// 	cli, err := CreateClient(ctx)
 | 
			
		||||
	// 	if err != nil {
 | 
			
		||||
	// 		t.Fatal(err)
 | 
			
		||||
	// 	}
 | 
			
		||||
	//
 | 
			
		||||
	// 	name := "helloworld"
 | 
			
		||||
	// 	bindings := []types.PortBinding{
 | 
			
		||||
	// 		types.PortBinding{
 | 
			
		||||
	// 			ServiceBindingPort:  80,
 | 
			
		||||
	// 			ContainerExposePort: 3000,
 | 
			
		||||
	// 		},
 | 
			
		||||
	// 		types.PortBinding{
 | 
			
		||||
	// 			ServiceBindingPort:  443,
 | 
			
		||||
	// 			ContainerExposePort: 3000,
 | 
			
		||||
	// 		},
 | 
			
		||||
	// 	}
 | 
			
		||||
	//
 | 
			
		||||
	// 	fn := types.Func{
 | 
			
		||||
	// 		Language: "node",
 | 
			
		||||
	// 		Source: `
 | 
			
		||||
	// module.exports = (ctx) => {
 | 
			
		||||
	// 	ctx.body = 'hello world'
 | 
			
		||||
	// }
 | 
			
		||||
	// `,
 | 
			
		||||
	// 	}
 | 
			
		||||
	// 	if err := cli.Deploy(ctx, fn, name, name, bindings); err != nil {
 | 
			
		||||
	// 		t.Fatal(err)
 | 
			
		||||
	// 	}
 | 
			
		||||
	//
 | 
			
		||||
	// 	time.Sleep(1 * time.Second)
 | 
			
		||||
	//
 | 
			
		||||
	// 	if err := cli.Destroy(ctx, name); err != nil {
 | 
			
		||||
	// 		t.Fatal(err)
 | 
			
		||||
	// 	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								deploy/k3s/constants.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								deploy/k3s/constants.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
package k3s
 | 
			
		||||
 | 
			
		||||
// ConfigMap is the key to function docker project source code in configmap
 | 
			
		||||
var ConfigMap = struct {
 | 
			
		||||
	AppMetaEnvName string
 | 
			
		||||
}{
 | 
			
		||||
	AppMetaEnvName: "APP_META",
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										102
									
								
								deploy/k3s/deployment.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								deploy/k3s/deployment.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
			
		||||
package k3s
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
	appsv1 "k8s.io/api/apps/v1"
 | 
			
		||||
	apiv1 "k8s.io/api/core/v1"
 | 
			
		||||
	v1 "k8s.io/api/core/v1"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func generateDeploymentSpec(
 | 
			
		||||
	name string,
 | 
			
		||||
	image string,
 | 
			
		||||
	bindPorts []types.PortBinding,
 | 
			
		||||
	replicas int32,
 | 
			
		||||
	selector map[string]string,
 | 
			
		||||
) *appsv1.Deployment {
 | 
			
		||||
	ports := []apiv1.ContainerPort{}
 | 
			
		||||
	for index, binding := range bindPorts {
 | 
			
		||||
		ports = append(ports, apiv1.ContainerPort{
 | 
			
		||||
			Name:          fmt.Sprintf("fx-container-%d", index),
 | 
			
		||||
			ContainerPort: binding.ContainerExposePort,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	container := apiv1.Container{
 | 
			
		||||
		Name:            "fx-placeholder-container-name",
 | 
			
		||||
		Image:           image,
 | 
			
		||||
		Ports:           ports,
 | 
			
		||||
		ImagePullPolicy: v1.PullIfNotPresent,
 | 
			
		||||
	}
 | 
			
		||||
	return &appsv1.Deployment{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Name: name,
 | 
			
		||||
		},
 | 
			
		||||
		Spec: appsv1.DeploymentSpec{
 | 
			
		||||
			Replicas: &replicas,
 | 
			
		||||
			Selector: &metav1.LabelSelector{
 | 
			
		||||
				MatchLabels: selector,
 | 
			
		||||
			},
 | 
			
		||||
			Template: apiv1.PodTemplateSpec{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
					Labels: selector,
 | 
			
		||||
				},
 | 
			
		||||
				Spec: apiv1.PodSpec{
 | 
			
		||||
					Containers: []apiv1.Container{container},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetDeployment get a deployment
 | 
			
		||||
func (k *K3S) GetDeployment(namespace string, name string) (*appsv1.Deployment, error) {
 | 
			
		||||
	return k.AppsV1().Deployments(namespace).Get(name, metav1.GetOptions{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateDeployment create a deployment
 | 
			
		||||
func (k *K3S) CreateDeployment(
 | 
			
		||||
	namespace string,
 | 
			
		||||
	name string,
 | 
			
		||||
	image string,
 | 
			
		||||
	ports []types.PortBinding,
 | 
			
		||||
	replicas int32,
 | 
			
		||||
	selector map[string]string,
 | 
			
		||||
) (*appsv1.Deployment, error) {
 | 
			
		||||
	deployment := generateDeploymentSpec(name, image, ports, replicas, selector)
 | 
			
		||||
	return k.AppsV1().Deployments(namespace).Create(deployment)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateDeployment update a deployment
 | 
			
		||||
func (k *K3S) UpdateDeployment(
 | 
			
		||||
	namespace string,
 | 
			
		||||
	name string,
 | 
			
		||||
	image string,
 | 
			
		||||
	ports []types.PortBinding,
 | 
			
		||||
	replicas int32,
 | 
			
		||||
	selector map[string]string,
 | 
			
		||||
) (*appsv1.Deployment, error) {
 | 
			
		||||
	deployment := generateDeploymentSpec(name, image, ports, replicas, selector)
 | 
			
		||||
	return k.AppsV1().Deployments(namespace).Update(deployment)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteDeployment delete a deployment
 | 
			
		||||
func (k *K3S) DeleteDeployment(namespace string, name string) error {
 | 
			
		||||
	return k.AppsV1().Deployments(namespace).Delete(name, &metav1.DeleteOptions{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateDeploymentWithInitContainer create a deployment which will wait InitContainer to do the image build before function container start
 | 
			
		||||
func (k *K3S) CreateDeploymentWithInitContainer(
 | 
			
		||||
	namespace string,
 | 
			
		||||
	name string,
 | 
			
		||||
	ports []types.PortBinding,
 | 
			
		||||
	replicas int32,
 | 
			
		||||
	selector map[string]string,
 | 
			
		||||
) (*appsv1.Deployment, error) {
 | 
			
		||||
	deployment := generateDeploymentSpec(name, name, ports, replicas, selector)
 | 
			
		||||
	updatedDeployment := injectInitContainer(name, deployment)
 | 
			
		||||
	return k.AppsV1().Deployments(namespace).Create(updatedDeployment)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										61
									
								
								deploy/k3s/deployment_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								deploy/k3s/deployment_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
package k3s
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestDeployment(t *testing.T) {
 | 
			
		||||
	namespace := "default"
 | 
			
		||||
	name := "fx-hello-world"
 | 
			
		||||
	image := "metrue/kube-hello"
 | 
			
		||||
	selector := map[string]string{
 | 
			
		||||
		"app": "fx-app",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	kubeconfig := os.Getenv("KUBECONFIG")
 | 
			
		||||
	if kubeconfig == "" {
 | 
			
		||||
		t.Skip("skip test since no KUBECONFIG given in environment variable")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	k8s, err := Create()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := k8s.GetDeployment(namespace, name); err == nil {
 | 
			
		||||
		t.Fatalf("should get not found error")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	replicas := int32(2)
 | 
			
		||||
	bindings := []types.PortBinding{
 | 
			
		||||
		types.PortBinding{
 | 
			
		||||
			ServiceBindingPort:  80,
 | 
			
		||||
			ContainerExposePort: 3000,
 | 
			
		||||
		},
 | 
			
		||||
		types.PortBinding{
 | 
			
		||||
			ServiceBindingPort:  443,
 | 
			
		||||
			ContainerExposePort: 3000,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	deployment, err := k8s.CreateDeployment(namespace, name, image, bindings, replicas, selector)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if deployment == nil {
 | 
			
		||||
		t.Fatalf("deploymetn should not be %v", nil)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if deployment.Name != name {
 | 
			
		||||
		t.Fatalf("should get %s but got %s", name, deployment.Name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if *deployment.Spec.Replicas != replicas {
 | 
			
		||||
		t.Fatalf("should get %v but got %v", replicas, deployment.Spec.Replicas)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := k8s.DeleteDeployment(namespace, name); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										70
									
								
								deploy/k3s/init_container.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								deploy/k3s/init_container.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,70 @@
 | 
			
		||||
package k3s
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	appsv1 "k8s.io/api/apps/v1"
 | 
			
		||||
	apiv1 "k8s.io/api/core/v1"
 | 
			
		||||
	v1 "k8s.io/api/core/v1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// This is docker image provided by fx/contrib/docker_packer
 | 
			
		||||
// it can build a Docker image with give Docker project source codes encoded with base64
 | 
			
		||||
// check the detail fx/contrib/docker_packer/main.go
 | 
			
		||||
const image = "metrue/fx-docker"
 | 
			
		||||
 | 
			
		||||
func injectInitContainer(name string, deployment *appsv1.Deployment) *appsv1.Deployment {
 | 
			
		||||
	configMapHasToBeReady := true
 | 
			
		||||
	valueInConfigMapHasToBeReady := true
 | 
			
		||||
	initContainer := v1.Container{
 | 
			
		||||
		Name:            "fx-docker-build-c",
 | 
			
		||||
		Image:           image,
 | 
			
		||||
		ImagePullPolicy: v1.PullAlways,
 | 
			
		||||
		Command: []string{
 | 
			
		||||
			"/bin/sh",
 | 
			
		||||
			"-c",
 | 
			
		||||
			"/usr/bin/docker_packer $(APP_META) " + name,
 | 
			
		||||
		}, // Maybe it can be passed by Binary data from config map
 | 
			
		||||
		// Args:    []string{"${APP_META}"}, // function source codes and name
 | 
			
		||||
		VolumeMounts: []v1.VolumeMount{
 | 
			
		||||
			v1.VolumeMount{
 | 
			
		||||
				Name:      "dockersock",
 | 
			
		||||
				MountPath: "/var/run/docker.sock",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		Env: []v1.EnvVar{
 | 
			
		||||
			v1.EnvVar{
 | 
			
		||||
				Name: ConfigMap.AppMetaEnvName,
 | 
			
		||||
				ValueFrom: &v1.EnvVarSource{
 | 
			
		||||
					ConfigMapKeyRef: &v1.ConfigMapKeySelector{
 | 
			
		||||
						LocalObjectReference: v1.LocalObjectReference{Name: name},
 | 
			
		||||
						Key:                  ConfigMap.AppMetaEnvName,
 | 
			
		||||
						Optional:             &valueInConfigMapHasToBeReady,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		EnvFrom: []v1.EnvFromSource{
 | 
			
		||||
			v1.EnvFromSource{
 | 
			
		||||
				ConfigMapRef: &v1.ConfigMapEnvSource{
 | 
			
		||||
					LocalObjectReference: v1.LocalObjectReference{
 | 
			
		||||
						Name: name,
 | 
			
		||||
					},
 | 
			
		||||
					Optional: &configMapHasToBeReady,
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	volumes := []v1.Volume{
 | 
			
		||||
		v1.Volume{
 | 
			
		||||
			Name: "dockersock",
 | 
			
		||||
			VolumeSource: v1.VolumeSource{
 | 
			
		||||
				HostPath: &v1.HostPathVolumeSource{
 | 
			
		||||
					Path: "/var/run/docker.sock",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	deployment.Spec.Template.Spec.InitContainers = []apiv1.Container{initContainer}
 | 
			
		||||
	deployment.Spec.Template.Spec.Volumes = volumes
 | 
			
		||||
	return deployment
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										131
									
								
								deploy/k3s/k3s.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								deploy/k3s/k3s.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,131 @@
 | 
			
		||||
package k3s
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/metrue/fx/deploy"
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
	"k8s.io/client-go/kubernetes"
 | 
			
		||||
	"k8s.io/client-go/tools/clientcmd"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// K3S client
 | 
			
		||||
type K3S struct {
 | 
			
		||||
	*kubernetes.Clientset
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const namespace = "default"
 | 
			
		||||
 | 
			
		||||
// Create a k8s cluster client
 | 
			
		||||
func Create() (*K3S, error) {
 | 
			
		||||
	config, err := clientcmd.BuildConfigFromKubeconfigGetter("", clientcmd.NewDefaultClientConfigLoadingRules().Load)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	clientset, err := kubernetes.NewForConfig(config)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return &K3S{clientset}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Deploy a image to be a service
 | 
			
		||||
func (k *K3S) Deploy(
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
	fn types.Func,
 | 
			
		||||
	name string,
 | 
			
		||||
	image string,
 | 
			
		||||
	ports []types.PortBinding,
 | 
			
		||||
) error {
 | 
			
		||||
	selector := map[string]string{
 | 
			
		||||
		"app": "fx-app-" + name,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const replicas = int32(3)
 | 
			
		||||
	if _, err := k.GetDeployment(namespace, name); err != nil {
 | 
			
		||||
		// TODO enable passing replica from fx CLI
 | 
			
		||||
		if _, err := k.CreateDeployment(
 | 
			
		||||
			namespace,
 | 
			
		||||
			name,
 | 
			
		||||
			image,
 | 
			
		||||
			ports,
 | 
			
		||||
			replicas,
 | 
			
		||||
			selector,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if _, err := k.UpdateDeployment(
 | 
			
		||||
			namespace,
 | 
			
		||||
			name,
 | 
			
		||||
			image,
 | 
			
		||||
			ports,
 | 
			
		||||
			replicas,
 | 
			
		||||
			selector,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO fx should be able to know what's the target Kubernetes service platform
 | 
			
		||||
	// it's going to deploy to
 | 
			
		||||
	typ := "LoadBalancer"
 | 
			
		||||
	if os.Getenv("SERVICE_TYPE") != "" {
 | 
			
		||||
		typ = os.Getenv("SERVICE_TYPE")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := k.GetService(namespace, name); err != nil {
 | 
			
		||||
		if _, err := k.CreateService(
 | 
			
		||||
			namespace,
 | 
			
		||||
			name,
 | 
			
		||||
			typ,
 | 
			
		||||
			ports,
 | 
			
		||||
			selector,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if _, err := k.UpdateService(
 | 
			
		||||
			namespace,
 | 
			
		||||
			name,
 | 
			
		||||
			typ,
 | 
			
		||||
			ports,
 | 
			
		||||
			selector,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Update a service
 | 
			
		||||
func (k *K3S) Update(ctx context.Context, name string) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Destroy a service
 | 
			
		||||
func (k *K3S) Destroy(ctx context.Context, name string) error {
 | 
			
		||||
	if err := k.DeleteService(namespace, name); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := k.DeleteDeployment(namespace, name); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetStatus get status of a service
 | 
			
		||||
func (k *K3S) GetStatus(ctx context.Context, name string) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// List services
 | 
			
		||||
func (k *K3S) List(ctx context.Context, name string) ([]types.Service, error) {
 | 
			
		||||
	return []types.Service{}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	_ deploy.Deployer = &K3S{}
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										87
									
								
								deploy/k3s/service.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								deploy/k3s/service.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
			
		||||
package k3s
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
	apiv1 "k8s.io/api/core/v1"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	intstr "k8s.io/apimachinery/pkg/util/intstr"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func generateServiceSpec(
 | 
			
		||||
	namespace string,
 | 
			
		||||
	name string,
 | 
			
		||||
	typ string,
 | 
			
		||||
	bindings []types.PortBinding,
 | 
			
		||||
	selector map[string]string,
 | 
			
		||||
) *apiv1.Service {
 | 
			
		||||
	servicePorts := []apiv1.ServicePort{}
 | 
			
		||||
	for index, binding := range bindings {
 | 
			
		||||
		servicePorts = append(servicePorts, apiv1.ServicePort{
 | 
			
		||||
			Name:       "port-" + strconv.Itoa(index),
 | 
			
		||||
			Protocol:   apiv1.ProtocolTCP,
 | 
			
		||||
			Port:       binding.ServiceBindingPort,
 | 
			
		||||
			TargetPort: intstr.FromInt(int(binding.ContainerExposePort)),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &apiv1.Service{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Name:        name,
 | 
			
		||||
			ClusterName: namespace,
 | 
			
		||||
		},
 | 
			
		||||
		Spec: apiv1.ServiceSpec{
 | 
			
		||||
			Ports:    servicePorts,
 | 
			
		||||
			Type:     apiv1.ServiceType(typ),
 | 
			
		||||
			Selector: selector,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateService create a service
 | 
			
		||||
func (k *K3S) CreateService(
 | 
			
		||||
	namespace string,
 | 
			
		||||
	name string,
 | 
			
		||||
	typ string,
 | 
			
		||||
	bindings []types.PortBinding,
 | 
			
		||||
	selector map[string]string,
 | 
			
		||||
) (*apiv1.Service, error) {
 | 
			
		||||
	service := generateServiceSpec(namespace, name, typ, bindings, selector)
 | 
			
		||||
	createdService, err := k.CoreV1().Services(namespace).Create(service)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return createdService, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateService update a service
 | 
			
		||||
// TODO this method is not perfect yet, should refactor later
 | 
			
		||||
func (k *K3S) UpdateService(
 | 
			
		||||
	namespace string,
 | 
			
		||||
	name string,
 | 
			
		||||
	typ string,
 | 
			
		||||
	bindings []types.PortBinding,
 | 
			
		||||
	selector map[string]string,
 | 
			
		||||
) (*apiv1.Service, error) {
 | 
			
		||||
	svc, err := k.GetService(namespace, name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	svc.Spec.Selector = selector
 | 
			
		||||
	svc.Spec.Type = apiv1.ServiceType(typ)
 | 
			
		||||
	return k.CoreV1().Services(namespace).Update(svc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteService a service
 | 
			
		||||
func (k *K3S) DeleteService(namespace string, name string) error {
 | 
			
		||||
	// TODO figure out the elegant way to delete a service
 | 
			
		||||
	options := &metav1.DeleteOptions{}
 | 
			
		||||
	return k.CoreV1().Services(namespace).Delete(name, options)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetService get a service
 | 
			
		||||
func (k *K3S) GetService(namespace string, name string) (*apiv1.Service, error) {
 | 
			
		||||
	return k.CoreV1().Services(namespace).Get(name, metav1.GetOptions{})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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",
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										102
									
								
								deploy/kubernetes/deployment.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								deploy/kubernetes/deployment.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
			
		||||
package kubernetes
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
	appsv1 "k8s.io/api/apps/v1"
 | 
			
		||||
	apiv1 "k8s.io/api/core/v1"
 | 
			
		||||
	v1 "k8s.io/api/core/v1"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func generateDeploymentSpec(
 | 
			
		||||
	name string,
 | 
			
		||||
	image string,
 | 
			
		||||
	bindPorts []types.PortBinding,
 | 
			
		||||
	replicas int32,
 | 
			
		||||
	selector map[string]string,
 | 
			
		||||
) *appsv1.Deployment {
 | 
			
		||||
	ports := []apiv1.ContainerPort{}
 | 
			
		||||
	for index, binding := range bindPorts {
 | 
			
		||||
		ports = append(ports, apiv1.ContainerPort{
 | 
			
		||||
			Name:          fmt.Sprintf("fx-container-%d", index),
 | 
			
		||||
			ContainerPort: binding.ContainerExposePort,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	container := apiv1.Container{
 | 
			
		||||
		Name:            "fx-placeholder-container-name",
 | 
			
		||||
		Image:           image,
 | 
			
		||||
		Ports:           ports,
 | 
			
		||||
		ImagePullPolicy: v1.PullIfNotPresent,
 | 
			
		||||
	}
 | 
			
		||||
	return &appsv1.Deployment{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Name: name,
 | 
			
		||||
		},
 | 
			
		||||
		Spec: appsv1.DeploymentSpec{
 | 
			
		||||
			Replicas: &replicas,
 | 
			
		||||
			Selector: &metav1.LabelSelector{
 | 
			
		||||
				MatchLabels: selector,
 | 
			
		||||
			},
 | 
			
		||||
			Template: apiv1.PodTemplateSpec{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
					Labels: selector,
 | 
			
		||||
				},
 | 
			
		||||
				Spec: apiv1.PodSpec{
 | 
			
		||||
					Containers: []apiv1.Container{container},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetDeployment get a deployment
 | 
			
		||||
func (k *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,
 | 
			
		||||
	ports []types.PortBinding,
 | 
			
		||||
	replicas int32,
 | 
			
		||||
	selector map[string]string,
 | 
			
		||||
) (*appsv1.Deployment, error) {
 | 
			
		||||
	deployment := generateDeploymentSpec(name, image, ports, replicas, selector)
 | 
			
		||||
	return k.AppsV1().Deployments(namespace).Create(deployment)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateDeployment update a deployment
 | 
			
		||||
func (k *K8S) UpdateDeployment(
 | 
			
		||||
	namespace string,
 | 
			
		||||
	name string,
 | 
			
		||||
	image string,
 | 
			
		||||
	ports []types.PortBinding,
 | 
			
		||||
	replicas int32,
 | 
			
		||||
	selector map[string]string,
 | 
			
		||||
) (*appsv1.Deployment, error) {
 | 
			
		||||
	deployment := generateDeploymentSpec(name, image, ports, replicas, selector)
 | 
			
		||||
	return k.AppsV1().Deployments(namespace).Update(deployment)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteDeployment delete a deployment
 | 
			
		||||
func (k *K8S) DeleteDeployment(namespace string, name string) error {
 | 
			
		||||
	return k.AppsV1().Deployments(namespace).Delete(name, &metav1.DeleteOptions{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateDeploymentWithInitContainer create a deployment which will wait InitContainer to do the image build before function container start
 | 
			
		||||
func (k *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)
 | 
			
		||||
	return k.AppsV1().Deployments(namespace).Create(updatedDeployment)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										61
									
								
								deploy/kubernetes/deployment_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								deploy/kubernetes/deployment_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
			
		||||
package kubernetes
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestDeployment(t *testing.T) {
 | 
			
		||||
	namespace := "default"
 | 
			
		||||
	name := "fx-hello-world"
 | 
			
		||||
	image := "metrue/kube-hello"
 | 
			
		||||
	selector := map[string]string{
 | 
			
		||||
		"app": "fx-app",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	kubeconfig := os.Getenv("KUBECONFIG")
 | 
			
		||||
	if kubeconfig == "" {
 | 
			
		||||
		t.Skip("skip test since no KUBECONFIG given in environment variable")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	k8s, err := Create()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := k8s.GetDeployment(namespace, name); err == nil {
 | 
			
		||||
		t.Fatalf("should get not found error")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	replicas := int32(2)
 | 
			
		||||
	bindings := []types.PortBinding{
 | 
			
		||||
		types.PortBinding{
 | 
			
		||||
			ServiceBindingPort:  80,
 | 
			
		||||
			ContainerExposePort: 3000,
 | 
			
		||||
		},
 | 
			
		||||
		types.PortBinding{
 | 
			
		||||
			ServiceBindingPort:  443,
 | 
			
		||||
			ContainerExposePort: 3000,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	deployment, err := k8s.CreateDeployment(namespace, name, image, bindings, replicas, selector)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if deployment == nil {
 | 
			
		||||
		t.Fatalf("deploymetn should not be %v", nil)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if deployment.Name != name {
 | 
			
		||||
		t.Fatalf("should get %s but got %s", name, deployment.Name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if *deployment.Spec.Replicas != replicas {
 | 
			
		||||
		t.Fatalf("should get %v but got %v", replicas, deployment.Spec.Replicas)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := k8s.DeleteDeployment(namespace, name); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
}
 | 
			
		||||
@@ -2,12 +2,11 @@ package kubernetes
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
	runtime "github.com/metrue/fx/container_runtimes/docker/sdk"
 | 
			
		||||
	"github.com/metrue/fx/deploy"
 | 
			
		||||
	"github.com/metrue/fx/packer"
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
	"k8s.io/client-go/kubernetes"
 | 
			
		||||
	"k8s.io/client-go/tools/clientcmd"
 | 
			
		||||
)
 | 
			
		||||
@@ -17,14 +16,11 @@ type K8S struct {
 | 
			
		||||
	*kubernetes.Clientset
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const namespace = "default"
 | 
			
		||||
 | 
			
		||||
// Create a k8s cluster client
 | 
			
		||||
func Create() (*K8S, error) {
 | 
			
		||||
	kubeconfig := os.Getenv("KUBECONFIG")
 | 
			
		||||
	if kubeconfig == "" {
 | 
			
		||||
		return nil, fmt.Errorf("KUBECONFIG not given")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
 | 
			
		||||
	config, err := clientcmd.BuildConfigFromKubeconfigGetter("", clientcmd.NewDefaultClientConfigLoadingRules().Load)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
@@ -39,54 +35,78 @@ func Create() (*K8S, error) {
 | 
			
		||||
// Deploy a image to be a service
 | 
			
		||||
func (k *K8S) Deploy(
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
	workdir string,
 | 
			
		||||
	fn types.Func,
 | 
			
		||||
	name string,
 | 
			
		||||
	ports []int32,
 | 
			
		||||
	image string,
 | 
			
		||||
	ports []types.PortBinding,
 | 
			
		||||
) error {
 | 
			
		||||
	namespace := "default"
 | 
			
		||||
 | 
			
		||||
	dockerClient, err := runtime.CreateClient(ctx)
 | 
			
		||||
	// put source code of function docker project into k8s config map
 | 
			
		||||
	tree, err := packer.PackIntoK8SConfigMapFile(fn)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := dockerClient.BuildImage(ctx, workdir, name); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	image, err := dockerClient.PushImage(ctx, name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
	data := map[string]string{}
 | 
			
		||||
	data[ConfigMap.AppMetaEnvName] = tree
 | 
			
		||||
	if _, err := k.CreateOrUpdateConfigMap(namespace, name, data); err != nil {
 | 
			
		||||
		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.
 | 
			
		||||
	labels := map[string]string{
 | 
			
		||||
		"fx-app": "fx-app-" + uuid.New().String(),
 | 
			
		||||
	selector := map[string]string{
 | 
			
		||||
		"app": "fx-app-" + name,
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := k.CreatePod(
 | 
			
		||||
		namespace,
 | 
			
		||||
		name,
 | 
			
		||||
		image,
 | 
			
		||||
		labels,
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
 | 
			
		||||
	const replicas = int32(3)
 | 
			
		||||
	if _, err := k.GetDeployment(namespace, name); err != nil {
 | 
			
		||||
		// TODO enable passing replica from fx CLI
 | 
			
		||||
		if _, err := k.CreateDeploymentWithInitContainer(
 | 
			
		||||
			namespace,
 | 
			
		||||
			name,
 | 
			
		||||
			ports,
 | 
			
		||||
			replicas,
 | 
			
		||||
			selector,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if _, err := k.UpdateDeployment(
 | 
			
		||||
			namespace,
 | 
			
		||||
			name,
 | 
			
		||||
			name,
 | 
			
		||||
			ports,
 | 
			
		||||
			replicas,
 | 
			
		||||
			selector,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO fx should be able to know what's the target Kubernetes service platform
 | 
			
		||||
	// it's going to deploy to
 | 
			
		||||
	const isOnPublicCloud = true
 | 
			
		||||
	typ := "LoadBalancer"
 | 
			
		||||
	if !isOnPublicCloud {
 | 
			
		||||
		typ = "NodePort"
 | 
			
		||||
	if os.Getenv("SERVICE_TYPE") != "" {
 | 
			
		||||
		typ = os.Getenv("SERVICE_TYPE")
 | 
			
		||||
	}
 | 
			
		||||
	if _, err := k.CreateService(
 | 
			
		||||
		namespace,
 | 
			
		||||
		name,
 | 
			
		||||
		typ,
 | 
			
		||||
		ports,
 | 
			
		||||
		labels,
 | 
			
		||||
	); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
 | 
			
		||||
	if _, err := k.GetService(namespace, name); err != nil {
 | 
			
		||||
		if _, err := k.CreateService(
 | 
			
		||||
			namespace,
 | 
			
		||||
			name,
 | 
			
		||||
			typ,
 | 
			
		||||
			ports,
 | 
			
		||||
			selector,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		if _, err := k.UpdateService(
 | 
			
		||||
			namespace,
 | 
			
		||||
			name,
 | 
			
		||||
			typ,
 | 
			
		||||
			ports,
 | 
			
		||||
			selector,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -98,11 +118,10 @@ func (k *K8S) Update(ctx context.Context, name string) error {
 | 
			
		||||
 | 
			
		||||
// Destroy a service
 | 
			
		||||
func (k *K8S) Destroy(ctx context.Context, name string) error {
 | 
			
		||||
	const namespace = "default"
 | 
			
		||||
	if err := k.DeleteService(namespace, name); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := k.DeletePod(namespace, name); err != nil {
 | 
			
		||||
	if err := k.DeleteDeployment(namespace, name); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
@@ -113,6 +132,11 @@ func (k *K8S) GetStatus(ctx context.Context, name string) error {
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// List services
 | 
			
		||||
func (k *K8S) List(ctx context.Context, name string) ([]types.Service, error) {
 | 
			
		||||
	return []types.Service{}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	_ deploy.Deployer = &K8S{}
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -4,23 +4,43 @@ import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestK8SRunner(t *testing.T) {
 | 
			
		||||
	workdir := "./fixture"
 | 
			
		||||
	name := "hello"
 | 
			
		||||
	ports := []int32{32300}
 | 
			
		||||
func TestK8SDeployer(t *testing.T) {
 | 
			
		||||
	name := "hellohello"
 | 
			
		||||
	bindings := []types.PortBinding{
 | 
			
		||||
		types.PortBinding{
 | 
			
		||||
			ServiceBindingPort:  80,
 | 
			
		||||
			ContainerExposePort: 3000,
 | 
			
		||||
		},
 | 
			
		||||
		types.PortBinding{
 | 
			
		||||
			ServiceBindingPort:  443,
 | 
			
		||||
			ContainerExposePort: 3000,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	kubeconfig := os.Getenv("KUBECONFIG")
 | 
			
		||||
	if kubeconfig == "" {
 | 
			
		||||
		t.Skip("skip test since no KUBECONFIG given in environment variable")
 | 
			
		||||
	username := os.Getenv("DOCKER_USERNAME")
 | 
			
		||||
	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()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fn := types.Func{
 | 
			
		||||
		Language: "node",
 | 
			
		||||
		Source: `
 | 
			
		||||
module.exports = (ctx) => {
 | 
			
		||||
	ctx.body = 'hello world'
 | 
			
		||||
}
 | 
			
		||||
`,
 | 
			
		||||
	}
 | 
			
		||||
	ctx := context.Background()
 | 
			
		||||
	if err := k8s.Deploy(ctx, workdir, name, ports); err != nil {
 | 
			
		||||
	if err := k8s.Deploy(ctx, fn, name, name, bindings); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,56 +1,53 @@
 | 
			
		||||
package kubernetes
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/metrue/fx/constants"
 | 
			
		||||
	v1 "k8s.io/api/core/v1"
 | 
			
		||||
	"strconv"
 | 
			
		||||
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
	apiv1 "k8s.io/api/core/v1"
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
	intstr "k8s.io/apimachinery/pkg/util/intstr"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func generateServiceSpec(
 | 
			
		||||
	namespace string,
 | 
			
		||||
	name string,
 | 
			
		||||
	typ string,
 | 
			
		||||
	bindings []types.PortBinding,
 | 
			
		||||
	selector map[string]string,
 | 
			
		||||
) *apiv1.Service {
 | 
			
		||||
	servicePorts := []apiv1.ServicePort{}
 | 
			
		||||
	for index, binding := range bindings {
 | 
			
		||||
		servicePorts = append(servicePorts, apiv1.ServicePort{
 | 
			
		||||
			Name:       "port-" + strconv.Itoa(index),
 | 
			
		||||
			Protocol:   apiv1.ProtocolTCP,
 | 
			
		||||
			Port:       binding.ServiceBindingPort,
 | 
			
		||||
			TargetPort: intstr.FromInt(int(binding.ContainerExposePort)),
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &apiv1.Service{
 | 
			
		||||
		ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
			Name:        name,
 | 
			
		||||
			ClusterName: namespace,
 | 
			
		||||
		},
 | 
			
		||||
		Spec: apiv1.ServiceSpec{
 | 
			
		||||
			Ports:    servicePorts,
 | 
			
		||||
			Type:     apiv1.ServiceType(typ),
 | 
			
		||||
			Selector: selector,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateService create a service
 | 
			
		||||
func (k *K8S) CreateService(
 | 
			
		||||
	namespace string,
 | 
			
		||||
	name string,
 | 
			
		||||
	typ string,
 | 
			
		||||
	ports []int32,
 | 
			
		||||
	podsLabels map[string]string,
 | 
			
		||||
) (*v1.Service, error) {
 | 
			
		||||
	servicePorts := []v1.ServicePort{
 | 
			
		||||
		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,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bindings []types.PortBinding,
 | 
			
		||||
	selector map[string]string,
 | 
			
		||||
) (*apiv1.Service, error) {
 | 
			
		||||
	service := generateServiceSpec(namespace, name, typ, bindings, selector)
 | 
			
		||||
	createdService, err := k.CoreV1().Services(namespace).Create(service)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
@@ -59,9 +56,32 @@ func (k *K8S) CreateService(
 | 
			
		||||
	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,
 | 
			
		||||
	bindings []types.PortBinding,
 | 
			
		||||
	selector map[string]string,
 | 
			
		||||
) (*apiv1.Service, error) {
 | 
			
		||||
	svc, err := k.GetService(namespace, name)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	svc.Spec.Selector = selector
 | 
			
		||||
	svc.Spec.Type = apiv1.ServiceType(typ)
 | 
			
		||||
	return k.CoreV1().Services(namespace).Update(svc)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteService a service
 | 
			
		||||
func (k *K8S) DeleteService(namespace string, name string) error {
 | 
			
		||||
	// TODO figure out the elegant way to delete a service
 | 
			
		||||
	options := &metav1.DeleteOptions{}
 | 
			
		||||
	return k.CoreV1().Services(namespace).Delete(name, options)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetService get a service
 | 
			
		||||
func (k *K8S) GetService(namespace string, name string) (*apiv1.Service, error) {
 | 
			
		||||
	return k.CoreV1().Services(namespace).Get(name, metav1.GetOptions{})
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,14 +2,26 @@ package kubernetes
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestK8S(t *testing.T) {
 | 
			
		||||
	namespace := "default"
 | 
			
		||||
	// TODO image is ready on hub.docker.com
 | 
			
		||||
	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"
 | 
			
		||||
	kubeconfig := os.Getenv("KUBECONFIG")
 | 
			
		||||
	if kubeconfig == "" {
 | 
			
		||||
@@ -49,13 +61,37 @@ func TestK8S(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	serviceName := podName + "-svc"
 | 
			
		||||
	svc, err := k8s.CreateService(namespace, serviceName, "NodePort", ports, labels)
 | 
			
		||||
	if _, err := k8s.GetService(namespace, serviceName); err == nil {
 | 
			
		||||
		t.Fatalf("should get no service name %s", serviceName)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	svc, err := k8s.CreateService(namespace, serviceName, "NodePort", bindings, labels)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if svc.Name != serviceName {
 | 
			
		||||
		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", bindings, 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
 | 
			
		||||
	if err := k8s.DeleteService(namespace, serviceName); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								docs/fx-workflow.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/fx-workflow.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 50 KiB  | 
							
								
								
									
										46
									
								
								docs/ubuntu.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								docs/ubuntu.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
# fx on Ubuntu
 | 
			
		||||
 | 
			
		||||
> The guide is verified on Amazon Lightsail ubuntu 18.08 instance
 | 
			
		||||
 | 
			
		||||
## Install Docker
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
apt-get remove -y docker docker-engine docker.io containerd runc
 | 
			
		||||
apt-get update -y
 | 
			
		||||
apt-get install -y apt-transport-https ca-certificates curl software-properties-common lsb-core
 | 
			
		||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
 | 
			
		||||
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
 | 
			
		||||
apt-get update -y
 | 
			
		||||
apt-get install -y docker-ce
 | 
			
		||||
docker run hello-world
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Install fx
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
$ curl -o- https://raw.githubusercontent.com/metrue/fx/master/scripts/install.sh | sudo bash
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Deploy a function onto localhost
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
$ cat func.js
 | 
			
		||||
 | 
			
		||||
module.exports = (ctx) => {
 | 
			
		||||
  ctx.body = 'hello world'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
$ fx up -n test -p 2000 func.js
 | 
			
		||||
$ curl 127.0.0.1:2000
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
##  Deploy a function onto remote host
 | 
			
		||||
 | 
			
		||||
* make sure your instance can be ssh login
 | 
			
		||||
* make sure your instance accept port 8866
 | 
			
		||||
 | 
			
		||||
then you can deploy function to remote host
 | 
			
		||||
 | 
			
		||||
```shell
 | 
			
		||||
DOCKER_REMOTE_HOST_ADDR=<your host> DOCKER_REMOTE_HOST_USER=<your user> DOCKER_REMOTE_HOST_PASSWORD=<your password> fx up -p 2000 test/functions/func.js
 | 
			
		||||
```
 | 
			
		||||
@@ -2,7 +2,6 @@ package doctor
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/apex/log"
 | 
			
		||||
	"github.com/metrue/fx/config"
 | 
			
		||||
	"github.com/metrue/fx/constants"
 | 
			
		||||
	"github.com/metrue/fx/pkg/command"
 | 
			
		||||
	"github.com/metrue/go-ssh-client"
 | 
			
		||||
@@ -10,16 +9,23 @@ import (
 | 
			
		||||
 | 
			
		||||
// Doctor health checking
 | 
			
		||||
type Doctor struct {
 | 
			
		||||
	host config.Host
 | 
			
		||||
	host string
 | 
			
		||||
 | 
			
		||||
	sshClient ssh.Client
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isLocal(host string) bool {
 | 
			
		||||
	if host == "" {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return host == "127.0.0.1" || host == "localhost" || host == "0.0.0.0"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New a doctor
 | 
			
		||||
func New(host config.Host) *Doctor {
 | 
			
		||||
	sshClient := ssh.New(host.Host).
 | 
			
		||||
		WithUser(host.User).
 | 
			
		||||
		WithPassword(host.Password)
 | 
			
		||||
func New(host, user, password string) *Doctor {
 | 
			
		||||
	sshClient := ssh.New(host).
 | 
			
		||||
		WithUser(user).
 | 
			
		||||
		WithPassword(password)
 | 
			
		||||
	return &Doctor{
 | 
			
		||||
		host:      host,
 | 
			
		||||
		sshClient: sshClient,
 | 
			
		||||
@@ -32,7 +38,7 @@ func (d *Doctor) Start() error {
 | 
			
		||||
	checkAgent := "docker inspect " + constants.AgentContainerName
 | 
			
		||||
 | 
			
		||||
	cmds := []*command.Command{}
 | 
			
		||||
	if d.host.IsRemote() {
 | 
			
		||||
	if !isLocal(d.host) {
 | 
			
		||||
		cmds = append(cmds,
 | 
			
		||||
			command.New("check if dockerd is running", checkDocker, command.NewRemoteRunner(d.sshClient)),
 | 
			
		||||
			command.New("check if fx agent is running", checkAgent, command.NewRemoteRunner(d.sshClient)),
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										276
									
								
								fx.go
									
									
									
									
									
								
							
							
						
						
									
										276
									
								
								fx.go
									
									
									
									
									
								
							@@ -1,25 +1,52 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path"
 | 
			
		||||
	"regexp"
 | 
			
		||||
 | 
			
		||||
	"github.com/apex/log"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
	"github.com/metrue/fx/config"
 | 
			
		||||
	"github.com/metrue/fx/context"
 | 
			
		||||
	"github.com/metrue/fx/handlers"
 | 
			
		||||
	"github.com/metrue/fx/middlewares"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var cfg *config.Config
 | 
			
		||||
const version = "0.8.3"
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	configDir := path.Join(os.Getenv("HOME"), ".fx")
 | 
			
		||||
	cfg := config.New(configDir)
 | 
			
		||||
	go checkForUpdate()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
	if err := cfg.Init(); err != nil {
 | 
			
		||||
		log.Fatalf("Init config failed %s", err)
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -27,68 +54,92 @@ func main() {
 | 
			
		||||
	app := cli.NewApp()
 | 
			
		||||
	app.Name = "fx"
 | 
			
		||||
	app.Usage = "makes function as a service"
 | 
			
		||||
	app.Version = "0.6.2"
 | 
			
		||||
	app.Version = version
 | 
			
		||||
 | 
			
		||||
	app.Commands = []cli.Command{
 | 
			
		||||
		{
 | 
			
		||||
			Name:  "infra",
 | 
			
		||||
			Usage: "manage infrastructure of fx",
 | 
			
		||||
			Subcommands: []cli.Command{
 | 
			
		||||
				{
 | 
			
		||||
					Name:  "add",
 | 
			
		||||
					Usage: "add a new machine",
 | 
			
		||||
					Flags: []cli.Flag{
 | 
			
		||||
						cli.StringFlag{
 | 
			
		||||
							Name:  "name, N",
 | 
			
		||||
							Usage: "a alias name for this machine",
 | 
			
		||||
						},
 | 
			
		||||
						cli.StringFlag{
 | 
			
		||||
							Name:  "host, H",
 | 
			
		||||
							Usage: "host name or IP address of a machine",
 | 
			
		||||
						},
 | 
			
		||||
						cli.StringFlag{
 | 
			
		||||
							Name:  "user, U",
 | 
			
		||||
							Usage: "user name required for SSH login",
 | 
			
		||||
						},
 | 
			
		||||
						cli.StringFlag{
 | 
			
		||||
							Name:  "password, P",
 | 
			
		||||
							Usage: "password required for SSH login",
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					Action: func(c *cli.Context) error {
 | 
			
		||||
						return handlers.AddHost(cfg)(c)
 | 
			
		||||
					},
 | 
			
		||||
			Name:  "init",
 | 
			
		||||
			Usage: "start fx agent on host",
 | 
			
		||||
			Action: func(c *cli.Context) error {
 | 
			
		||||
				return handlers.Init()(context.FromCliContext(c))
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:      "up",
 | 
			
		||||
			Usage:     "deploy a function",
 | 
			
		||||
			ArgsUsage: "[func.go func.js func.py func.rb ...]",
 | 
			
		||||
			Flags: []cli.Flag{
 | 
			
		||||
				cli.StringFlag{
 | 
			
		||||
					Name:  "name, n",
 | 
			
		||||
					Value: uuid.New().String(),
 | 
			
		||||
					Usage: "service name",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:  "remove",
 | 
			
		||||
					Usage: "remove an existing machine",
 | 
			
		||||
					Action: func(c *cli.Context) error {
 | 
			
		||||
						return handlers.RemoveHost(cfg)(c)
 | 
			
		||||
					},
 | 
			
		||||
				cli.IntFlag{
 | 
			
		||||
					Name:  "port, p",
 | 
			
		||||
					Usage: "port number",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:    "list",
 | 
			
		||||
					Aliases: []string{"ls"},
 | 
			
		||||
					Usage:   "list machines",
 | 
			
		||||
					Action: func(c *cli.Context) error {
 | 
			
		||||
						return handlers.ListHosts(cfg)(c)
 | 
			
		||||
					},
 | 
			
		||||
				cli.BoolFlag{
 | 
			
		||||
					Name:  "healthcheck, hc",
 | 
			
		||||
					Usage: "do a health check after service up",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:  "activate",
 | 
			
		||||
					Usage: "enable a machine be a host of fx infrastructure",
 | 
			
		||||
					Action: func(c *cli.Context) error {
 | 
			
		||||
						return handlers.Activate(cfg)(c)
 | 
			
		||||
					},
 | 
			
		||||
				cli.BoolFlag{
 | 
			
		||||
					Name:  "force, f",
 | 
			
		||||
					Usage: "force deploy a function or functions",
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
					Name:  "deactivate",
 | 
			
		||||
					Usage: "disable a machine be a host of fx infrastructure",
 | 
			
		||||
					Action: func(c *cli.Context) error {
 | 
			
		||||
						return handlers.Deactivate(cfg)(c)
 | 
			
		||||
					},
 | 
			
		||||
			},
 | 
			
		||||
			Action: func(c *cli.Context) error {
 | 
			
		||||
				ctx := context.FromCliContext(c)
 | 
			
		||||
				if err := ctx.Use(middlewares.Setup); err != nil {
 | 
			
		||||
					log.Fatalf("%v", err)
 | 
			
		||||
				}
 | 
			
		||||
				if err := ctx.Use(middlewares.Binding); err != nil {
 | 
			
		||||
					log.Fatalf("%v", err)
 | 
			
		||||
				}
 | 
			
		||||
				if err := ctx.Use(middlewares.Parse); err != nil {
 | 
			
		||||
					log.Fatalf("%v", err)
 | 
			
		||||
				}
 | 
			
		||||
				if err := ctx.Use(middlewares.Build); err != nil {
 | 
			
		||||
					log.Fatalf("%v", err)
 | 
			
		||||
				}
 | 
			
		||||
				return handlers.Up()(ctx)
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:      "down",
 | 
			
		||||
			Usage:     "destroy a service",
 | 
			
		||||
			ArgsUsage: "[service 1, service 2, ....]",
 | 
			
		||||
			Action: func(c *cli.Context) error {
 | 
			
		||||
				ctx := context.FromCliContext(c)
 | 
			
		||||
				if err := ctx.Use(middlewares.Setup); err != nil {
 | 
			
		||||
					log.Fatalf("%v", err)
 | 
			
		||||
				}
 | 
			
		||||
				return handlers.Down()(ctx)
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:    "list",
 | 
			
		||||
			Aliases: []string{"ls"},
 | 
			
		||||
			Usage:   "list deployed services",
 | 
			
		||||
			Action: func(c *cli.Context) error {
 | 
			
		||||
				ctx := context.FromCliContext(c)
 | 
			
		||||
				if err := ctx.Use(middlewares.Setup); err != nil {
 | 
			
		||||
					log.Fatalf("%v", err)
 | 
			
		||||
				}
 | 
			
		||||
				return handlers.List()(ctx)
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:  "call",
 | 
			
		||||
			Usage: "run a function instantly",
 | 
			
		||||
			Flags: []cli.Flag{
 | 
			
		||||
				cli.StringFlag{
 | 
			
		||||
					Name:  "host, H",
 | 
			
		||||
					Usage: "fx server host, default is localhost",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			Action: func(c *cli.Context) error {
 | 
			
		||||
				return handlers.Call()(context.FromCliContext(c))
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:  "image",
 | 
			
		||||
@@ -104,7 +155,11 @@ func main() {
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					Action: func(c *cli.Context) error {
 | 
			
		||||
						return handlers.BuildImage(cfg)(c)
 | 
			
		||||
						ctx := context.FromCliContext(c)
 | 
			
		||||
						if err := ctx.Use(middlewares.Setup); err != nil {
 | 
			
		||||
							log.Fatalf("%v", err)
 | 
			
		||||
						}
 | 
			
		||||
						return handlers.BuildImage()(ctx)
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
				{
 | 
			
		||||
@@ -117,7 +172,7 @@ func main() {
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
					Action: func(c *cli.Context) error {
 | 
			
		||||
						return handlers.ExportImage()(c)
 | 
			
		||||
						return handlers.ExportImage()(context.FromCliContext(c))
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
@@ -126,103 +181,12 @@ func main() {
 | 
			
		||||
			Name:  "doctor",
 | 
			
		||||
			Usage: "health check for fx",
 | 
			
		||||
			Action: func(c *cli.Context) error {
 | 
			
		||||
				return handlers.Doctor(cfg)(c)
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:      "up",
 | 
			
		||||
			Usage:     "deploy a function or a group of functions",
 | 
			
		||||
			ArgsUsage: "[func.go func.js func.py func.rb ...]",
 | 
			
		||||
			Flags: []cli.Flag{
 | 
			
		||||
				cli.StringFlag{
 | 
			
		||||
					Name:  "name, n",
 | 
			
		||||
					Value: uuid.New().String(),
 | 
			
		||||
					Usage: "service name",
 | 
			
		||||
				},
 | 
			
		||||
				cli.IntFlag{
 | 
			
		||||
					Name:  "port, p",
 | 
			
		||||
					Usage: "port number",
 | 
			
		||||
				},
 | 
			
		||||
				cli.BoolFlag{
 | 
			
		||||
					Name:  "healthcheck, hc",
 | 
			
		||||
					Usage: "do a health check after service up",
 | 
			
		||||
				},
 | 
			
		||||
				cli.BoolFlag{
 | 
			
		||||
					Name:  "force, f",
 | 
			
		||||
					Usage: "force deploy a function or functions",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			Action: func(c *cli.Context) error {
 | 
			
		||||
				return handlers.Up(cfg)(c)
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:      "deploy",
 | 
			
		||||
			Usage:     "deploy a function or a group of functions",
 | 
			
		||||
			ArgsUsage: "[func.go func.js func.py func.rb ...]",
 | 
			
		||||
			Flags: []cli.Flag{
 | 
			
		||||
				cli.StringFlag{
 | 
			
		||||
					Name:  "name, n",
 | 
			
		||||
					Value: uuid.New().String(),
 | 
			
		||||
					Usage: "service name",
 | 
			
		||||
				},
 | 
			
		||||
				cli.IntFlag{
 | 
			
		||||
					Name:  "port, p",
 | 
			
		||||
					Usage: "port number",
 | 
			
		||||
				},
 | 
			
		||||
				cli.BoolFlag{
 | 
			
		||||
					Name:  "healthcheck, hc",
 | 
			
		||||
					Usage: "do a health check after service up",
 | 
			
		||||
				},
 | 
			
		||||
				cli.BoolFlag{
 | 
			
		||||
					Name:  "force, f",
 | 
			
		||||
					Usage: "force deploy a function or functions",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			Action: func(c *cli.Context) error {
 | 
			
		||||
				return handlers.Deploy(cfg)(c)
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:      "down",
 | 
			
		||||
			Usage:     "destroy a service",
 | 
			
		||||
			ArgsUsage: "[service 1, service 2, ....]",
 | 
			
		||||
			Action: func(c *cli.Context) error {
 | 
			
		||||
				return handlers.Down(cfg)(c)
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:      "destroy",
 | 
			
		||||
			Usage:     "destroy a service",
 | 
			
		||||
			ArgsUsage: "[service 1, service 2, ....]",
 | 
			
		||||
			Action: func(c *cli.Context) error {
 | 
			
		||||
				return handlers.Destroy(cfg)(c)
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:    "list",
 | 
			
		||||
			Aliases: []string{"ls"},
 | 
			
		||||
			Usage:   "list deployed services",
 | 
			
		||||
			Action: func(c *cli.Context) error {
 | 
			
		||||
				return handlers.List(cfg)(c)
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:  "call",
 | 
			
		||||
			Usage: "run a function instantly",
 | 
			
		||||
			Flags: []cli.Flag{
 | 
			
		||||
				cli.StringFlag{
 | 
			
		||||
					Name:  "host, H",
 | 
			
		||||
					Usage: "fx server host, default is localhost",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			Action: func(c *cli.Context) error {
 | 
			
		||||
				return handlers.Call(cfg)(c)
 | 
			
		||||
				return handlers.Doctor()(context.FromCliContext(c))
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := app.Run(os.Args); err != nil {
 | 
			
		||||
		log.Fatalf("fx startup with fatal: %v", err)
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.mod
									
									
									
									
									
								
							@@ -6,6 +6,7 @@ require (
 | 
			
		||||
	github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
 | 
			
		||||
	github.com/Microsoft/go-winio v0.4.14 // indirect
 | 
			
		||||
	github.com/apex/log v1.1.1
 | 
			
		||||
	github.com/briandowns/spinner v1.7.0
 | 
			
		||||
	github.com/docker/distribution v2.7.1+incompatible // indirect
 | 
			
		||||
	github.com/docker/docker v0.0.0-20190313072916-46036c230805
 | 
			
		||||
	github.com/docker/go-connections v0.4.0
 | 
			
		||||
@@ -20,7 +21,7 @@ require (
 | 
			
		||||
	github.com/googleapis/gnostic v0.3.1 // indirect
 | 
			
		||||
	github.com/gorilla/mux v1.7.3 // indirect
 | 
			
		||||
	github.com/imdario/mergo v0.3.7 // indirect
 | 
			
		||||
	github.com/magiconair/properties v1.8.1 // indirect
 | 
			
		||||
	github.com/logrusorgru/aurora v0.0.0-20191017060258-dc85c304c434
 | 
			
		||||
	github.com/metrue/go-ssh-client v0.0.0-20190810064746-98a7a27048f3
 | 
			
		||||
	github.com/mholt/archiver v3.1.1+incompatible
 | 
			
		||||
	github.com/morikuni/aec v1.0.0 // indirect
 | 
			
		||||
@@ -30,8 +31,9 @@ require (
 | 
			
		||||
	github.com/pelletier/go-toml v1.4.0 // indirect
 | 
			
		||||
	github.com/pierrec/lz4 v0.0.0-20190222153722-062282ea0dcf // indirect
 | 
			
		||||
	github.com/pkg/errors v0.8.1
 | 
			
		||||
	github.com/schollz/progressbar/v2 v2.14.0
 | 
			
		||||
	github.com/spf13/jwalterweatherman v1.1.0 // indirect
 | 
			
		||||
	github.com/spf13/viper v1.4.0
 | 
			
		||||
	github.com/spf13/viper v1.5.0
 | 
			
		||||
	github.com/stretchr/testify v1.4.0
 | 
			
		||||
	github.com/ugorji/go v1.1.7 // indirect
 | 
			
		||||
	github.com/urfave/cli v1.22.1
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								go.sum
									
									
									
									
									
								
							@@ -29,6 +29,8 @@ github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN
 | 
			
		||||
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
 | 
			
		||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 | 
			
		||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 | 
			
		||||
github.com/briandowns/spinner v1.7.0 h1:aan1hBBOoscry2TXAkgtxkJiq7Se0+9pt+TUWaPrB4g=
 | 
			
		||||
github.com/briandowns/spinner v1.7.0/go.mod h1://Zf9tMcxfRUA36V23M6YGEAv+kECGfvpnLTnb8n4XQ=
 | 
			
		||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 | 
			
		||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 | 
			
		||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 | 
			
		||||
@@ -55,6 +57,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-units v0.3.3 h1:Xk8S3Xj5sLGlG5g67hJmYMmUgXv5N4PhkjJHHqrwnTk=
 | 
			
		||||
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/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=
 | 
			
		||||
@@ -63,6 +66,7 @@ github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdf
 | 
			
		||||
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
 | 
			
		||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
 | 
			
		||||
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
 | 
			
		||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
 | 
			
		||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
 | 
			
		||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 | 
			
		||||
@@ -173,11 +177,14 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
 | 
			
		||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 | 
			
		||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 | 
			
		||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 | 
			
		||||
github.com/logrusorgru/aurora v0.0.0-20191017060258-dc85c304c434 h1:im9kkmH0WWwxzegiv18gSUJbuXR9y028rXrWuPp6Jug=
 | 
			
		||||
github.com/logrusorgru/aurora v0.0.0-20191017060258-dc85c304c434/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
 | 
			
		||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 | 
			
		||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
 | 
			
		||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 | 
			
		||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 | 
			
		||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
 | 
			
		||||
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
 | 
			
		||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
 | 
			
		||||
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
 | 
			
		||||
@@ -190,6 +197,8 @@ github.com/metrue/go-ssh-client v0.0.0-20190810064746-98a7a27048f3/go.mod h1:ERH
 | 
			
		||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
 | 
			
		||||
github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU=
 | 
			
		||||
github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU=
 | 
			
		||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
 | 
			
		||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
 | 
			
		||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
 | 
			
		||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
 | 
			
		||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
 | 
			
		||||
@@ -250,6 +259,8 @@ github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNue
 | 
			
		||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
 | 
			
		||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
 | 
			
		||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 | 
			
		||||
github.com/schollz/progressbar/v2 v2.14.0 h1:vo7bdkI9E4/CIk9DnL5uVIaybLQiVtiCC2vO+u9j5IM=
 | 
			
		||||
github.com/schollz/progressbar/v2 v2.14.0/go.mod h1:6YZjqdthH6SCZKv2rqGryrxPtfmRB/DWZxSMfCXPyD8=
 | 
			
		||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
 | 
			
		||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
 | 
			
		||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 | 
			
		||||
@@ -278,6 +289,8 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
 | 
			
		||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
 | 
			
		||||
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
 | 
			
		||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
 | 
			
		||||
github.com/spf13/viper v1.5.0 h1:GpsTwfsQ27oS/Aha/6d1oD7tpKIqWnOA6tgOX9HHkt4=
 | 
			
		||||
github.com/spf13/viper v1.5.0/go.mod h1:AkYRkVJF8TkSG/xet6PzXX+l39KhhXa2pdqVSxnTcn4=
 | 
			
		||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
 | 
			
		||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
			
		||||
@@ -287,6 +300,8 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
 | 
			
		||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 | 
			
		||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
 | 
			
		||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 | 
			
		||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 | 
			
		||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
 | 
			
		||||
github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
 | 
			
		||||
github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0=
 | 
			
		||||
github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao=
 | 
			
		||||
@@ -422,6 +437,8 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
 | 
			
		||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 | 
			
		||||
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
 | 
			
		||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
 | 
			
		||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								hack/install_docker.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										13
									
								
								hack/install_docker.sh
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
set -e
 | 
			
		||||
# ++
 | 
			
		||||
# verified on Ubuntu 16.04 x64
 | 
			
		||||
# ++
 | 
			
		||||
apt-get remove -y docker docker-engine docker.io containerd runc
 | 
			
		||||
apt-get update -y
 | 
			
		||||
apt-get install -y apt-transport-https ca-certificates curl software-properties-common lsb-core
 | 
			
		||||
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
 | 
			
		||||
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
 | 
			
		||||
apt-get update -y
 | 
			
		||||
apt-get install -y docker-ce
 | 
			
		||||
docker run hello-world
 | 
			
		||||
@@ -5,25 +5,19 @@ import (
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"github.com/apex/log"
 | 
			
		||||
	"github.com/metrue/fx/config"
 | 
			
		||||
	"github.com/metrue/fx/constants"
 | 
			
		||||
	api "github.com/metrue/fx/container_runtimes/docker/http"
 | 
			
		||||
	"github.com/metrue/fx/context"
 | 
			
		||||
	"github.com/metrue/fx/packer"
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
	"github.com/metrue/fx/utils"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Call command handle
 | 
			
		||||
func Call(cfg config.Configer) HandleFunc {
 | 
			
		||||
	return func(ctx *cli.Context) error {
 | 
			
		||||
		params := strings.Join(ctx.Args()[1:], " ")
 | 
			
		||||
		hosts, err := cfg.ListActiveMachines()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatalf("list active machines failed: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
func Call() HandleFunc {
 | 
			
		||||
	return func(ctx *context.Context) error {
 | 
			
		||||
		cli := ctx.GetCliContext()
 | 
			
		||||
		_ = strings.Join(cli.Args()[1:], " ")
 | 
			
		||||
 | 
			
		||||
		file := ctx.Args().First()
 | 
			
		||||
		file := cli.Args().First()
 | 
			
		||||
		src, err := ioutil.ReadFile(file)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatalf("Read Source: %v", err)
 | 
			
		||||
@@ -32,21 +26,20 @@ func Call(cfg config.Configer) HandleFunc {
 | 
			
		||||
		log.Info("Read Source: \u2713")
 | 
			
		||||
 | 
			
		||||
		lang := utils.GetLangFromFileName(file)
 | 
			
		||||
		fn := types.ServiceFunctionSource{
 | 
			
		||||
		fn := types.Func{
 | 
			
		||||
			Language: lang,
 | 
			
		||||
			Source:   string(src),
 | 
			
		||||
		}
 | 
			
		||||
		project, err := packer.Pack(file, fn)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
		if _, err := packer.Pack(file, fn); err != nil {
 | 
			
		||||
			panic(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for name, host := range hosts {
 | 
			
		||||
			if err := api.MustCreate(host.Host, constants.AgentPort).
 | 
			
		||||
				Call(file, params, project); err != nil {
 | 
			
		||||
				log.Fatalf("call functions on machine %s with %v failed: %v", name, params, err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		// TODO not supported
 | 
			
		||||
		// if err := api.MustCreate(host.Host, constants.AgentPort).
 | 
			
		||||
		// 	Call(file, params, project); err != nil {
 | 
			
		||||
		// 	log.Fatalf("call functions on machine %s with %v failed: %v", name, params, err)
 | 
			
		||||
		// }
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,101 +0,0 @@
 | 
			
		||||
package handlers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/apex/log"
 | 
			
		||||
	"github.com/metrue/fx/config"
 | 
			
		||||
	"github.com/metrue/fx/constants"
 | 
			
		||||
	api "github.com/metrue/fx/container_runtimes/docker/http"
 | 
			
		||||
	"github.com/metrue/fx/deploy"
 | 
			
		||||
	dockerDeployer "github.com/metrue/fx/deploy/docker"
 | 
			
		||||
	k8sDeployer "github.com/metrue/fx/deploy/kubernetes"
 | 
			
		||||
	"github.com/metrue/fx/packer"
 | 
			
		||||
	"github.com/metrue/fx/utils"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Deploy deploy handle function
 | 
			
		||||
func Deploy(cfg config.Configer) HandleFunc {
 | 
			
		||||
	return func(ctx *cli.Context) (err error) {
 | 
			
		||||
		funcFile := ctx.Args().First()
 | 
			
		||||
		name := ctx.String("name")
 | 
			
		||||
		port := ctx.Int("port")
 | 
			
		||||
		force := ctx.Bool("force")
 | 
			
		||||
 | 
			
		||||
		defer func() {
 | 
			
		||||
			if r := recover(); r != nil {
 | 
			
		||||
				log.Fatalf("fatal error happened: %v", r)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Fatalf("deploy function %s (%s) failed: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
			log.Infof("function %s (%s) deployed successfully", name, funcFile)
 | 
			
		||||
		}()
 | 
			
		||||
 | 
			
		||||
		if port < PortRange.min || port > PortRange.max {
 | 
			
		||||
			return fmt.Errorf("invalid port number: %d, port number should in range of %d -  %d", port, PortRange.min, PortRange.max)
 | 
			
		||||
		}
 | 
			
		||||
		hosts, err := cfg.ListActiveMachines()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, "list active machines failed")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(hosts) == 0 {
 | 
			
		||||
			log.Warnf("no active machines")
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// try to stop service firt
 | 
			
		||||
		if force {
 | 
			
		||||
			for n, host := range hosts {
 | 
			
		||||
				if err := api.MustCreate(host.Host, constants.AgentPort).
 | 
			
		||||
					Stop(name); err != nil {
 | 
			
		||||
					log.Infof("stop function %s on machine %s failed: %v", name, n, err)
 | 
			
		||||
				} else {
 | 
			
		||||
					log.Infof("stop function %s on machine %s: %v", name, n, constants.CheckedSymbol)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		body, err := ioutil.ReadFile(funcFile)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, "read source failed")
 | 
			
		||||
		}
 | 
			
		||||
		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
 | 
			
		||||
		if os.Getenv("KUBECONFIG") != "" {
 | 
			
		||||
			deployer, err = k8sDeployer.Create()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			bctx := context.Background()
 | 
			
		||||
			deployer, err = dockerDeployer.CreateClient(bctx)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		// TODO multiple ports support
 | 
			
		||||
		return deployer.Deploy(
 | 
			
		||||
			context.Background(),
 | 
			
		||||
			workdir,
 | 
			
		||||
			name,
 | 
			
		||||
			[]int32{int32(port)},
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,38 +0,0 @@
 | 
			
		||||
package handlers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/metrue/fx/config"
 | 
			
		||||
	"github.com/metrue/fx/deploy"
 | 
			
		||||
	dockerDeployer "github.com/metrue/fx/deploy/docker"
 | 
			
		||||
	k8sDeployer "github.com/metrue/fx/deploy/kubernetes"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Destroy command handle
 | 
			
		||||
func Destroy(cfg config.Configer) HandleFunc {
 | 
			
		||||
	return func(ctx *cli.Context) (err error) {
 | 
			
		||||
		services := ctx.Args()
 | 
			
		||||
		c := context.Background()
 | 
			
		||||
		var runner deploy.Deployer
 | 
			
		||||
		if os.Getenv("KUBECONFIG") != "" {
 | 
			
		||||
			runner, err = k8sDeployer.Create()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			runner, err = dockerDeployer.CreateClient(c)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for _, svc := range services {
 | 
			
		||||
			if err := runner.Destroy(c, svc); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,27 +1,27 @@
 | 
			
		||||
package handlers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/apex/log"
 | 
			
		||||
	"github.com/metrue/fx/config"
 | 
			
		||||
	"github.com/metrue/fx/constants"
 | 
			
		||||
	"github.com/metrue/fx/context"
 | 
			
		||||
	"github.com/metrue/fx/doctor"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Doctor command handle
 | 
			
		||||
func Doctor(cfg config.Configer) HandleFunc {
 | 
			
		||||
	return func(ctx *cli.Context) error {
 | 
			
		||||
		hosts, err := cfg.ListMachines()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatalf("list machines failed %v", err)
 | 
			
		||||
			return nil
 | 
			
		||||
func Doctor() HandleFunc {
 | 
			
		||||
	return func(ctx *context.Context) error {
 | 
			
		||||
		host := os.Getenv("DOCKER_REMOTE_HOST_ADDR")
 | 
			
		||||
		user := os.Getenv("DOCKER_REMOTE_HOST_USER")
 | 
			
		||||
		password := os.Getenv("DOCKER_REMOTE_HOST_PASSWORD")
 | 
			
		||||
		if host == "" {
 | 
			
		||||
			host = "localhost"
 | 
			
		||||
		}
 | 
			
		||||
		for name, h := range hosts {
 | 
			
		||||
			if err := doctor.New(h).Start(); err != nil {
 | 
			
		||||
				log.Warnf("machine %s is in dirty state: %v", name, err)
 | 
			
		||||
			} else {
 | 
			
		||||
				log.Infof("machine %s is in healthy state: %s", name, constants.CheckedSymbol)
 | 
			
		||||
			}
 | 
			
		||||
		if err := doctor.New(host, user, password).Start(); err != nil {
 | 
			
		||||
			log.Warnf("machine %s is in dirty state: %v", host, err)
 | 
			
		||||
		} else {
 | 
			
		||||
			log.Infof("machine %s is in healthy state: %s", host, constants.CheckedSymbol)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +1,27 @@
 | 
			
		||||
package handlers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/apex/log"
 | 
			
		||||
	"github.com/metrue/fx/config"
 | 
			
		||||
	"github.com/metrue/fx/constants"
 | 
			
		||||
	api "github.com/metrue/fx/container_runtimes/docker/http"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
	"github.com/metrue/fx/context"
 | 
			
		||||
	"github.com/metrue/fx/deploy"
 | 
			
		||||
	"github.com/metrue/fx/pkg/spinner"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Down command handle
 | 
			
		||||
func Down(cfg config.Configer) HandleFunc {
 | 
			
		||||
	return func(ctx *cli.Context) error {
 | 
			
		||||
		containerID := ctx.Args()
 | 
			
		||||
		hosts, err := cfg.ListActiveMachines()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "list active machines failed: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		for name, host := range hosts {
 | 
			
		||||
			if err := api.MustCreate(host.Host, constants.AgentPort).
 | 
			
		||||
				Down(containerID); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "stop function on machine %s failed: %v", name, err)
 | 
			
		||||
func Down() HandleFunc {
 | 
			
		||||
	return func(ctx *context.Context) (err error) {
 | 
			
		||||
		const task = "destroying"
 | 
			
		||||
		spinner.Start(task)
 | 
			
		||||
		defer func() {
 | 
			
		||||
			spinner.Stop(task, err)
 | 
			
		||||
		}()
 | 
			
		||||
 | 
			
		||||
		cli := ctx.GetCliContext()
 | 
			
		||||
		services := cli.Args()
 | 
			
		||||
		runner := ctx.Get("deployer").(deploy.Deployer)
 | 
			
		||||
		for _, svc := range services {
 | 
			
		||||
			if err := runner.Destroy(ctx.Context, svc); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			log.Infof("stop function on machine %s: %v", name, constants.CheckedSymbol)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
package handlers
 | 
			
		||||
 | 
			
		||||
import "github.com/urfave/cli"
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/metrue/fx/context"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// HandleFunc command handle function
 | 
			
		||||
type HandleFunc func(ctx *cli.Context) error
 | 
			
		||||
type HandleFunc func(ctx *context.Context) error
 | 
			
		||||
 
 | 
			
		||||
@@ -1,56 +0,0 @@
 | 
			
		||||
package handlers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"log"
 | 
			
		||||
 | 
			
		||||
	"github.com/metrue/fx/config"
 | 
			
		||||
	"github.com/metrue/fx/utils"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// AddHost add a host
 | 
			
		||||
func AddHost(cfg config.Configer) HandleFunc {
 | 
			
		||||
	return func(ctx *cli.Context) error {
 | 
			
		||||
		name := ctx.String("name")
 | 
			
		||||
		addr := ctx.String("host")
 | 
			
		||||
		user := ctx.String("user")
 | 
			
		||||
		password := ctx.String("password")
 | 
			
		||||
		host := config.NewHost(addr, user, password)
 | 
			
		||||
		if !host.Valid() {
 | 
			
		||||
			log.Fatalf("invaid host %v", host)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if host.IsRemote() {
 | 
			
		||||
			if host.User == "" || host.Password == "" {
 | 
			
		||||
				log.Fatalf("the host to add is a remote, user and password for SSH login is required")
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return cfg.AddMachine(name, host)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RemoveHost remove a host
 | 
			
		||||
func RemoveHost(cfg config.Configer) HandleFunc {
 | 
			
		||||
	return func(ctx *cli.Context) error {
 | 
			
		||||
		name := ctx.Args().First()
 | 
			
		||||
		if name == "" {
 | 
			
		||||
			log.Fatalf("no name given: fx infra remove <name>")
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return cfg.RemoveHost(name)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListHosts list hosts
 | 
			
		||||
func ListHosts(cfg config.Configer) HandleFunc {
 | 
			
		||||
	return func(ctx *cli.Context) error {
 | 
			
		||||
		hosts, err := cfg.ListMachines()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return utils.OutputJSON(hosts)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -4,28 +4,32 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"os"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/apex/log"
 | 
			
		||||
	"github.com/google/uuid"
 | 
			
		||||
	"github.com/metrue/fx/config"
 | 
			
		||||
	"github.com/metrue/fx/constants"
 | 
			
		||||
	api "github.com/metrue/fx/container_runtimes/docker/http"
 | 
			
		||||
	containerruntimes "github.com/metrue/fx/container_runtimes"
 | 
			
		||||
	"github.com/metrue/fx/context"
 | 
			
		||||
	"github.com/metrue/fx/packer"
 | 
			
		||||
	"github.com/metrue/fx/provision"
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
	"github.com/metrue/fx/utils"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// BuildImage build image
 | 
			
		||||
func BuildImage(cfg config.Configer) HandleFunc {
 | 
			
		||||
	return func(ctx *cli.Context) error {
 | 
			
		||||
		funcFile := ctx.Args().First()
 | 
			
		||||
		tag := ctx.String("tag")
 | 
			
		||||
func BuildImage() HandleFunc {
 | 
			
		||||
	return func(ctx *context.Context) error {
 | 
			
		||||
		cli := ctx.GetCliContext()
 | 
			
		||||
		funcFile := cli.Args().First()
 | 
			
		||||
		tag := cli.String("tag")
 | 
			
		||||
		if tag == "" {
 | 
			
		||||
			tag = uuid.New().String()
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		workdir := fmt.Sprintf("/tmp/fx-%d", time.Now().Unix())
 | 
			
		||||
		defer os.RemoveAll(workdir)
 | 
			
		||||
 | 
			
		||||
		body, err := ioutil.ReadFile(funcFile)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatalf("function code load failed: %v", err)
 | 
			
		||||
@@ -33,57 +37,33 @@ func BuildImage(cfg config.Configer) HandleFunc {
 | 
			
		||||
		}
 | 
			
		||||
		log.Infof("function code loaded: %v", constants.CheckedSymbol)
 | 
			
		||||
		lang := utils.GetLangFromFileName(funcFile)
 | 
			
		||||
		pwd, err := os.Getwd()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatalf("could not get current work directory: %v", err)
 | 
			
		||||
 | 
			
		||||
		fn := types.Func{Language: lang, Source: string(body)}
 | 
			
		||||
 | 
			
		||||
		if err := packer.PackIntoDir(fn, workdir); err != nil {
 | 
			
		||||
			log.Fatalf("could not pack function %v: %v", fn, err)
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		tarFile := fmt.Sprintf("%s.%s.tar", pwd, tag)
 | 
			
		||||
		defer os.RemoveAll(tarFile)
 | 
			
		||||
 | 
			
		||||
		if err := packer.PackIntoTar(lang, string(body), tarFile); err != nil {
 | 
			
		||||
			log.Fatalf("could not pack function: %v", err)
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		log.Infof("function packed: %v", constants.CheckedSymbol)
 | 
			
		||||
 | 
			
		||||
		hosts, err := cfg.ListActiveMachines()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatalf("could not list active machine: %v", err)
 | 
			
		||||
			return errors.Wrap(err, "list active machines failed")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(hosts) == 0 {
 | 
			
		||||
			log.Warnf("no active machines")
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		for n, host := range hosts {
 | 
			
		||||
			if !host.Provisioned {
 | 
			
		||||
				provisionor := provision.New(host)
 | 
			
		||||
				if err := provisionor.Start(); err != nil {
 | 
			
		||||
					return errors.Wrapf(err, "could not provision %s", n)
 | 
			
		||||
				}
 | 
			
		||||
				log.Infof("provision machine %v: %s", n, constants.CheckedSymbol)
 | 
			
		||||
				if err := cfg.UpdateProvisionedStatus(n, true); err != nil {
 | 
			
		||||
					return errors.Wrap(err, "update machine provision status failed")
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := api.MustCreate(host.Host, constants.AgentPort).
 | 
			
		||||
				BuildImage(tarFile, tag, map[string]string{}); err != nil {
 | 
			
		||||
		docker, ok := ctx.Get("docker").(containerruntimes.ContainerRuntime)
 | 
			
		||||
		if ok {
 | 
			
		||||
			nameWithTag := tag + ":latest"
 | 
			
		||||
			if err := docker.BuildImage(ctx.Context, workdir, nameWithTag); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			log.Infof("image built on machine %s: %v", n, constants.CheckedSymbol)
 | 
			
		||||
			log.Infof("image built: %v", constants.CheckedSymbol)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
		return fmt.Errorf("no available docker cli")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ExportImage export service's code into a directory
 | 
			
		||||
func ExportImage() HandleFunc {
 | 
			
		||||
	return func(ctx *cli.Context) (err error) {
 | 
			
		||||
		funcFile := ctx.Args().First()
 | 
			
		||||
		outputDir := ctx.String("output")
 | 
			
		||||
	return func(ctx *context.Context) (err error) {
 | 
			
		||||
		cli := ctx.GetCliContext()
 | 
			
		||||
		funcFile := cli.Args().First()
 | 
			
		||||
		outputDir := cli.String("output")
 | 
			
		||||
		if outputDir == "" {
 | 
			
		||||
			log.Fatalf("output directory required")
 | 
			
		||||
			return nil
 | 
			
		||||
@@ -95,7 +75,7 @@ func ExportImage() HandleFunc {
 | 
			
		||||
		}
 | 
			
		||||
		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)
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,46 +0,0 @@
 | 
			
		||||
package handlers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/apex/log"
 | 
			
		||||
	"github.com/metrue/fx/config"
 | 
			
		||||
	"github.com/metrue/fx/constants"
 | 
			
		||||
	"github.com/metrue/fx/provision"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Activate a machine to be fx server
 | 
			
		||||
func Activate(cfg config.Configer) HandleFunc {
 | 
			
		||||
	return func(ctx *cli.Context) error {
 | 
			
		||||
		name := ctx.Args().First()
 | 
			
		||||
		if name == "" {
 | 
			
		||||
			log.Fatalf("name required for: fx infra activate <name>")
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		host, err := cfg.GetMachine(name)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatalf("could get host %v, make sure you add it first", err)
 | 
			
		||||
			log.Info("You can add a machine by: \n fx infra add -Name <name> -H <ip or hostname> -U <user> -P <password>")
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		if !host.Provisioned {
 | 
			
		||||
			provisionor := provision.New(host)
 | 
			
		||||
			if err := provisionor.Start(); err != nil {
 | 
			
		||||
				log.Fatalf("could not provision %s: %v", name, err)
 | 
			
		||||
				return nil
 | 
			
		||||
			}
 | 
			
		||||
			log.Infof("provision machine %v: %s", name, constants.CheckedSymbol)
 | 
			
		||||
			if err := cfg.UpdateProvisionedStatus(name, true); err != nil {
 | 
			
		||||
				log.Fatalf("update machine provision status failed: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if err := cfg.EnableMachine(name); err != nil {
 | 
			
		||||
			log.Fatalf("could not enable %s: %v", name, err)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		log.Infof("enble machine %v: %s", name, constants.CheckedSymbol)
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,25 +0,0 @@
 | 
			
		||||
package handlers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/apex/log"
 | 
			
		||||
	"github.com/metrue/fx/config"
 | 
			
		||||
	"github.com/metrue/fx/constants"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Deactivate a machine
 | 
			
		||||
func Deactivate(cfg config.Configer) HandleFunc {
 | 
			
		||||
	return func(ctx *cli.Context) error {
 | 
			
		||||
		name := ctx.Args().First()
 | 
			
		||||
		if name == "" {
 | 
			
		||||
			log.Fatalf("name required for: fx infra activate <name>")
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		if err := cfg.DisableMachine(name); err != nil {
 | 
			
		||||
			log.Fatalf("could not disable %s: %v", name, err)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		log.Infof("machine %s deactive: %v", name, constants.CheckedSymbol)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								handlers/init.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								handlers/init.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
package handlers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/apex/log"
 | 
			
		||||
	"github.com/metrue/fx/context"
 | 
			
		||||
	"github.com/metrue/fx/provision"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Init start fx-agent
 | 
			
		||||
func Init() HandleFunc {
 | 
			
		||||
	return func(ctx *context.Context) error {
 | 
			
		||||
		host := os.Getenv("DOCKER_REMOTE_HOST_ADDR")
 | 
			
		||||
		user := os.Getenv("DOCKER_REMOTE_HOST_USER")
 | 
			
		||||
		passord := os.Getenv("DOCKER_REMOTE_HOST_PASSWORD")
 | 
			
		||||
		if host == "" {
 | 
			
		||||
			host = "127.0.0.1"
 | 
			
		||||
		}
 | 
			
		||||
		provisioner := provision.NewWithHost(host, user, passord)
 | 
			
		||||
		if !provisioner.IsFxAgentRunning() {
 | 
			
		||||
			if err := provisioner.StartFxAgent(); err != nil {
 | 
			
		||||
				log.Fatalf("could not start fx agent on host: %s", err)
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			log.Info("fx agent started")
 | 
			
		||||
		}
 | 
			
		||||
		log.Info("fx agent already started")
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,25 +1,35 @@
 | 
			
		||||
package handlers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/apex/log"
 | 
			
		||||
	"github.com/metrue/fx/config"
 | 
			
		||||
	"github.com/metrue/fx/constants"
 | 
			
		||||
	api "github.com/metrue/fx/container_runtimes/docker/http"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
	"github.com/metrue/fx/context"
 | 
			
		||||
	"github.com/metrue/fx/deploy"
 | 
			
		||||
	"github.com/metrue/fx/pkg/spinner"
 | 
			
		||||
	"github.com/metrue/fx/utils"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// List command handle
 | 
			
		||||
func List(cfg config.Configer) HandleFunc {
 | 
			
		||||
	return func(ctx *cli.Context) error {
 | 
			
		||||
		hosts, err := cfg.ListActiveMachines()
 | 
			
		||||
func List() HandleFunc {
 | 
			
		||||
	return func(ctx *context.Context) (err error) {
 | 
			
		||||
		const task = "deploying"
 | 
			
		||||
		spinner.Start(task)
 | 
			
		||||
		defer func() {
 | 
			
		||||
			spinner.Stop(task, err)
 | 
			
		||||
		}()
 | 
			
		||||
 | 
			
		||||
		cli := ctx.GetCliContext()
 | 
			
		||||
		deployer := ctx.Get("deployer").(deploy.Deployer)
 | 
			
		||||
 | 
			
		||||
		services, err := deployer.List(ctx.Context, cli.Args().First())
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatalf("list active machines failed: %v", err)
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		for name, host := range hosts {
 | 
			
		||||
			if err := api.MustCreate(host.Host, constants.AgentPort).List(ctx.Args().First()); err != nil {
 | 
			
		||||
				log.Fatalf("list functions on machine %s failed: %v", name, err)
 | 
			
		||||
 | 
			
		||||
		for _, service := range services {
 | 
			
		||||
			if err := utils.OutputJSON(service); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										112
									
								
								handlers/up.go
									
									
									
									
									
								
							
							
						
						
									
										112
									
								
								handlers/up.go
									
									
									
									
									
								
							@@ -2,18 +2,11 @@ package handlers
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
 | 
			
		||||
	"github.com/apex/log"
 | 
			
		||||
	"github.com/metrue/fx/config"
 | 
			
		||||
	"github.com/metrue/fx/constants"
 | 
			
		||||
	api "github.com/metrue/fx/container_runtimes/docker/http"
 | 
			
		||||
	"github.com/metrue/fx/packer"
 | 
			
		||||
	"github.com/metrue/fx/provision"
 | 
			
		||||
	"github.com/metrue/fx/context"
 | 
			
		||||
	"github.com/metrue/fx/deploy"
 | 
			
		||||
	"github.com/metrue/fx/pkg/spinner"
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
	"github.com/metrue/fx/utils"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"github.com/urfave/cli"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// PortRange usable port range https: //en.wikipedia.org/wiki/Ephemeral_port
 | 
			
		||||
@@ -26,91 +19,32 @@ var PortRange = struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Up command handle
 | 
			
		||||
func Up(cfg config.Configer) HandleFunc {
 | 
			
		||||
	return func(ctx *cli.Context) (err error) {
 | 
			
		||||
		funcFile := ctx.Args().First()
 | 
			
		||||
		name := ctx.String("name")
 | 
			
		||||
		port := ctx.Int("port")
 | 
			
		||||
		healtcheck := ctx.Bool("healthcheck")
 | 
			
		||||
		force := ctx.Bool("force")
 | 
			
		||||
 | 
			
		||||
func Up() HandleFunc {
 | 
			
		||||
	return func(ctx *context.Context) (err error) {
 | 
			
		||||
		const task = "deploying"
 | 
			
		||||
		spinner.Start(task)
 | 
			
		||||
		defer func() {
 | 
			
		||||
			if r := recover(); r != nil {
 | 
			
		||||
				log.Fatalf("fatal error happened: %v", r)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Fatalf("deploy function %s (%s) failed: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
			log.Infof("function %s (%s) deployed successfully", name, funcFile)
 | 
			
		||||
			spinner.Stop(task, err)
 | 
			
		||||
		}()
 | 
			
		||||
 | 
			
		||||
		cli := ctx.GetCliContext()
 | 
			
		||||
		name := cli.String("name")
 | 
			
		||||
		port := cli.Int("port")
 | 
			
		||||
 | 
			
		||||
		if port < PortRange.min || port > PortRange.max {
 | 
			
		||||
			return fmt.Errorf("invalid port number: %d, port number should in range of %d -  %d", port, PortRange.min, PortRange.max)
 | 
			
		||||
		}
 | 
			
		||||
		hosts, err := cfg.ListActiveMachines()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, "list active machines failed")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if len(hosts) == 0 {
 | 
			
		||||
			log.Warnf("no active machines")
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// try to stop service firt
 | 
			
		||||
		if force {
 | 
			
		||||
			for n, host := range hosts {
 | 
			
		||||
				if err := api.MustCreate(host.Host, constants.AgentPort).
 | 
			
		||||
					Stop(name); err != nil {
 | 
			
		||||
					log.Infof("stop function %s on machine %s failed: %v", name, n, err)
 | 
			
		||||
				} else {
 | 
			
		||||
					log.Infof("stop function %s on machine %s: %v", name, n, constants.CheckedSymbol)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		body, err := ioutil.ReadFile(funcFile)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err, "read source failed")
 | 
			
		||||
		}
 | 
			
		||||
		lang := utils.GetLangFromFileName(funcFile)
 | 
			
		||||
 | 
			
		||||
		fn := types.ServiceFunctionSource{
 | 
			
		||||
			Language: lang,
 | 
			
		||||
			Source:   string(body),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		project, err := packer.Pack(name, fn)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrapf(err, "could pack function %s (%s)", name, funcFile)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for n, host := range hosts {
 | 
			
		||||
			if !host.Provisioned {
 | 
			
		||||
				provisionor := provision.New(host)
 | 
			
		||||
				if err := provisionor.Start(); err != nil {
 | 
			
		||||
					return errors.Wrapf(err, "could not provision %s", n)
 | 
			
		||||
				}
 | 
			
		||||
				log.Infof("provision machine %v: %s", n, constants.CheckedSymbol)
 | 
			
		||||
				if err := cfg.UpdateProvisionedStatus(n, true); err != nil {
 | 
			
		||||
					return errors.Wrap(err, "update machine provision status failed")
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := api.MustCreate(host.Host, constants.AgentPort).
 | 
			
		||||
				Up(api.UpOptions{
 | 
			
		||||
					Body:       body,
 | 
			
		||||
					Lang:       lang,
 | 
			
		||||
					Name:       name,
 | 
			
		||||
					Port:       port,
 | 
			
		||||
					HealtCheck: healtcheck,
 | 
			
		||||
					Project:    project,
 | 
			
		||||
				}); err != nil {
 | 
			
		||||
				return errors.Wrapf(err, "up function %s(%s) to machine %s failed", name, funcFile, n)
 | 
			
		||||
			}
 | 
			
		||||
			log.Infof("up function %s(%s) to machine %s: %v", name, funcFile, n, constants.CheckedSymbol)
 | 
			
		||||
		}
 | 
			
		||||
		return nil
 | 
			
		||||
		fn := ctx.Get("fn").(types.Func)
 | 
			
		||||
		image := ctx.Get("image").(string)
 | 
			
		||||
		deployer := ctx.Get("deployer").(deploy.Deployer)
 | 
			
		||||
		bindings := ctx.Get("bindings").([]types.PortBinding)
 | 
			
		||||
		return deployer.Deploy(
 | 
			
		||||
			ctx.Context,
 | 
			
		||||
			fn,
 | 
			
		||||
			name,
 | 
			
		||||
			image,
 | 
			
		||||
			bindings,
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										42
									
								
								middlewares/binding.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								middlewares/binding.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
package middlewares
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/metrue/fx/constants"
 | 
			
		||||
	"github.com/metrue/fx/context"
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Binding create bindings
 | 
			
		||||
func Binding(ctx *context.Context) error {
 | 
			
		||||
	cli := ctx.GetCliContext()
 | 
			
		||||
	port := cli.Int("port")
 | 
			
		||||
 | 
			
		||||
	var bindings []types.PortBinding
 | 
			
		||||
	if os.Getenv("KUBECONFIG") != "" {
 | 
			
		||||
		bindings = []types.PortBinding{
 | 
			
		||||
			types.PortBinding{
 | 
			
		||||
				ServiceBindingPort:  80,
 | 
			
		||||
				ContainerExposePort: constants.FxContainerExposePort,
 | 
			
		||||
			},
 | 
			
		||||
			types.PortBinding{
 | 
			
		||||
				ServiceBindingPort:  443,
 | 
			
		||||
				ContainerExposePort: constants.FxContainerExposePort,
 | 
			
		||||
			},
 | 
			
		||||
			types.PortBinding{
 | 
			
		||||
				ServiceBindingPort:  int32(port),
 | 
			
		||||
				ContainerExposePort: constants.FxContainerExposePort,
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		bindings = []types.PortBinding{
 | 
			
		||||
			types.PortBinding{
 | 
			
		||||
				ServiceBindingPort:  int32(port),
 | 
			
		||||
				ContainerExposePort: constants.FxContainerExposePort,
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Set("bindings", bindings)
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										55
									
								
								middlewares/build.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								middlewares/build.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
package middlewares
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	containerruntimes "github.com/metrue/fx/container_runtimes"
 | 
			
		||||
	"github.com/metrue/fx/context"
 | 
			
		||||
	"github.com/metrue/fx/packer"
 | 
			
		||||
	"github.com/metrue/fx/pkg/spinner"
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Build image
 | 
			
		||||
func Build(ctx *context.Context) (err error) {
 | 
			
		||||
	const task = "building"
 | 
			
		||||
	spinner.Start(task)
 | 
			
		||||
	defer func() {
 | 
			
		||||
		spinner.Stop(task, err)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	cli := ctx.GetCliContext()
 | 
			
		||||
	name := cli.String("name")
 | 
			
		||||
	fn := ctx.Get("fn").(types.Func)
 | 
			
		||||
	docker := ctx.Get("docker").(containerruntimes.ContainerRuntime)
 | 
			
		||||
	workdir := fmt.Sprintf("/tmp/fx-%d", time.Now().Unix())
 | 
			
		||||
	defer os.RemoveAll(workdir)
 | 
			
		||||
 | 
			
		||||
	if err := packer.PackIntoDir(fn, workdir); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if err := docker.BuildImage(ctx.Context, workdir, name); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	nameWithTag := name + ":latest"
 | 
			
		||||
	if err := docker.TagImage(ctx, name, nameWithTag); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Set("image", nameWithTag)
 | 
			
		||||
 | 
			
		||||
	if os.Getenv("K3S") != "" {
 | 
			
		||||
		name := cli.String("name")
 | 
			
		||||
		username := os.Getenv("DOCKER_USERNAME")
 | 
			
		||||
		password := os.Getenv("DOCKER_PASSWORD")
 | 
			
		||||
		if username != "" && password != "" {
 | 
			
		||||
			if _, err := docker.PushImage(ctx.Context, name); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			ctx.Set("image", username+"/"+name)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								middlewares/parse.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								middlewares/parse.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
package middlewares
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
 | 
			
		||||
	"github.com/metrue/fx/context"
 | 
			
		||||
	"github.com/metrue/fx/pkg/spinner"
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
	"github.com/metrue/fx/utils"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Parse parse input
 | 
			
		||||
func Parse(ctx *context.Context) (err error) {
 | 
			
		||||
	const task = "parsing"
 | 
			
		||||
	spinner.Start(task)
 | 
			
		||||
	defer func() {
 | 
			
		||||
		spinner.Stop(task, err)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	cli := ctx.GetCliContext()
 | 
			
		||||
	funcFile := cli.Args().First()
 | 
			
		||||
	lang := utils.GetLangFromFileName(funcFile)
 | 
			
		||||
	body, err := ioutil.ReadFile(funcFile)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err, "read source failed")
 | 
			
		||||
	}
 | 
			
		||||
	fn := types.Func{
 | 
			
		||||
		Language: lang,
 | 
			
		||||
		Source:   string(body),
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Set("fn", fn)
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										71
									
								
								middlewares/setup.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								middlewares/setup.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
package middlewares
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/metrue/fx/constants"
 | 
			
		||||
	containerruntimes "github.com/metrue/fx/container_runtimes"
 | 
			
		||||
	dockerHTTP "github.com/metrue/fx/container_runtimes/docker/http"
 | 
			
		||||
	dockerSDK "github.com/metrue/fx/container_runtimes/docker/sdk"
 | 
			
		||||
	"github.com/metrue/fx/context"
 | 
			
		||||
	"github.com/metrue/fx/deploy"
 | 
			
		||||
	dockerDeployer "github.com/metrue/fx/deploy/docker"
 | 
			
		||||
	k3sDeployer "github.com/metrue/fx/deploy/k3s"
 | 
			
		||||
	k8sDeployer "github.com/metrue/fx/deploy/kubernetes"
 | 
			
		||||
	"github.com/metrue/fx/pkg/spinner"
 | 
			
		||||
	"github.com/metrue/fx/provision"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Setup create k8s or docker cli
 | 
			
		||||
func Setup(ctx *context.Context) (err error) {
 | 
			
		||||
	const task = "setup"
 | 
			
		||||
	spinner.Start(task)
 | 
			
		||||
	defer func() {
 | 
			
		||||
		spinner.Stop(task, err)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	host := os.Getenv("DOCKER_REMOTE_HOST_ADDR")
 | 
			
		||||
	user := os.Getenv("DOCKER_REMOTE_HOST_USER")
 | 
			
		||||
	passord := os.Getenv("DOCKER_REMOTE_HOST_PASSWORD")
 | 
			
		||||
	var docker containerruntimes.ContainerRuntime
 | 
			
		||||
	if host != "" && user != "" {
 | 
			
		||||
		provisioner := provision.NewWithHost(host, user, passord)
 | 
			
		||||
		if !provisioner.IsFxAgentRunning() {
 | 
			
		||||
			if err := provisioner.StartFxAgent(); err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		docker, err = dockerHTTP.Create(host, constants.AgentPort)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		docker, err = dockerSDK.CreateClient(ctx)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Set("docker", docker)
 | 
			
		||||
 | 
			
		||||
	var deployer deploy.Deployer
 | 
			
		||||
	if os.Getenv("K3S") != "" {
 | 
			
		||||
		deployer, err = k3sDeployer.Create()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else if os.Getenv("KUBECONFIG") != "" {
 | 
			
		||||
		deployer, err = k8sDeployer.Create()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		deployer, err = dockerDeployer.CreateClient(ctx.Context)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	ctx.Set("deployer", deployer)
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -14,7 +14,7 @@ type DockerPacker struct {
 | 
			
		||||
	box packr.Box
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isHandler(lang string, name string) bool {
 | 
			
		||||
func isHandler(name string) bool {
 | 
			
		||||
	basename := filepath.Base(name)
 | 
			
		||||
	nameWithoutExt := strings.TrimSuffix(basename, filepath.Ext(basename))
 | 
			
		||||
	return nameWithoutExt == "fx" ||
 | 
			
		||||
@@ -28,7 +28,7 @@ func NewDockerPacker(box packr.Box) *DockerPacker {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
	for _, name := range p.box.List() {
 | 
			
		||||
		prefix := fmt.Sprintf("%s/", fn.Language)
 | 
			
		||||
@@ -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 isHandler(fn.Language, name) {
 | 
			
		||||
			if isHandler(name) {
 | 
			
		||||
				files = append(files, types.ProjectSourceFile{
 | 
			
		||||
					Path:      strings.Replace(name, prefix, "", 1),
 | 
			
		||||
					Body:      fn.Source,
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ module.exports = ({a, b}) => {
 | 
			
		||||
	return a + b
 | 
			
		||||
}
 | 
			
		||||
`
 | 
			
		||||
	fn := types.ServiceFunctionSource{
 | 
			
		||||
	fn := types.Func{
 | 
			
		||||
		Language: "node",
 | 
			
		||||
		Source:   mockSource,
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										5
									
								
								packer/images/go/Dockerfile
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								packer/images/go/Dockerfile
									
									
									
									
										vendored
									
									
								
							@@ -1,8 +1,11 @@
 | 
			
		||||
FROM metrue/fx-go-base:latest
 | 
			
		||||
FROM golang:latest
 | 
			
		||||
 | 
			
		||||
COPY . /go/src/github.com/metrue/fx
 | 
			
		||||
WORKDIR /go/src/github.com/metrue/fx
 | 
			
		||||
 | 
			
		||||
# dependency management
 | 
			
		||||
RUN go get github.com/gin-gonic/gin
 | 
			
		||||
 | 
			
		||||
RUN go build -ldflags "-w -s" -o fx fx.go app.go
 | 
			
		||||
 | 
			
		||||
EXPOSE 3000
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,9 @@ import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
 | 
			
		||||
	"github.com/gobuffalo/packr"
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
	"github.com/metrue/fx/utils"
 | 
			
		||||
@@ -12,22 +15,18 @@ import (
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
func Pack(svcName string, fn types.ServiceFunctionSource) (types.Project, error) {
 | 
			
		||||
func Pack(svcName string, fn types.Func) (types.Project, error) {
 | 
			
		||||
	box := packr.NewBox("./images")
 | 
			
		||||
	pkr := NewDockerPacker(box)
 | 
			
		||||
	return pkr.Pack(svcName, fn)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PackIntoDir pack service code into directory
 | 
			
		||||
func PackIntoDir(lang string, source string, outputDir string) error {
 | 
			
		||||
	fn := types.ServiceFunctionSource{
 | 
			
		||||
		Language: lang,
 | 
			
		||||
		Source:   source,
 | 
			
		||||
	}
 | 
			
		||||
func PackIntoDir(fn types.Func, outputDir string) error {
 | 
			
		||||
	project, err := Pack("", fn)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
@@ -44,15 +43,47 @@ func PackIntoDir(lang string, source string, outputDir string) error {
 | 
			
		||||
	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
 | 
			
		||||
func PackIntoTar(lang string, source string, path string) error {
 | 
			
		||||
func PackIntoTar(fn types.Func, path string) error {
 | 
			
		||||
	tarDir, err := ioutil.TempDir("/tmp", "fx-tar")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer os.RemoveAll(tarDir)
 | 
			
		||||
 | 
			
		||||
	if err := PackIntoDir(lang, source, tarDir); err != nil {
 | 
			
		||||
	if err := PackIntoDir(fn, tarDir); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,20 +3,16 @@ package packer
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang/mock/gomock"
 | 
			
		||||
	"github.com/metrue/fx/types"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestPack(t *testing.T) {
 | 
			
		||||
	ctrl := gomock.NewController(t)
 | 
			
		||||
	defer ctrl.Finish()
 | 
			
		||||
 | 
			
		||||
	mockSource := `
 | 
			
		||||
module.exports = ({a, b}) => {
 | 
			
		||||
	return a + b
 | 
			
		||||
}
 | 
			
		||||
`
 | 
			
		||||
	fn := types.ServiceFunctionSource{
 | 
			
		||||
	fn := types.Func{
 | 
			
		||||
		Language: "node",
 | 
			
		||||
		Source:   mockSource,
 | 
			
		||||
	}
 | 
			
		||||
@@ -58,3 +54,27 @@ 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.Func{
 | 
			
		||||
		Language: "java",
 | 
			
		||||
		Source:   mockSource,
 | 
			
		||||
	}
 | 
			
		||||
	_, err := PackIntoK8SConfigMapFile(fn)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -42,8 +42,10 @@ func (l *LocalRunner) Run(script string) ([]byte, error) {
 | 
			
		||||
	params := strings.Split(script, " ")
 | 
			
		||||
	var cmd *exec.Cmd
 | 
			
		||||
	if len(params) > 1 {
 | 
			
		||||
		// nolint: gosec
 | 
			
		||||
		cmd = exec.Command(params[0], params[1:]...)
 | 
			
		||||
	} else {
 | 
			
		||||
		// nolint: gosec
 | 
			
		||||
		cmd = exec.Command(params[0])
 | 
			
		||||
	}
 | 
			
		||||
	return cmd.CombinedOutput()
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										52
									
								
								pkg/spinner/spiner.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								pkg/spinner/spiner.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
package spinner
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"math/rand"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/briandowns/spinner"
 | 
			
		||||
	aurora "github.com/logrusorgru/aurora"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var s *spinner.Spinner
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	style := spinner.CharSets[36]
 | 
			
		||||
	interval := 100 * time.Millisecond
 | 
			
		||||
	s = spinner.New(style, interval)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Start spinner
 | 
			
		||||
func Start(task string) {
 | 
			
		||||
	colors := []string{
 | 
			
		||||
		"red",
 | 
			
		||||
		"green",
 | 
			
		||||
		"yellow",
 | 
			
		||||
		"blue",
 | 
			
		||||
		"magenta",
 | 
			
		||||
		"cyan",
 | 
			
		||||
		"white",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rand.Seed(time.Now().UnixNano())
 | 
			
		||||
	// nolint
 | 
			
		||||
	s.Color(colors[rand.Intn(len(colors))])
 | 
			
		||||
	s.Prefix = task + " "
 | 
			
		||||
	if s.Active() {
 | 
			
		||||
		s.Restart()
 | 
			
		||||
	} else {
 | 
			
		||||
		s.Start()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Stop spinner
 | 
			
		||||
func Stop(task string, err error) {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		fmt.Println(aurora.Red("\u2717"))
 | 
			
		||||
		fmt.Println(aurora.Red("*****************"))
 | 
			
		||||
		fmt.Println(err)
 | 
			
		||||
		fmt.Println(aurora.Red("*****************"))
 | 
			
		||||
	}
 | 
			
		||||
	s.Stop()
 | 
			
		||||
}
 | 
			
		||||
@@ -2,11 +2,10 @@ package provision
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"os"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"github.com/apex/log"
 | 
			
		||||
	"github.com/metrue/fx/config"
 | 
			
		||||
	"github.com/metrue/fx/constants"
 | 
			
		||||
	"github.com/metrue/fx/pkg/command"
 | 
			
		||||
	ssh "github.com/metrue/go-ssh-client"
 | 
			
		||||
@@ -20,25 +19,82 @@ type Provisioner interface {
 | 
			
		||||
// Provisionor provision-or
 | 
			
		||||
type Provisionor struct {
 | 
			
		||||
	sshClient ssh.Client
 | 
			
		||||
 | 
			
		||||
	host config.Host
 | 
			
		||||
	host      string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// New new provision
 | 
			
		||||
func New(host config.Host) *Provisionor {
 | 
			
		||||
	p := &Provisionor{host: host}
 | 
			
		||||
	if host.IsRemote() {
 | 
			
		||||
		p.sshClient = ssh.New(host.Host).
 | 
			
		||||
			WithUser(host.User).
 | 
			
		||||
			WithPassword(host.Password)
 | 
			
		||||
func isLocal(host string) bool {
 | 
			
		||||
	if host == "" {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return host == "127.0.0.1" || host == "localhost" || host == "0.0.0.0"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewWithHost create a provisionor with host, user, and password
 | 
			
		||||
func NewWithHost(host string, user string, password string) *Provisionor {
 | 
			
		||||
	p := &Provisionor{
 | 
			
		||||
		host: host,
 | 
			
		||||
	}
 | 
			
		||||
	if !isLocal(host) {
 | 
			
		||||
		p.sshClient = ssh.New(host).
 | 
			
		||||
			WithUser(user).
 | 
			
		||||
			WithPassword(password)
 | 
			
		||||
	}
 | 
			
		||||
	return p
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsFxAgentRunning check if fx-agent is running on host
 | 
			
		||||
func (p *Provisionor) IsFxAgentRunning() bool {
 | 
			
		||||
	script := fmt.Sprintf("docker inspect %s", constants.AgentContainerName)
 | 
			
		||||
	var cmd *command.Command
 | 
			
		||||
	if !isLocal(p.host) {
 | 
			
		||||
		cmd = command.New("inspect fx-agent", script, command.NewRemoteRunner(p.sshClient))
 | 
			
		||||
	} else {
 | 
			
		||||
		cmd = command.New("inspect fx-agent", script, command.NewLocalRunner())
 | 
			
		||||
	}
 | 
			
		||||
	output, err := cmd.Exec()
 | 
			
		||||
	if os.Getenv("DEBUG") != "" {
 | 
			
		||||
		log.Infof(string(output))
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StartFxAgent start fx agent
 | 
			
		||||
func (p *Provisionor) StartFxAgent() error {
 | 
			
		||||
	script := fmt.Sprintf("docker run -d --name=%s --rm -v /var/run/docker.sock:/var/run/docker.sock -p 0.0.0.0:%s:1234 bobrik/socat TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock", constants.AgentContainerName, constants.AgentPort)
 | 
			
		||||
	var cmd *command.Command
 | 
			
		||||
	if !isLocal(p.host) {
 | 
			
		||||
		cmd = command.New("start fx-agent", script, command.NewRemoteRunner(p.sshClient))
 | 
			
		||||
	} else {
 | 
			
		||||
		cmd = command.New("start fx-agent", script, command.NewLocalRunner())
 | 
			
		||||
	}
 | 
			
		||||
	if output, err := cmd.Exec(); err != nil {
 | 
			
		||||
		log.Info(string(output))
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StopFxAgent stop fx agent
 | 
			
		||||
func (p *Provisionor) StopFxAgent() error {
 | 
			
		||||
	script := fmt.Sprintf("docker stop %s", constants.AgentContainerName)
 | 
			
		||||
	var cmd *command.Command
 | 
			
		||||
	if !isLocal(p.host) {
 | 
			
		||||
		cmd = command.New("stop fx agent", script, command.NewRemoteRunner(p.sshClient))
 | 
			
		||||
	} else {
 | 
			
		||||
		cmd = command.New("stop fx agent", script, command.NewLocalRunner())
 | 
			
		||||
	}
 | 
			
		||||
	if output, err := cmd.Exec(); err != nil {
 | 
			
		||||
		log.Infof(string(output))
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Start start provision progress
 | 
			
		||||
func (p *Provisionor) Start() error {
 | 
			
		||||
	startFxAgent := fmt.Sprintf("docker run -d --name=%s --rm -v /var/run/docker.sock:/var/run/docker.sock -p 0.0.0.0:%s:1234 bobrik/socat TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock", constants.AgentContainerName, constants.AgentPort)
 | 
			
		||||
	stopFxAgent := fmt.Sprintf("docker stop %s", constants.AgentContainerName)
 | 
			
		||||
	scripts := map[string]string{
 | 
			
		||||
		"pull java Docker base image":   "docker pull metrue/fx-java-base",
 | 
			
		||||
		"pull julia Docker base image":  "docker pull metrue/fx-julia-base",
 | 
			
		||||
@@ -48,35 +104,12 @@ func (p *Provisionor) Start() error {
 | 
			
		||||
		"pull go Docker base image":     "docker pull metrue/fx-go-base",
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	agentStartupCmds := []*command.Command{}
 | 
			
		||||
	if p.host.IsRemote() {
 | 
			
		||||
		agentStartupCmds = append(agentStartupCmds,
 | 
			
		||||
			command.New("stop current fx agent", stopFxAgent, command.NewRemoteRunner(p.sshClient)),
 | 
			
		||||
			command.New("start fx agent", startFxAgent, command.NewRemoteRunner(p.sshClient)),
 | 
			
		||||
		)
 | 
			
		||||
	} else {
 | 
			
		||||
		agentStartupCmds = append(agentStartupCmds,
 | 
			
		||||
			command.New("stop current fx agent", stopFxAgent, command.NewLocalRunner()),
 | 
			
		||||
			command.New("start fx agent", startFxAgent, command.NewLocalRunner()),
 | 
			
		||||
		)
 | 
			
		||||
	}
 | 
			
		||||
	for _, cmd := range agentStartupCmds {
 | 
			
		||||
		if output, err := cmd.Exec(); err != nil {
 | 
			
		||||
			if strings.Contains(string(output), "No such container: fx-agent") {
 | 
			
		||||
				// Skip stop a fx-agent error when there is not agent running
 | 
			
		||||
			} else {
 | 
			
		||||
				log.Fatalf("Provision:%s: %s, %s", cmd.Name, err, output)
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var wg sync.WaitGroup
 | 
			
		||||
	for n, s := range scripts {
 | 
			
		||||
		wg.Add(1)
 | 
			
		||||
		go func(name, script string) {
 | 
			
		||||
			var cmd *command.Command
 | 
			
		||||
			if p.host.IsRemote() {
 | 
			
		||||
			if !isLocal(p.host) {
 | 
			
		||||
				cmd = command.New(name, script, command.NewRemoteRunner(p.sshClient))
 | 
			
		||||
			} else {
 | 
			
		||||
				cmd = command.New(name, script, command.NewLocalRunner())
 | 
			
		||||
 
 | 
			
		||||
@@ -2,14 +2,35 @@ package provision
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/metrue/fx/config"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestStart(t *testing.T) {
 | 
			
		||||
	host := config.Host{Host: "127.0.0.1"}
 | 
			
		||||
	provisionor := New(host)
 | 
			
		||||
func TestProvisionWorkflow(t *testing.T) {
 | 
			
		||||
	provisionor := NewWithHost("127.0.0.1", "", "")
 | 
			
		||||
 | 
			
		||||
	_ = provisionor.StopFxAgent()
 | 
			
		||||
	// TODO wait too long here to make test pass
 | 
			
		||||
	time.Sleep(40 * time.Second)
 | 
			
		||||
 | 
			
		||||
	running := provisionor.IsFxAgentRunning()
 | 
			
		||||
	if running {
 | 
			
		||||
		t.Fatalf("fx-agent should not be running")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := provisionor.StartFxAgent(); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	running = provisionor.IsFxAgentRunning()
 | 
			
		||||
	if !running {
 | 
			
		||||
		t.Fatalf("fx-agent should be running")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := provisionor.Start(); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := provisionor.StopFxAgent(); err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,41 +1,64 @@
 | 
			
		||||
#!/usr/bin/env bash
 | 
			
		||||
 | 
			
		||||
fx_has() {
 | 
			
		||||
set -e
 | 
			
		||||
 | 
			
		||||
has() {
 | 
			
		||||
  type "$1" > /dev/null 2>&1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
get_package_url() {
 | 
			
		||||
    label=""
 | 
			
		||||
    platform=""
 | 
			
		||||
    if [ "$(uname)" == "Darwin" ]; then
 | 
			
		||||
        label="macOS"
 | 
			
		||||
        platform="macOS"
 | 
			
		||||
    elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then
 | 
			
		||||
        label="Tux"
 | 
			
		||||
        platform="Tux"
 | 
			
		||||
    elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" ]; then
 | 
			
		||||
        label="windows"
 | 
			
		||||
        platform="windows"
 | 
			
		||||
    elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]; then
 | 
			
		||||
        label="windows"
 | 
			
		||||
        platform="windows"
 | 
			
		||||
    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() {
 | 
			
		||||
    local url=$1
 | 
			
		||||
    # TODO we can do it on one line
 | 
			
		||||
    rm -rf fx.tar.gz
 | 
			
		||||
    curl -o fx.tar.gz -L -O ${url} && tar -xvzf ./fx.tar.gz --exclude=*.md -C /usr/local/bin
 | 
			
		||||
    rm -rf ./fx.tar.gz
 | 
			
		||||
    url=$(get_package_url)
 | 
			
		||||
    tarFile="fx.tar.gz"
 | 
			
		||||
    targetFile=$(pwd)
 | 
			
		||||
 | 
			
		||||
    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() {
 | 
			
		||||
    if fx_has "docker"; then
 | 
			
		||||
        url=$(get_package_url)
 | 
			
		||||
        if [ ${url}"X" != "X" ];then
 | 
			
		||||
            download_and_install ${url}
 | 
			
		||||
        fi
 | 
			
		||||
    if has "curl";then
 | 
			
		||||
      download_and_install
 | 
			
		||||
    else
 | 
			
		||||
        echo "No Docker found on this host"
 | 
			
		||||
        echo "  - Docker installation: https://docs.docker.com/engine/installation"
 | 
			
		||||
      echo "You need cURL to use this script"
 | 
			
		||||
      exit 1
 | 
			
		||||
    fi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,12 +10,3 @@ sudo apt-get update -y
 | 
			
		||||
sudo apt-get install -y docker-ce
 | 
			
		||||
 | 
			
		||||
docker run hello-world
 | 
			
		||||
 | 
			
		||||
# curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/${K8S_VERSION}/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/
 | 
			
		||||
# mkdir -p ${HOME}/.kube
 | 
			
		||||
# touch ${HOME}/.kube/confi
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
## start fx proxy agent
 | 
			
		||||
docker run -d --name=fx-agent --rm -v /var/run/docker.sock:/var/run/docker.sock -p 0.0.0.0:8866:1234 bobrik/socat TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -3,22 +3,15 @@
 | 
			
		||||
set -e
 | 
			
		||||
 | 
			
		||||
fx="./build/fx"
 | 
			
		||||
service='fx-service-abc'
 | 
			
		||||
service='fx-service'
 | 
			
		||||
 | 
			
		||||
run() {
 | 
			
		||||
  local lang=$1
 | 
			
		||||
  local port=$2
 | 
			
		||||
  # localhost
 | 
			
		||||
  $fx up --name ${service}_${lang} --port ${port} --healthcheck test/functions/func.${lang}
 | 
			
		||||
  $fx list # | jq ''
 | 
			
		||||
  $fx down ${service}_${lang} # | grep "Down Service ${service}"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
deploy() {
 | 
			
		||||
  local lang=$1
 | 
			
		||||
  local port=$2
 | 
			
		||||
  $fx deploy --name ${service}_${lang} --port ${port} test/functions/func.${lang}
 | 
			
		||||
  docker ps
 | 
			
		||||
  $fx destroy ${service}_${lang}
 | 
			
		||||
  $fx down ${service}_${lang} || true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
build_image() {
 | 
			
		||||
@@ -35,16 +28,13 @@ export_image() {
 | 
			
		||||
 | 
			
		||||
# main
 | 
			
		||||
# clean up
 | 
			
		||||
docker stop fx-agent || true && docker rm fx-agent || true
 | 
			
		||||
# docker stop fx-agent || true && docker rm fx-agent || true
 | 
			
		||||
 | 
			
		||||
$fx infra activate localhost
 | 
			
		||||
port=20000
 | 
			
		||||
for lang in 'js' 'rb' 'py' 'go' 'php' 'jl' 'java' 'd'; do
 | 
			
		||||
for lang in 'js' 'rb' 'py' 'go' 'php' 'java' 'd'; do
 | 
			
		||||
  run $lang $port
 | 
			
		||||
  ((port++))
 | 
			
		||||
 | 
			
		||||
  deploy $lang $port
 | 
			
		||||
 | 
			
		||||
  build_image $lang "test-fx-image-build-${lang}"
 | 
			
		||||
  mkdir -p /tmp/${lang}/images
 | 
			
		||||
  export_image ${lang} /tmp/${lang}/images
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ServiceFunctionSource source of service's function
 | 
			
		||||
type ServiceFunctionSource struct {
 | 
			
		||||
	Language string `json:"language"`
 | 
			
		||||
	Source   string `json:"source"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DefaultHost default host IP
 | 
			
		||||
const DefaultHost = "0.0.0.0"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +0,0 @@
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
 | 
			
		||||
func TestDockerVersion(t *testing.T) {
 | 
			
		||||
	host := "localhost"
 | 
			
		||||
	port := "8866"
 | 
			
		||||
	version, err := DockerVersion(host, port)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if version == "" {
 | 
			
		||||
		t.Fatal("should version empty")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -22,6 +22,7 @@ func Download(filepath string, url string) (err error) {
 | 
			
		||||
	}
 | 
			
		||||
	defer out.Close()
 | 
			
		||||
 | 
			
		||||
	// nolint: gosec
 | 
			
		||||
	resp, err := http.Get(url)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
@@ -48,6 +49,7 @@ func Unzip(source string, target string) (err error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, file := range reader.File {
 | 
			
		||||
		//nolint: gosec
 | 
			
		||||
		path := filepath.Join(target, file.Name)
 | 
			
		||||
		if file.FileInfo().IsDir() {
 | 
			
		||||
			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 {
 | 
			
		||||
	bytes, err := json.MarshalIndent(v, "", "\t")
 | 
			
		||||
	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))
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user