mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
fn: improve UX and publish/deploy command (#359)
* fn: improve UX and publish/deploy command * fn: remove wrong use cases for deploy * fn: fix regression introduced by merge
This commit is contained in:
@@ -23,16 +23,13 @@ build:
|
|||||||
- make test
|
- make test
|
||||||
```
|
```
|
||||||
|
|
||||||
`app` (optional) is the application name to which this function will be pushed
|
`name` is the name and tag to which this function will be pushed to and the
|
||||||
to.
|
|
||||||
|
|
||||||
`image` is the name and tag to which this function will be pushed to and the
|
|
||||||
route updated to use it.
|
route updated to use it.
|
||||||
|
|
||||||
`route` (optional) allows you to overwrite the calculated route from the path
|
`path` (optional) allows you to overwrite the calculated route from the path
|
||||||
position. You may use it to override the calculated route.
|
position. You may use it to override the calculated route.
|
||||||
|
|
||||||
`version` represents current version of the function. When publishing, it is
|
`version` represents current version of the function. When deploying, it is
|
||||||
appended to the image as a tag.
|
appended to the image as a tag.
|
||||||
|
|
||||||
`type` (optional) allows you to set the type of the route. `sync`, for functions
|
`type` (optional) allows you to set the type of the route. `sync`, for functions
|
||||||
|
|||||||
@@ -42,12 +42,12 @@ the name of the function to run, in the form that python expects
|
|||||||
(`module.function`). Where you would package the files into a `.zip` to upload
|
(`module.function`). Where you would package the files into a `.zip` to upload
|
||||||
to Lambda, we just pass the list of files to `fn`.
|
to Lambda, we just pass the list of files to `fn`.
|
||||||
|
|
||||||
## Publishing the function to IronFunctions
|
## Deploying the function to IronFunctions
|
||||||
|
|
||||||
Next we want to publish the function to our IronFunctions
|
Next we want to deploy the function to our IronFunctions
|
||||||
```sh
|
```sh
|
||||||
$ fn publish -v -f -d ./irontest
|
$ fn deploy -v -d ./irontest irontest
|
||||||
publishing irontest/hello_world:1/function.yaml
|
deploying irontest/hello_world:1/function.yaml
|
||||||
Sending build context to Docker daemon 4.096 kB
|
Sending build context to Docker daemon 4.096 kB
|
||||||
Step 1 : FROM iron/lambda-python2.7
|
Step 1 : FROM iron/lambda-python2.7
|
||||||
latest: Pulling from iron/lambda-python2.7
|
latest: Pulling from iron/lambda-python2.7
|
||||||
@@ -82,7 +82,7 @@ Next we want to publish the function to our IronFunctions
|
|||||||
irontest/hello_world:1/function.yaml done
|
irontest/hello_world:1/function.yaml done
|
||||||
```
|
```
|
||||||
|
|
||||||
This will publish the generated function under the app `irontest` with `hello_world` as a route, e.g:
|
This will deploy the generated function under the app `irontest` with `hello_world` as a route, e.g:
|
||||||
`http://<hostname>/r/irontest/hello_world:1`,
|
`http://<hostname>/r/irontest/hello_world:1`,
|
||||||
|
|
||||||
You should also now see the generated Docker image.
|
You should also now see the generated Docker image.
|
||||||
@@ -108,7 +108,7 @@ You should see the output.
|
|||||||
|
|
||||||
## Calling the function from IronFunctions
|
## Calling the function from IronFunctions
|
||||||
|
|
||||||
The `fn call` command can call the published version with a given payload.
|
The `fn call` command can call the deployed version with a given payload.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ echo '{ "first_name": "Jon", "last_name": "Snow" }' | ./fn call irontest /hello_world:1
|
$ echo '{ "first_name": "Jon", "last_name": "Snow" }' | ./fn call irontest /hello_world:1
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ If you only want to download the code, pass the `--download-only` flag. The
|
|||||||
you tweak the settings on a command level. Finally, you can import a different version of your lambda function than the latest one
|
you tweak the settings on a command level. Finally, you can import a different version of your lambda function than the latest one
|
||||||
by passing `--version <version>.`
|
by passing `--version <version>.`
|
||||||
|
|
||||||
You can then publish the imported lambda as follows:
|
You can then deploy the imported lambda as follows:
|
||||||
```
|
```
|
||||||
./fn publish -d ./user/my-function
|
./fn deploy -d ./user/my-function user
|
||||||
````
|
````
|
||||||
Now the function can be reached via ```http://$HOSTNAME/r/user/my-function```
|
Now the function can be reached via ```http://$HOSTNAME/r/user/my-function```
|
||||||
@@ -9,7 +9,7 @@ Once it's pushed to a registry, you can use it by referencing it when adding a r
|
|||||||
|
|
||||||
## Using fn
|
## Using fn
|
||||||
|
|
||||||
This is the easiest way to build, package and publish your functions.
|
This is the easiest way to build, package and deploy your functions.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
3
examples/blog/func.yaml
Normal file
3
examples/blog/func.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
name: iron/func-blog
|
||||||
|
build:
|
||||||
|
- ./build.sh
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
image: iron/func-blog
|
|
||||||
build:
|
|
||||||
- ./build.sh
|
|
||||||
3
examples/caddy-lb/func.yaml
Normal file
3
examples/caddy-lb/func.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
name: iron/func-caddy-lb
|
||||||
|
build:
|
||||||
|
- ./build.sh
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
image: iron/func-caddy-lb
|
|
||||||
build:
|
|
||||||
- ./build.sh
|
|
||||||
3
examples/checker/func.yaml
Normal file
3
examples/checker/func.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
name: iron/func-checker
|
||||||
|
build:
|
||||||
|
- ./build.sh
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
image: iron/func-checker
|
|
||||||
build:
|
|
||||||
- ./build.sh
|
|
||||||
3
examples/echo/func.yaml
Normal file
3
examples/echo/func.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
name: iron/func-echo
|
||||||
|
build:
|
||||||
|
- ./build.sh
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
image: iron/func-echo
|
|
||||||
build:
|
|
||||||
- ./build.sh
|
|
||||||
3
examples/error/func.yaml
Normal file
3
examples/error/func.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
name: iron/func-error
|
||||||
|
build:
|
||||||
|
- ./build.sh
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
image: iron/func-error
|
|
||||||
build:
|
|
||||||
- ./build.sh
|
|
||||||
@@ -9,7 +9,7 @@ fn init <YOUR_DOCKERHUB_USERNAME>/hello
|
|||||||
fn build
|
fn build
|
||||||
# test it
|
# test it
|
||||||
cat hello.payload.json | fn run
|
cat hello.payload.json | fn run
|
||||||
# push it to Docker Hub for use with IronFunctions
|
# push it to Docker Hub
|
||||||
fn push
|
fn push
|
||||||
# Create a route to this function on IronFunctions
|
# Create a route to this function on IronFunctions
|
||||||
fn routes create myapp /hello
|
fn routes create myapp /hello
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ This example will show you how to test and deploy Go (Golang) code to IronFuncti
|
|||||||
### 1. Prepare the `func.yaml` file:
|
### 1. Prepare the `func.yaml` file:
|
||||||
|
|
||||||
At func.yaml you will find:
|
At func.yaml you will find:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
app: phpapp
|
name: USERNAME/hello
|
||||||
route: /hello
|
|
||||||
image: USERNAME/hello
|
|
||||||
version: 0.0.1
|
version: 0.0.1
|
||||||
|
path: /hello
|
||||||
build:
|
build:
|
||||||
- docker run --rm -v "$PWD":/worker -w /worker iron/php:dev composer install
|
- docker run --rm -v "$PWD":/worker -w /worker iron/php:dev composer install
|
||||||
```
|
```
|
||||||
@@ -21,7 +21,14 @@ the moment you try to test this function.
|
|||||||
### 2. Build:
|
### 2. Build:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
fn publish
|
# build the function
|
||||||
|
fn build
|
||||||
|
# test it
|
||||||
|
cat hello.payload.json | fn run
|
||||||
|
# push it to Docker Hub
|
||||||
|
fn push
|
||||||
|
# Create a route to this function on IronFunctions
|
||||||
|
fn routes create phpapp /hello
|
||||||
```
|
```
|
||||||
|
|
||||||
`-v` is optional, but it allows you to see how this function is being built.
|
`-v` is optional, but it allows you to see how this function is being built.
|
||||||
@@ -31,7 +38,7 @@ fn publish
|
|||||||
Now you can start jobs on your function. Let's quickly queue up a job to try it out.
|
Now you can start jobs on your function. Let's quickly queue up a job to try it out.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cat hello.payload.json | fn run phpapp /hello
|
cat hello.payload.json | fn call phpapp /hello
|
||||||
```
|
```
|
||||||
|
|
||||||
Here's a curl example to show how easy it is to do in any language:
|
Here's a curl example to show how easy it is to do in any language:
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
app: phpapp
|
name: USERNAME/hello
|
||||||
route: /hello
|
version: 0.0.1
|
||||||
image: USERNAME/hello:0.0.1
|
path: /hello
|
||||||
build:
|
build:
|
||||||
- docker run --rm -v "$PWD":/worker -w /worker iron/php:dev composer install
|
- docker run --rm -v "$PWD":/worker -w /worker iron/php:dev composer install
|
||||||
|
|||||||
@@ -7,10 +7,9 @@ This example will show you how to test and deploy Go (Golang) code to IronFuncti
|
|||||||
At func.yaml you will find:
|
At func.yaml you will find:
|
||||||
|
|
||||||
```yml
|
```yml
|
||||||
app: pythonapp
|
name: USERNAME/hello
|
||||||
route: /hello
|
|
||||||
image: USERNAME/hello
|
|
||||||
version: 0.0.1
|
version: 0.0.1
|
||||||
|
path: /hello
|
||||||
build:
|
build:
|
||||||
- docker run --rm -v "$PWD":/worker -w /worker iron/python:2-dev pip install -t packages -r requirements.txt
|
- docker run --rm -v "$PWD":/worker -w /worker iron/python:2-dev pip install -t packages -r requirements.txt
|
||||||
```
|
```
|
||||||
@@ -22,7 +21,14 @@ the moment you try to test this function.
|
|||||||
### 2. Build:
|
### 2. Build:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
fn publish
|
# build the function
|
||||||
|
fn build
|
||||||
|
# test it
|
||||||
|
cat hello.payload.json | fn run
|
||||||
|
# push it to Docker Hub
|
||||||
|
fn push
|
||||||
|
# Create a route to this function on IronFunctions
|
||||||
|
fn routes create pythonapp /hello
|
||||||
```
|
```
|
||||||
|
|
||||||
`-v` is optional, but it allows you to see how this function is being built.
|
`-v` is optional, but it allows you to see how this function is being built.
|
||||||
@@ -32,7 +38,7 @@ fn publish
|
|||||||
Now you can start jobs on your function. Let's quickly queue up a job to try it out.
|
Now you can start jobs on your function. Let's quickly queue up a job to try it out.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cat hello.payload.json | fn run pythonapp /hello
|
cat hello.payload.json | fn call pythonapp /hello
|
||||||
```
|
```
|
||||||
|
|
||||||
Here's a curl example to show how easy it is to do in any language:
|
Here's a curl example to show how easy it is to do in any language:
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
app: pythonapp
|
|
||||||
route: /hello
|
|
||||||
image: USERNAME/hello:0.0.1
|
|
||||||
build:
|
|
||||||
- docker run --rm -v "$PWD":/worker -w /worker iron/python:2-dev pip install -t packages -r requirements.txt
|
|
||||||
@@ -11,10 +11,8 @@ docker run --rm -v "$PWD":/worker -w /worker iron/ruby:dev bundle install --stan
|
|||||||
fn build
|
fn build
|
||||||
# test it
|
# test it
|
||||||
cat hello.payload.json | fn run
|
cat hello.payload.json | fn run
|
||||||
# push it to Docker Hub for use with IronFunctions
|
|
||||||
fn push
|
|
||||||
# Create a route to this function on IronFunctions
|
# Create a route to this function on IronFunctions
|
||||||
fn routes create myapp /hello
|
fn deploy myapp
|
||||||
```
|
```
|
||||||
|
|
||||||
Now surf to: http://localhost:8080/r/myapp/hello
|
Now surf to: http://localhost:8080/r/myapp/hello
|
||||||
|
|||||||
3
examples/redis/func.yaml
Normal file
3
examples/redis/func.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
name: iron/func-redis
|
||||||
|
build:
|
||||||
|
- ./build.sh
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
image: iron/func-redis
|
|
||||||
build:
|
|
||||||
- ./build.sh
|
|
||||||
@@ -11,10 +11,8 @@ docker run --rm -v "$PWD":/worker -w /worker iron/ruby:dev bundle install --stan
|
|||||||
fn build
|
fn build
|
||||||
# test it
|
# test it
|
||||||
cat slack.payload | fn run
|
cat slack.payload | fn run
|
||||||
# push it to Docker Hub for use with IronFunctions
|
|
||||||
fn push
|
|
||||||
# Create a route to this function on IronFunctions
|
# Create a route to this function on IronFunctions
|
||||||
fn routes create slackbot /guppy
|
fn deploy slackbot
|
||||||
# Change the route response header content-type to application/json
|
# Change the route response header content-type to application/json
|
||||||
curl -X PUT http://127.0.0.1:8080/v1/apps/slackbot/routes/guppy -d '{ "route": { "headers": { "Content-type": ["application/json"] } } }'
|
curl -X PUT http://127.0.0.1:8080/v1/apps/slackbot/routes/guppy -d '{ "route": { "headers": { "Content-type": ["application/json"] } } }'
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
name: iron/sleeper
|
name: iron/sleeper
|
||||||
version: 0.0.2
|
version: 0.0.2
|
||||||
3
examples/twitter/func.yaml
Normal file
3
examples/twitter/func.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
name: iron/func-twitter
|
||||||
|
build:
|
||||||
|
- ./build.sh
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
image: iron/func-twitter
|
|
||||||
build:
|
|
||||||
- ./build.sh
|
|
||||||
61
fn/README.md
61
fn/README.md
@@ -62,9 +62,8 @@ myapp
|
|||||||
$ fn apps create otherapp # create new app
|
$ fn apps create otherapp # create new app
|
||||||
otherapp created
|
otherapp created
|
||||||
|
|
||||||
$ fn apps describe otherapp # describe an app
|
$ fn apps config otherapp # show app-specific configuration
|
||||||
app: otherapp
|
this application has no configurations
|
||||||
no specific configuration
|
|
||||||
|
|
||||||
$ fn apps
|
$ fn apps
|
||||||
myapp
|
myapp
|
||||||
@@ -156,60 +155,20 @@ $ export API_URL="http://myfunctions.example.org/"
|
|||||||
$ fn ...
|
$ fn ...
|
||||||
```
|
```
|
||||||
|
|
||||||
## Publish
|
## Bulk deploy
|
||||||
|
|
||||||
Also there is the publish command that is going to scan all local directory for
|
Also there is the `deploy` command that is going to scan all local directory for
|
||||||
functions, rebuild them and push them to Docker Hub and update them in
|
functions, rebuild them and push them to Docker Hub and update them in
|
||||||
IronFunction.
|
IronFunction. It will use the `route` entry in the existing function file to
|
||||||
|
see the update in the daemon.
|
||||||
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ fn publish
|
$ fn deploy APP
|
||||||
path result
|
|
||||||
/app/hello done
|
|
||||||
/app/hello-sync error: no Dockerfile found for this function
|
|
||||||
/app/test done
|
|
||||||
```
|
```
|
||||||
|
|
||||||
It works by scanning all children directories of the current working directory,
|
`fn deploy` expects that each directory to contain a file `func.yaml`
|
||||||
following this convention:
|
which instructs `fn` on how to act with that particular update.
|
||||||
|
|
||||||
<pre><code>┌───────┐
|
|
||||||
│ ./ │
|
|
||||||
└───┬───┘
|
|
||||||
│ ┌───────┐
|
|
||||||
├────▶│ myapp │
|
|
||||||
│ └───┬───┘
|
|
||||||
│ │ ┌───────┐
|
|
||||||
│ ├────▶│route1 │
|
|
||||||
│ │ └───────┘
|
|
||||||
│ │ │ ┌─────────┐
|
|
||||||
│ │ ├────▶│subroute1│
|
|
||||||
│ │ │ └─────────┘
|
|
||||||
│
|
|
||||||
│ ┌───────┐
|
|
||||||
├────▶│ other │
|
|
||||||
│ └───┬───┘
|
|
||||||
│ │ ┌───────┐
|
|
||||||
│ ├────▶│route1 │
|
|
||||||
│ │ └───────┘</code></pre>
|
|
||||||
|
|
||||||
|
|
||||||
It will render this pattern of updates:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
$ fn publish
|
|
||||||
path result
|
|
||||||
/myapp/route1/subroute1 done
|
|
||||||
/other/route1 done
|
|
||||||
```
|
|
||||||
|
|
||||||
It means that first subdirectory are always considered app names (e.g. `myapp`
|
|
||||||
and `other`), each subdirectory of these firsts are considered part of the route
|
|
||||||
(e.g. `route1/subroute1`).
|
|
||||||
|
|
||||||
`fn publish` expects that each directory to contain a file `func.yaml`
|
|
||||||
which instructs `fn` on how to act with that particular update, and a
|
|
||||||
Dockerfile which it is going to use to build the image and push to Docker Hub.
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|||||||
38
fn/build.go
38
fn/build.go
@@ -8,35 +8,45 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func build() cli.Command {
|
func build() cli.Command {
|
||||||
cmd := buildcmd{commoncmd: &commoncmd{}}
|
cmd := buildcmd{}
|
||||||
flags := append([]cli.Flag{}, cmd.flags()...)
|
flags := append([]cli.Flag{}, cmd.flags()...)
|
||||||
return cli.Command{
|
return cli.Command{
|
||||||
Name: "build",
|
Name: "build",
|
||||||
Usage: "build function version",
|
Usage: "build function version",
|
||||||
Flags: flags,
|
Flags: flags,
|
||||||
Action: cmd.scan,
|
Action: cmd.build,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type buildcmd struct {
|
type buildcmd struct {
|
||||||
*commoncmd
|
verbose bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *buildcmd) scan(c *cli.Context) error {
|
func (b *buildcmd) flags() []cli.Flag {
|
||||||
b.commoncmd.scan(b.walker)
|
return []cli.Flag{
|
||||||
return nil
|
cli.BoolFlag{
|
||||||
}
|
Name: "v",
|
||||||
|
Usage: "verbose mode",
|
||||||
func (b *buildcmd) walker(path string, info os.FileInfo, err error) error {
|
Destination: &b.verbose,
|
||||||
walker(path, info, err, b.build)
|
},
|
||||||
return nil
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// build will take the found valid function and build it
|
// build will take the found valid function and build it
|
||||||
func (b *buildcmd) build(path string) error {
|
func (b *buildcmd) build(c *cli.Context) error {
|
||||||
fmt.Fprintln(b.verbwriter, "building", path)
|
verbwriter := verbwriter(b.verbose)
|
||||||
|
|
||||||
ff, err := b.buildfunc(path)
|
path, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fn, err := findFuncfile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(verbwriter, "building", fn)
|
||||||
|
ff, err := buildfunc(verbwriter, fn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
41
fn/bump.go
41
fn/bump.go
@@ -15,35 +15,46 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func bump() cli.Command {
|
func bump() cli.Command {
|
||||||
cmd := bumpcmd{commoncmd: &commoncmd{}}
|
cmd := bumpcmd{}
|
||||||
flags := append([]cli.Flag{}, cmd.flags()...)
|
flags := append([]cli.Flag{}, cmd.flags()...)
|
||||||
return cli.Command{
|
return cli.Command{
|
||||||
Name: "bump",
|
Name: "bump",
|
||||||
Usage: "bump function version",
|
Usage: "bump function version",
|
||||||
Flags: flags,
|
Flags: flags,
|
||||||
Action: cmd.scan,
|
Action: cmd.bump,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type bumpcmd struct {
|
type bumpcmd struct {
|
||||||
*commoncmd
|
verbose bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *bumpcmd) scan(c *cli.Context) error {
|
func (b *bumpcmd) flags() []cli.Flag {
|
||||||
b.commoncmd.scan(b.walker)
|
return []cli.Flag{
|
||||||
return nil
|
cli.BoolFlag{
|
||||||
}
|
Name: "v",
|
||||||
|
Usage: "verbose mode",
|
||||||
func (b *bumpcmd) walker(path string, info os.FileInfo, err error) error {
|
Destination: &b.verbose,
|
||||||
walker(path, info, err, b.bump)
|
},
|
||||||
return nil
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// bump will take the found valid function and bump its version
|
// bump will take the found valid function and bump its version
|
||||||
func (b *bumpcmd) bump(path string) error {
|
func (b *bumpcmd) bump(c *cli.Context) error {
|
||||||
fmt.Fprintln(b.verbwriter, "bumping version for", path)
|
verbwriter := verbwriter(b.verbose)
|
||||||
|
|
||||||
funcfile, err := parsefuncfile(path)
|
path, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fn, err := findFuncfile(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(verbwriter, "bumping version for", fn)
|
||||||
|
|
||||||
|
funcfile, err := parsefuncfile(fn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -66,7 +77,7 @@ func (b *bumpcmd) bump(path string) error {
|
|||||||
|
|
||||||
funcfile.Version = newver.String()
|
funcfile.Version = newver.String()
|
||||||
|
|
||||||
if err := storefuncfile(path, funcfile); err != nil {
|
if err := storefuncfile(fn, funcfile); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
165
fn/common.go
165
fn/common.go
@@ -11,159 +11,41 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/iron-io/functions/fn/langs"
|
"github.com/iron-io/functions/fn/langs"
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func isFuncfile(path string, info os.FileInfo) bool {
|
func verbwriter(verbose bool) io.Writer {
|
||||||
if info.IsDir() {
|
verbwriter := ioutil.Discard
|
||||||
return false
|
if verbose {
|
||||||
|
verbwriter = os.Stderr
|
||||||
}
|
}
|
||||||
|
return verbwriter
|
||||||
basefn := filepath.Base(path)
|
|
||||||
for _, fn := range validfn {
|
|
||||||
if basefn == fn {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func walker(path string, info os.FileInfo, err error, f func(path string) error) {
|
func buildfunc(verbwriter io.Writer, path string) (*funcfile, error) {
|
||||||
if err := f(path); err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, path, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type commoncmd struct {
|
|
||||||
wd string
|
|
||||||
verbose bool
|
|
||||||
force bool
|
|
||||||
recursively bool
|
|
||||||
|
|
||||||
verbwriter io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *commoncmd) flags() []cli.Flag {
|
|
||||||
return []cli.Flag{
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "d",
|
|
||||||
Usage: "working directory",
|
|
||||||
Destination: &c.wd,
|
|
||||||
EnvVar: "WORK_DIR",
|
|
||||||
Value: "./",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "v",
|
|
||||||
Usage: "verbose mode",
|
|
||||||
Destination: &c.verbose,
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "f",
|
|
||||||
Usage: "force updating of all functions that are already up-to-date",
|
|
||||||
Destination: &c.force,
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "r",
|
|
||||||
Usage: "recursively scan all functions",
|
|
||||||
Destination: &c.recursively,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *commoncmd) scan(walker func(path string, info os.FileInfo, err error) error) {
|
|
||||||
c.verbwriter = ioutil.Discard
|
|
||||||
if c.verbose {
|
|
||||||
c.verbwriter = os.Stderr
|
|
||||||
}
|
|
||||||
|
|
||||||
var walked bool
|
|
||||||
|
|
||||||
err := filepath.Walk(c.wd, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if !c.recursively && path != c.wd && info.IsDir() {
|
|
||||||
return filepath.SkipDir
|
|
||||||
}
|
|
||||||
|
|
||||||
if !isFuncfile(path, info) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.recursively && !c.force && !isstale(path) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
e := walker(path, info, err)
|
|
||||||
now := time.Now()
|
|
||||||
os.Chtimes(path, now, now)
|
|
||||||
walked = true
|
|
||||||
return e
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(c.verbwriter, "file walk error: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !walked {
|
|
||||||
fmt.Println("No function file found.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Theory of operation: this takes an optimistic approach to detect whether a
|
|
||||||
// package must be rebuild/bump/published. It loads for all files mtime's and
|
|
||||||
// compare with functions.json own mtime. If any file is younger than
|
|
||||||
// functions.json, it triggers a rebuild.
|
|
||||||
// The problem with this approach is that depending on the OS running it, the
|
|
||||||
// time granularity of these timestamps might lead to false negatives - that is
|
|
||||||
// a package that is stale but it is not recompiled. A more elegant solution
|
|
||||||
// could be applied here, like https://golang.org/src/cmd/go/pkg.go#L1111
|
|
||||||
func isstale(path string) bool {
|
|
||||||
fi, err := os.Stat(path)
|
|
||||||
if err != nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
fnmtime := fi.ModTime()
|
|
||||||
dir := filepath.Dir(path)
|
|
||||||
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
|
||||||
if info.IsDir() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if info.ModTime().After(fnmtime) {
|
|
||||||
return errors.New("found stale package")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return err != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c commoncmd) buildfunc(path string) (*funcfile, error) {
|
|
||||||
funcfile, err := parsefuncfile(path)
|
funcfile, err := parsefuncfile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.localbuild(path, funcfile.Build); err != nil {
|
if err := localbuild(verbwriter, path, funcfile.Build); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := c.dockerbuild(path, funcfile); err != nil {
|
if err := dockerbuild(verbwriter, path, funcfile); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return funcfile, nil
|
return funcfile, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c commoncmd) localbuild(path string, steps []string) error {
|
func localbuild(verbwriter io.Writer, path string, steps []string) error {
|
||||||
for _, cmd := range steps {
|
for _, cmd := range steps {
|
||||||
exe := exec.Command("/bin/sh", "-c", cmd)
|
exe := exec.Command("/bin/sh", "-c", cmd)
|
||||||
exe.Dir = filepath.Dir(path)
|
exe.Dir = filepath.Dir(path)
|
||||||
exe.Stderr = c.verbwriter
|
exe.Stderr = verbwriter
|
||||||
exe.Stdout = c.verbwriter
|
exe.Stdout = verbwriter
|
||||||
fmt.Fprintf(c.verbwriter, "- %s:\n", cmd)
|
|
||||||
if err := exe.Run(); err != nil {
|
if err := exe.Run(); err != nil {
|
||||||
return fmt.Errorf("error running command %v (%v)", cmd, err)
|
return fmt.Errorf("error running command %v (%v)", cmd, err)
|
||||||
}
|
}
|
||||||
@@ -172,7 +54,7 @@ func (c commoncmd) localbuild(path string, steps []string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c commoncmd) dockerbuild(path string, ff *funcfile) error {
|
func dockerbuild(verbwriter io.Writer, path string, ff *funcfile) error {
|
||||||
dir := filepath.Dir(path)
|
dir := filepath.Dir(path)
|
||||||
|
|
||||||
var helper langs.LangHelper
|
var helper langs.LangHelper
|
||||||
@@ -275,7 +157,6 @@ func writeTmpDockerfile(dir string, ff *funcfile) error {
|
|||||||
buffer.WriteString(s)
|
buffer.WriteString(s)
|
||||||
buffer.WriteString("\"")
|
buffer.WriteString("\"")
|
||||||
}
|
}
|
||||||
fmt.Println(buffer.String())
|
|
||||||
|
|
||||||
t := template.Must(template.New("Dockerfile").Parse(tplDockerfile))
|
t := template.Must(template.New("Dockerfile").Parse(tplDockerfile))
|
||||||
err = t.Execute(fd, struct {
|
err = t.Execute(fd, struct {
|
||||||
@@ -293,3 +174,25 @@ func extractEnvConfig(configs []string) map[string]string {
|
|||||||
}
|
}
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func dockerpush(ff *funcfile) error {
|
||||||
|
cmd := exec.Command("docker", "push", ff.FullName())
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("error running docker push: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func appNamePath(img string) (string, string) {
|
||||||
|
sep := strings.Index(img, "/")
|
||||||
|
if sep < 0 {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
tag := strings.Index(img[sep:], ":")
|
||||||
|
if tag < 0 {
|
||||||
|
tag = len(img[sep:])
|
||||||
|
}
|
||||||
|
return img[:sep], img[sep : sep+tag]
|
||||||
|
}
|
||||||
|
|||||||
239
fn/deploy.go
Normal file
239
fn/deploy.go
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
functions "github.com/iron-io/functions_go"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func deploy() cli.Command {
|
||||||
|
cmd := deploycmd{
|
||||||
|
RoutesApi: functions.NewRoutesApi(),
|
||||||
|
}
|
||||||
|
var flags []cli.Flag
|
||||||
|
flags = append(flags, cmd.flags()...)
|
||||||
|
return cli.Command{
|
||||||
|
Name: "deploy",
|
||||||
|
ArgsUsage: "`APPNAME`",
|
||||||
|
Usage: "scan local directory for functions, build and push all of them to `APPNAME`.",
|
||||||
|
Flags: flags,
|
||||||
|
Action: cmd.scan,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type deploycmd struct {
|
||||||
|
appName string
|
||||||
|
*functions.RoutesApi
|
||||||
|
|
||||||
|
wd string
|
||||||
|
verbose bool
|
||||||
|
incremental bool
|
||||||
|
skippush bool
|
||||||
|
|
||||||
|
verbwriter io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *deploycmd) flags() []cli.Flag {
|
||||||
|
return []cli.Flag{
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "v",
|
||||||
|
Usage: "verbose mode",
|
||||||
|
Destination: &p.verbose,
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "d",
|
||||||
|
Usage: "working directory",
|
||||||
|
Destination: &p.wd,
|
||||||
|
EnvVar: "WORK_DIR",
|
||||||
|
Value: "./",
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "i",
|
||||||
|
Usage: "uses incremental building",
|
||||||
|
Destination: &p.incremental,
|
||||||
|
},
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "skip-push",
|
||||||
|
Usage: "does not push Docker built images onto Docker Hub - useful for local development.",
|
||||||
|
Destination: &p.skippush,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *deploycmd) scan(c *cli.Context) error {
|
||||||
|
if c.Args().First() == "" {
|
||||||
|
return errors.New("application name is missing")
|
||||||
|
}
|
||||||
|
p.appName = c.Args().First()
|
||||||
|
p.verbwriter = verbwriter(p.verbose)
|
||||||
|
|
||||||
|
var walked bool
|
||||||
|
|
||||||
|
err := filepath.Walk(p.wd, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if path != p.wd && info.IsDir() {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isFuncfile(path, info) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.incremental && !isstale(path) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
e := p.deploy(path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(p.verbwriter, path, e)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
os.Chtimes(path, now, now)
|
||||||
|
walked = true
|
||||||
|
return e
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(p.verbwriter, "file walk error: %s\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !walked {
|
||||||
|
return errors.New("No function file found.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// deploy will take the found function and check for the presence of a
|
||||||
|
// Dockerfile, and run a three step process: parse functions file, build and
|
||||||
|
// push the container, and finally it will update function's route. Optionally,
|
||||||
|
// the route can be overriden inside the functions file.
|
||||||
|
func (p *deploycmd) deploy(path string) error {
|
||||||
|
fmt.Fprintln(p.verbwriter, "deploying", path)
|
||||||
|
|
||||||
|
funcfile, err := buildfunc(p.verbwriter, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.skippush {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := dockerpush(funcfile); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.route(path, funcfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *deploycmd) route(path string, ff *funcfile) error {
|
||||||
|
if err := resetBasePath(p.Configuration); err != nil {
|
||||||
|
return fmt.Errorf("error setting endpoint: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ff.Path == nil {
|
||||||
|
_, path := appNamePath(ff.FullName())
|
||||||
|
ff.Path = &path
|
||||||
|
}
|
||||||
|
|
||||||
|
if ff.Memory == nil {
|
||||||
|
ff.Memory = new(int64)
|
||||||
|
}
|
||||||
|
if ff.Type == nil {
|
||||||
|
ff.Type = new(string)
|
||||||
|
}
|
||||||
|
if ff.Format == nil {
|
||||||
|
ff.Format = new(string)
|
||||||
|
}
|
||||||
|
if ff.MaxConcurrency == nil {
|
||||||
|
ff.MaxConcurrency = new(int)
|
||||||
|
}
|
||||||
|
if ff.Timeout == nil {
|
||||||
|
dur := time.Duration(0)
|
||||||
|
ff.Timeout = &dur
|
||||||
|
}
|
||||||
|
|
||||||
|
body := functions.RouteWrapper{
|
||||||
|
Route: functions.Route{
|
||||||
|
Path: *ff.Path,
|
||||||
|
Image: ff.FullName(),
|
||||||
|
Memory: *ff.Memory,
|
||||||
|
Type_: *ff.Type,
|
||||||
|
Config: expandEnvConfig(ff.Config),
|
||||||
|
Headers: ff.Headers,
|
||||||
|
Format: *ff.Format,
|
||||||
|
MaxConcurrency: int32(*ff.MaxConcurrency),
|
||||||
|
Timeout: int32(ff.Timeout.Seconds()),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(p.verbwriter, "updating API with app: %s route: %s name: %s \n", p.appName, *ff.Path, ff.Name)
|
||||||
|
|
||||||
|
wrapper, resp, err := p.AppsAppRoutesPost(p.appName, body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error getting routes: %v", err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode == http.StatusBadRequest {
|
||||||
|
return fmt.Errorf("error storing this route: %s", wrapper.Error_.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandEnvConfig(configs map[string]string) map[string]string {
|
||||||
|
for k, v := range configs {
|
||||||
|
configs[k] = os.ExpandEnv(v)
|
||||||
|
}
|
||||||
|
return configs
|
||||||
|
}
|
||||||
|
|
||||||
|
func isFuncfile(path string, info os.FileInfo) bool {
|
||||||
|
if info.IsDir() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
basefn := filepath.Base(path)
|
||||||
|
for _, fn := range validfn {
|
||||||
|
if basefn == fn {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Theory of operation: this takes an optimistic approach to detect whether a
|
||||||
|
// package must be rebuild/bump/deployed. It loads for all files mtime's and
|
||||||
|
// compare with functions.json own mtime. If any file is younger than
|
||||||
|
// functions.json, it triggers a rebuild.
|
||||||
|
// The problem with this approach is that depending on the OS running it, the
|
||||||
|
// time granularity of these timestamps might lead to false negatives - that is
|
||||||
|
// a package that is stale but it is not recompiled. A more elegant solution
|
||||||
|
// could be applied here, like https://golang.org/src/cmd/go/pkg.go#L1111
|
||||||
|
func isstale(path string) bool {
|
||||||
|
fi, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fnmtime := fi.ModTime()
|
||||||
|
dir := filepath.Dir(path)
|
||||||
|
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if info.ModTime().After(fnmtime) {
|
||||||
|
return errors.New("found stale package")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
return err != nil
|
||||||
|
}
|
||||||
@@ -24,12 +24,11 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type funcfile struct {
|
type funcfile struct {
|
||||||
App *string `yaml:"app,omitempty",json:"app,omitempty"`
|
|
||||||
Name string `yaml:"name,omitempty",json:"name,omitempty"`
|
Name string `yaml:"name,omitempty",json:"name,omitempty"`
|
||||||
Version string `yaml:"version,omitempty",json:"version,omitempty"`
|
Version string `yaml:"version,omitempty",json:"version,omitempty"`
|
||||||
Runtime *string `yaml:"runtime,omitempty",json:"runtime,omitempty"`
|
Runtime *string `yaml:"runtime,omitempty",json:"runtime,omitempty"`
|
||||||
Entrypoint *string `yaml:"entrypoint,omitempty",json:"entrypoint,omitempty"`
|
Entrypoint *string `yaml:"entrypoint,omitempty",json:"entrypoint,omitempty"`
|
||||||
Route *string `yaml:"route,omitempty",json:"route,omitempty"`
|
Path *string `yaml:"path,omitempty",json:"path,omitempty"`
|
||||||
Type *string `yaml:"type,omitempty",json:"type,omitempty"`
|
Type *string `yaml:"type,omitempty",json:"type,omitempty"`
|
||||||
Memory *int64 `yaml:"memory,omitempty",json:"memory,omitempty"`
|
Memory *int64 `yaml:"memory,omitempty",json:"memory,omitempty"`
|
||||||
Format *string `yaml:"format,omitempty",json:"format,omitempty"`
|
Format *string `yaml:"format,omitempty",json:"format,omitempty"`
|
||||||
@@ -62,13 +61,22 @@ func (ff *funcfile) RuntimeTag() (runtime, tag string) {
|
|||||||
return rt[:tagpos], rt[tagpos+1:]
|
return rt[:tagpos], rt[tagpos+1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
func findFuncfile() (*funcfile, error) {
|
func findFuncfile(path string) (string, error) {
|
||||||
for _, fn := range validfn {
|
for _, fn := range validfn {
|
||||||
if exists(fn) {
|
fullfn := filepath.Join(path, fn)
|
||||||
return parsefuncfile(fn)
|
if exists(fullfn) {
|
||||||
|
return fullfn, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, newNotFoundError("could not find function file")
|
return "", newNotFoundError("could not find function file")
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadFuncfile() (*funcfile, error) {
|
||||||
|
fn, err := findFuncfile(".")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return parsefuncfile(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parsefuncfile(path string) (*funcfile, error) {
|
func parsefuncfile(path string) (*funcfile, error) {
|
||||||
|
|||||||
14
fn/init.go
14
fn/init.go
@@ -91,7 +91,7 @@ func initFn() cli.Command {
|
|||||||
|
|
||||||
func (a *initFnCmd) init(c *cli.Context) error {
|
func (a *initFnCmd) init(c *cli.Context) error {
|
||||||
if !a.force {
|
if !a.force {
|
||||||
ff, err := findFuncfile()
|
ff, err := loadFuncfile()
|
||||||
if _, ok := err.(*notFoundError); !ok && err != nil {
|
if _, ok := err.(*notFoundError); !ok && err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -105,15 +105,23 @@ func (a *initFnCmd) init(c *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var ffmt *string
|
||||||
|
if a.format != "" {
|
||||||
|
ffmt = &a.format
|
||||||
|
}
|
||||||
|
|
||||||
ff := &funcfile{
|
ff := &funcfile{
|
||||||
Name: a.name,
|
Name: a.name,
|
||||||
Runtime: &a.runtime,
|
Runtime: &a.runtime,
|
||||||
Version: initialVersion,
|
Version: initialVersion,
|
||||||
Entrypoint: &a.entrypoint,
|
Entrypoint: &a.entrypoint,
|
||||||
Format: &a.format,
|
Format: ffmt,
|
||||||
MaxConcurrency: &a.maxConcurrency,
|
MaxConcurrency: &a.maxConcurrency,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, path := appNamePath(ff.FullName())
|
||||||
|
ff.Path = &path
|
||||||
|
|
||||||
if err := encodeFuncfileYAML("func.yaml", ff); err != nil {
|
if err := encodeFuncfileYAML("func.yaml", ff); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -130,7 +138,7 @@ func (a *initFnCmd) buildFuncFile(c *cli.Context) error {
|
|||||||
|
|
||||||
a.name = c.Args().First()
|
a.name = c.Args().First()
|
||||||
if a.name == "" || strings.Contains(a.name, ":") {
|
if a.name == "" || strings.Contains(a.name, ":") {
|
||||||
return errors.New("Please specify a name for your function in the following format <DOCKERHUB_USERNAME>/<FUNCTION_NAME>")
|
return errors.New("Please specify a name for your function in the following format <DOCKERHUB_USERNAME>/<FUNCTION_NAME>.\nTry: fn init <DOCKERHUB_USERNAME>/<FUNCTION_NAME>")
|
||||||
}
|
}
|
||||||
|
|
||||||
if exists("Dockerfile") {
|
if exists("Dockerfile") {
|
||||||
|
|||||||
@@ -276,11 +276,10 @@ func basicImportHandler(functionName, tmpFileName string, opts *createImageOptio
|
|||||||
|
|
||||||
func createFunctionYaml(opts createImageOptions) error {
|
func createFunctionYaml(opts createImageOptions) error {
|
||||||
strs := strings.Split(opts.Name, "/")
|
strs := strings.Split(opts.Name, "/")
|
||||||
route := fmt.Sprintf("/%s", strs[1])
|
path := fmt.Sprintf("/%s", strs[1])
|
||||||
funcDesc := &funcfile{
|
funcDesc := &funcfile{
|
||||||
App: &strs[0],
|
|
||||||
Name: opts.Name,
|
Name: opts.Name,
|
||||||
Route: &route,
|
Path: &path,
|
||||||
Config: opts.Config,
|
Config: opts.Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ ENVIRONMENT VARIABLES:
|
|||||||
build(),
|
build(),
|
||||||
bump(),
|
bump(),
|
||||||
call(),
|
call(),
|
||||||
|
deploy(),
|
||||||
initFn(),
|
initFn(),
|
||||||
lambda(),
|
lambda(),
|
||||||
publish(),
|
|
||||||
push(),
|
push(),
|
||||||
routes(),
|
routes(),
|
||||||
run(),
|
run(),
|
||||||
|
|||||||
153
fn/publish.go
153
fn/publish.go
@@ -1,153 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
functions "github.com/iron-io/functions_go"
|
|
||||||
"github.com/urfave/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
func publish() cli.Command {
|
|
||||||
cmd := publishcmd{
|
|
||||||
commoncmd: &commoncmd{},
|
|
||||||
RoutesApi: functions.NewRoutesApi(),
|
|
||||||
}
|
|
||||||
var flags []cli.Flag
|
|
||||||
flags = append(flags, cmd.flags()...)
|
|
||||||
flags = append(flags, cmd.commoncmd.flags()...)
|
|
||||||
return cli.Command{
|
|
||||||
Name: "publish",
|
|
||||||
Usage: "scan local directory for functions, build and publish them.",
|
|
||||||
Flags: flags,
|
|
||||||
Action: cmd.scan,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type publishcmd struct {
|
|
||||||
*commoncmd
|
|
||||||
*functions.RoutesApi
|
|
||||||
|
|
||||||
skippush bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *publishcmd) flags() []cli.Flag {
|
|
||||||
return []cli.Flag{
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "skip-push",
|
|
||||||
Usage: "does not push Docker built images onto Docker Hub - useful for local development.",
|
|
||||||
Destination: &p.skippush,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *publishcmd) scan(c *cli.Context) error {
|
|
||||||
p.commoncmd.scan(p.walker)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *publishcmd) walker(path string, info os.FileInfo, err error) error {
|
|
||||||
walker(path, info, err, p.publish)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// publish will take the found function and check for the presence of a
|
|
||||||
// Dockerfile, and run a three step process: parse functions file, build and
|
|
||||||
// push the container, and finally it will update function's route. Optionally,
|
|
||||||
// the route can be overriden inside the functions file.
|
|
||||||
func (p *publishcmd) publish(path string) error {
|
|
||||||
fmt.Fprintln(p.verbwriter, "publishing", path)
|
|
||||||
|
|
||||||
funcfile, err := p.buildfunc(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.skippush {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := p.dockerpush(funcfile); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.route(path, funcfile)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p publishcmd) dockerpush(ff *funcfile) error {
|
|
||||||
cmd := exec.Command("docker", "push", ff.FullName())
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("error running docker push: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *publishcmd) route(path string, ff *funcfile) error {
|
|
||||||
if err := resetBasePath(p.Configuration); err != nil {
|
|
||||||
return fmt.Errorf("error setting endpoint: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: This is just a nasty hack and should be cleaned up all the way
|
|
||||||
pathsSplit := strings.Split(ff.FullName(), "/")
|
|
||||||
|
|
||||||
if ff.App == nil {
|
|
||||||
ff.App = &pathsSplit[0]
|
|
||||||
}
|
|
||||||
if ff.Route == nil {
|
|
||||||
path := "/" + strings.Split(pathsSplit[1], ":")[0]
|
|
||||||
ff.Route = &path
|
|
||||||
}
|
|
||||||
|
|
||||||
if ff.Memory == nil {
|
|
||||||
ff.Memory = new(int64)
|
|
||||||
}
|
|
||||||
if ff.Type == nil {
|
|
||||||
ff.Type = new(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
body := functions.RouteWrapper{
|
|
||||||
Route: functions.Route{
|
|
||||||
Path: *ff.Route,
|
|
||||||
Image: ff.FullName(),
|
|
||||||
AppName: *ff.App,
|
|
||||||
Memory: *ff.Memory,
|
|
||||||
Type_: *ff.Type,
|
|
||||||
Config: expandEnvConfig(ff.Config),
|
|
||||||
Headers: ff.Headers,
|
|
||||||
Timeout: int32(ff.Timeout.Seconds()),
|
|
||||||
MaxConcurrency: int32(*ff.MaxConcurrency),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(p.verbwriter, "updating API with app: %s route: %s name: %s \n", *ff.App, *ff.Route, ff.Name)
|
|
||||||
|
|
||||||
wrapper, resp, err := p.AppsAppRoutesPost(*ff.App, body)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error getting routes: %v", err)
|
|
||||||
}
|
|
||||||
if resp.StatusCode == http.StatusBadRequest {
|
|
||||||
return fmt.Errorf("error storing this route: %s", wrapper.Error_.Message)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func expandEnvConfig(configs map[string]string) map[string]string {
|
|
||||||
for k, v := range configs {
|
|
||||||
configs[k] = os.ExpandEnv(v)
|
|
||||||
}
|
|
||||||
return configs
|
|
||||||
}
|
|
||||||
|
|
||||||
func reverse(s string) string {
|
|
||||||
r := []rune(s)
|
|
||||||
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
|
|
||||||
r[i], r[j] = r[j], r[i]
|
|
||||||
}
|
|
||||||
return string(r)
|
|
||||||
}
|
|
||||||
47
fn/push.go
47
fn/push.go
@@ -1,60 +1,59 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
|
|
||||||
functions "github.com/iron-io/functions_go"
|
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
func push() cli.Command {
|
func push() cli.Command {
|
||||||
cmd := pushcmd{
|
cmd := pushcmd{}
|
||||||
publishcmd: &publishcmd{
|
|
||||||
commoncmd: &commoncmd{},
|
|
||||||
RoutesApi: functions.NewRoutesApi(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
var flags []cli.Flag
|
var flags []cli.Flag
|
||||||
flags = append(flags, cmd.commoncmd.flags()...)
|
flags = append(flags, cmd.flags()...)
|
||||||
return cli.Command{
|
return cli.Command{
|
||||||
Name: "push",
|
Name: "push",
|
||||||
Usage: "push function to Docker Hub",
|
Usage: "push function to Docker Hub",
|
||||||
Flags: flags,
|
Flags: flags,
|
||||||
Action: cmd.scan,
|
Action: cmd.push,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type pushcmd struct {
|
type pushcmd struct {
|
||||||
*publishcmd
|
verbose bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pushcmd) scan(c *cli.Context) error {
|
func (p *pushcmd) flags() []cli.Flag {
|
||||||
p.commoncmd.scan(p.walker)
|
return []cli.Flag{
|
||||||
return nil
|
cli.BoolFlag{
|
||||||
}
|
Name: "v",
|
||||||
|
Usage: "verbose mode",
|
||||||
func (p *pushcmd) walker(path string, info os.FileInfo, err error) error {
|
Destination: &p.verbose,
|
||||||
walker(path, info, err, p.push)
|
},
|
||||||
return nil
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// push will take the found function and check for the presence of a
|
// push will take the found function and check for the presence of a
|
||||||
// Dockerfile, and run a three step process: parse functions file,
|
// Dockerfile, and run a three step process: parse functions file,
|
||||||
// push the container, and finally it will update function's route. Optionally,
|
// push the container, and finally it will update function's route. Optionally,
|
||||||
// the route can be overriden inside the functions file.
|
// the route can be overriden inside the functions file.
|
||||||
func (p *pushcmd) push(path string) error {
|
func (p *pushcmd) push(c *cli.Context) error {
|
||||||
fmt.Fprintln(p.verbwriter, "pushing", path)
|
verbwriter := verbwriter(p.verbose)
|
||||||
|
|
||||||
funcfile, err := parsefuncfile(path)
|
ff, err := loadFuncfile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if _, ok := err.(*notFoundError); ok {
|
||||||
|
return errors.New("error: image name is missing or no function file found")
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := p.dockerpush(funcfile); err != nil {
|
fmt.Fprintln(verbwriter, "pushing", ff.FullName())
|
||||||
|
|
||||||
|
if err := dockerpush(ff); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Function %v pushed successfully to Docker Hub.\n", funcfile.FullName())
|
fmt.Printf("Function %v pushed successfully to Docker Hub.\n", ff.FullName())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ func (a *routesCmd) create(c *cli.Context) error {
|
|||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
)
|
)
|
||||||
if image == "" {
|
if image == "" {
|
||||||
ff, err := findFuncfile()
|
ff, err := loadFuncfile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(*notFoundError); ok {
|
if _, ok := err.(*notFoundError); ok {
|
||||||
return errors.New("error: image name is missing or no function file found")
|
return errors.New("error: image name is missing or no function file found")
|
||||||
@@ -512,7 +512,7 @@ func (a *routesCmd) headersList(c *cli.Context) error {
|
|||||||
return errors.New("this route has no headers")
|
return errors.New("this route has no headers")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(wrapper.Route.AppName, wrapper.Route.Path, "headers:")
|
fmt.Println(appName, wrapper.Route.Path, "headers:")
|
||||||
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, ' ', 0)
|
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, ' ', 0)
|
||||||
for k, v := range headers {
|
for k, v := range headers {
|
||||||
fmt.Fprint(w, k, ":\t", v, "\n")
|
fmt.Fprint(w, k, ":\t", v, "\n")
|
||||||
@@ -557,7 +557,7 @@ func (a *routesCmd) headersSet(c *cli.Context) error {
|
|||||||
return fmt.Errorf("error updating route configuration: %v", err)
|
return fmt.Errorf("error updating route configuration: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(wrapper.Route.AppName, wrapper.Route.Path, "headers updated", key, "with", value)
|
fmt.Println(appName, wrapper.Route.Path, "headers updated", key, "with", value)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -600,6 +600,6 @@ func (a *routesCmd) headersUnset(c *cli.Context) error {
|
|||||||
return fmt.Errorf("error updating route configuration: %v", err)
|
return fmt.Errorf("error updating route configuration: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(wrapper.Route.AppName, wrapper.Route.Path, "removed header", key)
|
fmt.Println(appName, wrapper.Route.Path, "removed header", key)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ func runflags() []cli.Flag {
|
|||||||
func (r *runCmd) run(c *cli.Context) error {
|
func (r *runCmd) run(c *cli.Context) error {
|
||||||
image := c.Args().First()
|
image := c.Args().First()
|
||||||
if image == "" {
|
if image == "" {
|
||||||
ff, err := findFuncfile()
|
ff, err := loadFuncfile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(*notFoundError); ok {
|
if _, ok := err.(*notFoundError); ok {
|
||||||
return errors.New("error: image name is missing or no function file found")
|
return errors.New("error: image name is missing or no function file found")
|
||||||
|
|||||||
Reference in New Issue
Block a user