mirror of
https://github.com/fnproject/fn.git
synced 2022-10-28 21:29:17 +03:00
new commands fnctl build and bump (#204)
New commands & refectoring * fnctl: refactor code to improve reuse between commands build, bump and publish (formerly update) share a lot of code, this refactor ensure their logic are correctly reused. It renames update to publish, so it would be a strong diff between "update" and build. * fnctl: remove unnecessary dependency for build and bump * fnctl: improve code reuse between bump, build and publish Unify the use of walker function in all these three commands and drop dry-run support. * Code grooming - errcheck * fnctl: update README.md to be in sync with actual execution output * fnctl: move scan function to commoncmd structure * fnctl: change verbose flag handling does not use global variable anymore
This commit is contained in:
@@ -41,19 +41,18 @@ $ fnctl routes delete otherapp hello # delete route
|
||||
/hello deleted
|
||||
```
|
||||
|
||||
## Bulk Update
|
||||
## Publish
|
||||
|
||||
Also there is the update command that is going to scan all local directory for
|
||||
Also there is the publish command that is going to scan all local directory for
|
||||
functions, rebuild them and push them to Docker Hub and update them in
|
||||
IronFunction.
|
||||
|
||||
```sh
|
||||
$ fnctl update
|
||||
Updating for all functions.
|
||||
path action
|
||||
/app/hello updated
|
||||
$ fnctl publish
|
||||
path result
|
||||
/app/hello done
|
||||
/app/hello-sync error: no Dockerfile found for this function
|
||||
/app/test updated
|
||||
/app/test done
|
||||
```
|
||||
|
||||
It works by scanning all children directories of the current working directory,
|
||||
@@ -83,11 +82,10 @@ following this convention:
|
||||
It will render this pattern of updates:
|
||||
|
||||
```sh
|
||||
$ fnctl update
|
||||
Updating for all functions.
|
||||
path action
|
||||
/myapp/route1/subroute1 updated
|
||||
/other/route1 updated
|
||||
$ fnctl publish
|
||||
path result
|
||||
/myapp/route1/subroute1 done
|
||||
/other/route1 done
|
||||
```
|
||||
|
||||
It means that first subdirectory are always considered app names (e.g. `myapp`
|
||||
@@ -120,3 +118,31 @@ position. You may use it to override the calculated route.
|
||||
`build` (optional) is an array of shell calls which are used to helping building
|
||||
the image. These calls are executed before `fnctl` calls `docker build` and
|
||||
`docker push`.
|
||||
|
||||
## Build and Bump
|
||||
|
||||
When dealing with a lot of functions you might find yourself making lots of
|
||||
individual calls. `fnctl` offers two command to help you with that: `build` and
|
||||
`bump`.
|
||||
|
||||
```sh
|
||||
$ fnctl build
|
||||
path result
|
||||
/app/hello done
|
||||
/app/test done
|
||||
```
|
||||
|
||||
`fnctl build` is similar to `publish` except it neither publishes the resulting
|
||||
docker image to Docker Hub nor updates the routes in IronFunctions server.
|
||||
|
||||
```sh
|
||||
$ fnctl bump
|
||||
path result
|
||||
/app/hello done
|
||||
/app/test done
|
||||
```
|
||||
|
||||
`fnctl bump` will scan all IronFunctions for files named `VERSION` and bump
|
||||
their version according to [semver](http://semver.org/) rules. In their absence,
|
||||
it will skip.
|
||||
|
||||
|
||||
41
fnctl/build.go
Normal file
41
fnctl/build.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func build() cli.Command {
|
||||
cmd := buildcmd{commoncmd: &commoncmd{}}
|
||||
flags := append([]cli.Flag{}, cmd.flags()...)
|
||||
return cli.Command{
|
||||
Name: "build",
|
||||
Usage: "build function version",
|
||||
Flags: flags,
|
||||
Action: cmd.scan,
|
||||
}
|
||||
}
|
||||
|
||||
type buildcmd struct {
|
||||
*commoncmd
|
||||
}
|
||||
|
||||
func (b *buildcmd) scan(c *cli.Context) error {
|
||||
b.commoncmd.scan(b.walker)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *buildcmd) walker(path string, info os.FileInfo, err error, w io.Writer) error {
|
||||
walker(path, info, err, w, b.build)
|
||||
return nil
|
||||
}
|
||||
|
||||
// build will take the found valid function and build it
|
||||
func (b *buildcmd) build(path string) error {
|
||||
fmt.Fprintln(b.verbwriter, "building", path)
|
||||
_, err := b.buildfunc(path)
|
||||
return err
|
||||
}
|
||||
73
fnctl/bump.go
Normal file
73
fnctl/bump.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
bumper "github.com/giantswarm/semver-bump/bump"
|
||||
"github.com/giantswarm/semver-bump/storage"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var (
|
||||
initialVersion = "0.0.1"
|
||||
|
||||
errVersionFileNotFound = errors.New("no VERSION file found for this function")
|
||||
)
|
||||
|
||||
func bump() cli.Command {
|
||||
cmd := bumpcmd{commoncmd: &commoncmd{}}
|
||||
flags := append([]cli.Flag{}, cmd.flags()...)
|
||||
return cli.Command{
|
||||
Name: "bump",
|
||||
Usage: "bump function version",
|
||||
Flags: flags,
|
||||
Action: cmd.scan,
|
||||
}
|
||||
}
|
||||
|
||||
type bumpcmd struct {
|
||||
*commoncmd
|
||||
}
|
||||
|
||||
func (b *bumpcmd) scan(c *cli.Context) error {
|
||||
b.commoncmd.scan(b.walker)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *bumpcmd) walker(path string, info os.FileInfo, err error, w io.Writer) error {
|
||||
walker(path, info, err, w, b.bump)
|
||||
return nil
|
||||
}
|
||||
|
||||
// bump will take the found valid function and bump its version
|
||||
func (b *bumpcmd) bump(path string) error {
|
||||
fmt.Fprintln(b.verbwriter, "bumping version for", path)
|
||||
|
||||
dir := filepath.Dir(path)
|
||||
versionfile := filepath.Join(dir, "VERSION")
|
||||
if _, err := os.Stat(versionfile); os.IsNotExist(err) {
|
||||
return errVersionFileNotFound
|
||||
}
|
||||
|
||||
s, err := storage.NewVersionStorage("file", initialVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
version := bumper.NewSemverBumper(s, versionfile)
|
||||
newver, err := version.BumpPatchVersion("", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(versionfile, []byte(newver.String()), 0666); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
186
fnctl/common.go
Normal file
186
fnctl/common.go
Normal file
@@ -0,0 +1,186 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
validfn = [...]string{
|
||||
"functions.yaml",
|
||||
"functions.yml",
|
||||
"fn.yaml",
|
||||
"fn.yml",
|
||||
"functions.json",
|
||||
"fn.json",
|
||||
}
|
||||
|
||||
errDockerFileNotFound = errors.New("no Dockerfile found for this function")
|
||||
errUnexpectedFileFormat = errors.New("unexpected file format for function file")
|
||||
)
|
||||
|
||||
type funcfile struct {
|
||||
App *string
|
||||
Image string
|
||||
Route *string
|
||||
Build []string
|
||||
}
|
||||
|
||||
func parsefuncfile(path string) (*funcfile, error) {
|
||||
ext := filepath.Ext(path)
|
||||
switch ext {
|
||||
case ".json":
|
||||
return funcfileJSON(path)
|
||||
case ".yaml", ".yml":
|
||||
return funcfileYAML(path)
|
||||
}
|
||||
return nil, errUnexpectedFileFormat
|
||||
}
|
||||
|
||||
func funcfileJSON(path string) (*funcfile, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not open %s for parsing. Error: %v", path, err)
|
||||
}
|
||||
ff := new(funcfile)
|
||||
err = json.NewDecoder(f).Decode(ff)
|
||||
return ff, err
|
||||
}
|
||||
|
||||
func funcfileYAML(path string) (*funcfile, error) {
|
||||
b, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not open %s for parsing. Error: %v", path, err)
|
||||
}
|
||||
ff := new(funcfile)
|
||||
err = yaml.Unmarshal(b, ff)
|
||||
return ff, err
|
||||
}
|
||||
|
||||
func isvalid(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
|
||||
}
|
||||
|
||||
func walker(path string, info os.FileInfo, err error, w io.Writer, f func(path string) error) {
|
||||
if !isvalid(path, info) {
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprint(w, path, "\t")
|
||||
if err := f(path); err != nil {
|
||||
fmt.Fprintln(w, err)
|
||||
} else {
|
||||
fmt.Fprintln(w, "done")
|
||||
}
|
||||
}
|
||||
|
||||
type commoncmd struct {
|
||||
wd string
|
||||
verbose 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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *commoncmd) scan(walker func(path string, info os.FileInfo, err error, w io.Writer) error) {
|
||||
c.verbwriter = ioutil.Discard
|
||||
if c.verbose {
|
||||
c.verbwriter = os.Stderr
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 8, 0, '\t', 0)
|
||||
fmt.Fprint(w, "path", "\t", "result", "\n")
|
||||
|
||||
err := filepath.Walk(c.wd, func(path string, info os.FileInfo, err error) error {
|
||||
return walker(path, info, err, w)
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Fprintf(c.verbwriter, "file walk error: %s\n", err)
|
||||
}
|
||||
|
||||
w.Flush()
|
||||
}
|
||||
|
||||
func (c commoncmd) buildfunc(path string) (*funcfile, error) {
|
||||
dir := filepath.Dir(path)
|
||||
dockerfile := filepath.Join(dir, "Dockerfile")
|
||||
if _, err := os.Stat(dockerfile); os.IsNotExist(err) {
|
||||
return nil, errDockerFileNotFound
|
||||
}
|
||||
|
||||
funcfile, err := parsefuncfile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.localbuild(path, funcfile.Build); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.dockerbuild(path, funcfile.Image); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return funcfile, nil
|
||||
}
|
||||
|
||||
func (c commoncmd) localbuild(path string, steps []string) error {
|
||||
for _, cmd := range steps {
|
||||
exe := exec.Command("/bin/sh", "-c", cmd)
|
||||
exe.Dir = filepath.Dir(path)
|
||||
out, err := exe.CombinedOutput()
|
||||
fmt.Fprintf(c.verbwriter, "- %s:\n%s\n", cmd, out)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error running command %v (%v)", cmd, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c commoncmd) dockerbuild(path, image string) error {
|
||||
out, err := exec.Command("docker", "build", "-t", image, filepath.Dir(path)).CombinedOutput()
|
||||
fmt.Fprintf(c.verbwriter, "%s\n", out)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error running docker build: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
99
fnctl/glide.lock
generated
99
fnctl/glide.lock
generated
@@ -1,12 +1,107 @@
|
||||
hash: 9bc670813e50a1c5d7dd9703d2a0b33bcf812dafdde28fc98886bb2a3c5e441b
|
||||
updated: 2016-10-26T15:13:34.156397533+01:00
|
||||
hash: 6c0bc544bcabed5a74a7eeefd53bd6e088f10f6deee1a9fa65bb8b5e512b93aa
|
||||
updated: 2016-10-31T15:05:00.722579591-07:00
|
||||
imports:
|
||||
- name: github.com/aws/aws-sdk-go
|
||||
version: 32cdc88aa5cd2ba4afa049da884aaf9a3d103ef4
|
||||
subpackages:
|
||||
- aws/awserr
|
||||
- aws/credentials
|
||||
- name: github.com/Azure/go-ansiterm
|
||||
version: fa152c58bc15761d0200cb75fe958b89a9d4888e
|
||||
subpackages:
|
||||
- winterm
|
||||
- name: github.com/coreos/go-semver
|
||||
version: 8ab6407b697782a06568d4b7f1db25550ec2e4c6
|
||||
subpackages:
|
||||
- semver
|
||||
- name: github.com/docker/docker
|
||||
version: 8cced8702261224ffd726774812eb50e8a600e52
|
||||
subpackages:
|
||||
- api/types/blkiodev
|
||||
- api/types/container
|
||||
- api/types/filters
|
||||
- api/types/mount
|
||||
- api/types/strslice
|
||||
- api/types/swarm
|
||||
- api/types/versions
|
||||
- opts
|
||||
- pkg/archive
|
||||
- pkg/fileutils
|
||||
- pkg/homedir
|
||||
- pkg/idtools
|
||||
- pkg/ioutils
|
||||
- pkg/jsonlog
|
||||
- pkg/jsonmessage
|
||||
- pkg/longpath
|
||||
- pkg/pools
|
||||
- pkg/promise
|
||||
- pkg/stdcopy
|
||||
- pkg/system
|
||||
- pkg/term
|
||||
- pkg/term/windows
|
||||
- name: github.com/docker/go-connections
|
||||
version: f512407a188ecb16f31a33dbc9c4e4814afc1b03
|
||||
subpackages:
|
||||
- nat
|
||||
- name: github.com/docker/go-units
|
||||
version: 8a7beacffa3009a9ac66bad506b18ffdd110cf97
|
||||
- name: github.com/fsouza/go-dockerclient
|
||||
version: 3162ed100df52ad76c94cdf1b8b2a45d4f5e203d
|
||||
- name: github.com/giantswarm/semver-bump
|
||||
version: 7ec6ac8985c24dd50b4942f9a908d13cdfe70f23
|
||||
subpackages:
|
||||
- bump
|
||||
- storage
|
||||
- name: github.com/go-ini/ini
|
||||
version: 6e4869b434bd001f6983749881c7ead3545887d8
|
||||
- name: github.com/go-resty/resty
|
||||
version: 1a3bb60986d90e32c04575111b1ccb8eab24a3e5
|
||||
- name: github.com/hashicorp/go-cleanhttp
|
||||
version: ad28ea4487f05916463e2423a55166280e8254b5
|
||||
- name: github.com/iron-io/functions_go
|
||||
version: 584f4a6e13b53370f036012347cf0571128209f0
|
||||
- name: github.com/iron-io/iron_go3
|
||||
version: b50ecf8ff90187fc5fabccd9d028dd461adce4ee
|
||||
subpackages:
|
||||
- api
|
||||
- config
|
||||
- worker
|
||||
- name: github.com/iron-io/lambda
|
||||
version: 197598b21c6918d143244cc69d4d443f062d3c78
|
||||
subpackages:
|
||||
- lambda
|
||||
- name: github.com/juju/errgo
|
||||
version: 08cceb5d0b5331634b9826762a8fd53b29b86ad8
|
||||
subpackages:
|
||||
- errors
|
||||
- name: github.com/Microsoft/go-winio
|
||||
version: ce2922f643c8fd76b46cadc7f404a06282678b34
|
||||
- name: github.com/opencontainers/runc
|
||||
version: bc462c96bf7b15b68ab40e86335cefcb692707c1
|
||||
subpackages:
|
||||
- libcontainer/system
|
||||
- libcontainer/user
|
||||
- name: github.com/satori/go.uuid
|
||||
version: b061729afc07e77a8aa4fad0a2fd840958f1942a
|
||||
- name: github.com/Sirupsen/logrus
|
||||
version: 380f64d344b252a007a59baa61f31820f59cba89
|
||||
- name: github.com/urfave/cli
|
||||
version: 55f715e28c46073d0e217e2ce8eb46b0b45e3db6
|
||||
- name: golang.org/x/crypto
|
||||
version: 9477e0b78b9ac3d0b03822fd95422e2fe07627cd
|
||||
subpackages:
|
||||
- ssh/terminal
|
||||
- name: golang.org/x/net
|
||||
version: daba796358cd2742b75aae05761f1b898c9f6a5c
|
||||
subpackages:
|
||||
- context
|
||||
- context/ctxhttp
|
||||
- publicsuffix
|
||||
- name: golang.org/x/sys
|
||||
version: c200b10b5d5e122be351b67af224adc6128af5bf
|
||||
subpackages:
|
||||
- unix
|
||||
- windows
|
||||
- name: gopkg.in/yaml.v2
|
||||
version: a5b47d31c556af34a302ce5d659e6fea44d90de0
|
||||
testImports: []
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
package: github.com/iron-io/functions/fnctl
|
||||
import:
|
||||
- package: github.com/urfave/cli
|
||||
- package: github.com/docker/docker
|
||||
subpackages:
|
||||
- pkg/jsonmessage
|
||||
- package: github.com/giantswarm/semver-bump
|
||||
subpackages:
|
||||
- bump
|
||||
- storage
|
||||
- package: github.com/iron-io/functions_go
|
||||
- package: github.com/aws/aws-sdk-go
|
||||
version: ^1.4.20
|
||||
- package: github.com/iron-io/iron_go3
|
||||
subpackages:
|
||||
- config
|
||||
- package: github.com/iron-io/lambda
|
||||
version: ^0.1.0
|
||||
subpackages:
|
||||
- lambda
|
||||
- package: github.com/urfave/cli
|
||||
- package: golang.org/x/crypto
|
||||
subpackages:
|
||||
- ssh/terminal
|
||||
- package: gopkg.in/yaml.v2
|
||||
|
||||
@@ -19,9 +19,11 @@ func main() {
|
||||
app.CommandNotFound = func(c *cli.Context, cmd string) { fmt.Fprintf(os.Stderr, "command not found: %v\n", cmd) }
|
||||
app.Commands = []cli.Command{
|
||||
apps(),
|
||||
routes(),
|
||||
update(),
|
||||
build(),
|
||||
bump(),
|
||||
lambda(),
|
||||
publish(),
|
||||
routes(),
|
||||
}
|
||||
app.Run(os.Args)
|
||||
}
|
||||
|
||||
144
fnctl/publish.go
Normal file
144
fnctl/publish.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
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()...)
|
||||
flags = append(flags, confFlags(&cmd.Configuration)...)
|
||||
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
|
||||
|
||||
dry bool
|
||||
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, w io.Writer) error {
|
||||
walker(path, info, err, w, 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.Image); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := p.route(path, funcfile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p publishcmd) dockerpush(image string) error {
|
||||
out, err := exec.Command("docker", "push", image).CombinedOutput()
|
||||
fmt.Fprintf(p.verbwriter, "%s\n", out)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error running docker push: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *publishcmd) route(path string, ff *funcfile) error {
|
||||
resetBasePath(&p.Configuration)
|
||||
|
||||
an, r := extractAppNameRoute(path)
|
||||
if ff.App == nil {
|
||||
ff.App = &an
|
||||
}
|
||||
if ff.Route == nil {
|
||||
ff.Route = &r
|
||||
}
|
||||
|
||||
body := functions.RouteWrapper{
|
||||
Route: functions.Route{
|
||||
Path: *ff.Route,
|
||||
Image: ff.Image,
|
||||
},
|
||||
}
|
||||
|
||||
fmt.Fprintf(p.verbwriter, "updating API with appName: %s route: %s image: %s \n", *ff.App, *ff.Route, ff.Image)
|
||||
|
||||
_, _, err := p.AppsAppRoutesPost(*ff.App, body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting routes: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractAppNameRoute(path string) (appName, route string) {
|
||||
|
||||
// The idea here is to extract the root-most directory name
|
||||
// as application name, it turns out that stdlib tools are great to
|
||||
// extract the deepest one. Thus, we revert the string and use the
|
||||
// stdlib as it is - and revert back to its normal content. Not fastest
|
||||
// ever, but it is simple.
|
||||
|
||||
rpath := reverse(path)
|
||||
rroute, rappName := filepath.Split(rpath)
|
||||
route = filepath.Dir(reverse(rroute))
|
||||
return reverse(rappName), route
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -139,7 +139,7 @@ func (a *routesCmd) create(c *cli.Context) error {
|
||||
return fmt.Errorf("error creating route: %v", err)
|
||||
}
|
||||
if wrapper.Route.Path == "" || wrapper.Route.Image == "" {
|
||||
return fmt.Errorf("could not create this route (%s at %s), check if route path is correct.", route, appName)
|
||||
return fmt.Errorf("could not create this route (%s at %s), check if route path is correct", route, appName)
|
||||
}
|
||||
|
||||
fmt.Println(wrapper.Route.Path, "created with", wrapper.Route.Image)
|
||||
|
||||
292
fnctl/update.go
292
fnctl/update.go
@@ -1,292 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"text/tabwriter"
|
||||
|
||||
functions "github.com/iron-io/functions_go"
|
||||
"github.com/urfave/cli"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
validfn = [...]string{
|
||||
"functions.yaml",
|
||||
"functions.yml",
|
||||
"fn.yaml",
|
||||
"fn.yml",
|
||||
"functions.json",
|
||||
"fn.json",
|
||||
}
|
||||
|
||||
errDockerFileNotFound = errors.New("no Dockerfile found for this function")
|
||||
errUnexpectedFileFormat = errors.New("unexpected file format for function file")
|
||||
verbwriter = ioutil.Discard
|
||||
)
|
||||
|
||||
func update() cli.Command {
|
||||
cmd := updatecmd{RoutesApi: functions.NewRoutesApi()}
|
||||
var flags []cli.Flag
|
||||
flags = append(flags, cmd.flags()...)
|
||||
flags = append(flags, confFlags(&cmd.Configuration)...)
|
||||
return cli.Command{
|
||||
Name: "update",
|
||||
Usage: "scan local directory for functions, build and update them.",
|
||||
Flags: flags,
|
||||
Action: cmd.scan,
|
||||
}
|
||||
}
|
||||
|
||||
type updatecmd struct {
|
||||
*functions.RoutesApi
|
||||
|
||||
wd string
|
||||
dry bool
|
||||
skippush bool
|
||||
verbose bool
|
||||
}
|
||||
|
||||
func (u *updatecmd) flags() []cli.Flag {
|
||||
return []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "d",
|
||||
Usage: "working directory",
|
||||
Destination: &u.wd,
|
||||
EnvVar: "WORK_DIR",
|
||||
Value: "./",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-push",
|
||||
Usage: "does not push Docker built images onto Docker Hub - useful for local development.",
|
||||
Destination: &u.skippush,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "dry-run",
|
||||
Usage: "display how update will proceed when executed",
|
||||
Destination: &u.dry,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "v",
|
||||
Usage: "verbose mode",
|
||||
Destination: &u.verbose,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (u *updatecmd) scan(c *cli.Context) error {
|
||||
if u.verbose {
|
||||
verbwriter = os.Stderr
|
||||
}
|
||||
|
||||
os.Chdir(u.wd)
|
||||
|
||||
w := tabwriter.NewWriter(os.Stdout, 0, 8, 0, '\t', 0)
|
||||
fmt.Fprint(w, "path", "\t", "action", "\n")
|
||||
|
||||
filepath.Walk(u.wd, func(path string, info os.FileInfo, err error) error {
|
||||
return u.walker(path, info, err, w)
|
||||
})
|
||||
|
||||
w.Flush()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *updatecmd) walker(path string, info os.FileInfo, err error, w io.Writer) error {
|
||||
if !isvalid(path, info) {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Fprint(w, path, "\t")
|
||||
if u.dry {
|
||||
fmt.Fprintln(w, "dry-run")
|
||||
} else if err := u.update(path); err != nil {
|
||||
fmt.Fprintln(w, err)
|
||||
} else {
|
||||
fmt.Fprintln(w, "updated")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func isvalid(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
|
||||
}
|
||||
|
||||
// update 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 (u *updatecmd) update(path string) error {
|
||||
fmt.Fprintln(verbwriter, "deploying", path)
|
||||
|
||||
dir := filepath.Dir(path)
|
||||
dockerfile := filepath.Join(dir, "Dockerfile")
|
||||
if _, err := os.Stat(dockerfile); os.IsNotExist(err) {
|
||||
return errDockerFileNotFound
|
||||
}
|
||||
|
||||
funcfile, err := u.parse(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if funcfile.Build != nil {
|
||||
if err := u.localbuild(path, funcfile.Build); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := u.dockerbuild(path, funcfile.Image); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := u.route(path, funcfile); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *updatecmd) parse(path string) (*funcfile, error) {
|
||||
ext := filepath.Ext(path)
|
||||
switch ext {
|
||||
case ".json":
|
||||
return parseJSON(path)
|
||||
case ".yaml", ".yml":
|
||||
return parseYAML(path)
|
||||
}
|
||||
return nil, errUnexpectedFileFormat
|
||||
}
|
||||
|
||||
func parseJSON(path string) (*funcfile, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not open %s for parsing. Error: %v", path, err)
|
||||
}
|
||||
ff := new(funcfile)
|
||||
err = json.NewDecoder(f).Decode(ff)
|
||||
return ff, err
|
||||
}
|
||||
|
||||
func parseYAML(path string) (*funcfile, error) {
|
||||
b, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not open %s for parsing. Error: %v", path, err)
|
||||
}
|
||||
ff := new(funcfile)
|
||||
err = yaml.Unmarshal(b, ff)
|
||||
return ff, err
|
||||
}
|
||||
|
||||
type funcfile struct {
|
||||
App *string
|
||||
Image string
|
||||
Route *string
|
||||
Build []string
|
||||
}
|
||||
|
||||
func (u *updatecmd) localbuild(path string, steps []string) error {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot get current working directory. err: %v", err)
|
||||
}
|
||||
|
||||
fullwd := filepath.Join(wd, filepath.Dir(path))
|
||||
for _, cmd := range steps {
|
||||
c := exec.Command("/bin/sh", "-c", cmd)
|
||||
c.Dir = fullwd
|
||||
out, err := c.CombinedOutput()
|
||||
fmt.Fprintf(verbwriter, "- %s:\n%s\n", cmd, out)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error running command %v (%v)", cmd, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *updatecmd) dockerbuild(path, image string) error {
|
||||
cmds := [][]string{
|
||||
{"docker", "build", "-t", image, filepath.Dir(path)},
|
||||
}
|
||||
if !u.skippush {
|
||||
cmds = append(cmds, []string{"docker", "push", image})
|
||||
}
|
||||
|
||||
for _, cmd := range cmds {
|
||||
out, err := exec.Command(cmd[0], cmd[1:]...).CombinedOutput()
|
||||
fmt.Fprintf(verbwriter, "%s\n", out)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error running command %v (%v)", cmd, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *updatecmd) route(path string, ff *funcfile) error {
|
||||
resetBasePath(&u.Configuration)
|
||||
|
||||
an, r := extractAppNameRoute(path)
|
||||
if ff.App == nil {
|
||||
ff.App = &an
|
||||
}
|
||||
if ff.Route == nil {
|
||||
ff.Route = &r
|
||||
}
|
||||
|
||||
body := functions.RouteWrapper{
|
||||
Route: functions.Route{
|
||||
Path: *ff.Route,
|
||||
Image: ff.Image,
|
||||
},
|
||||
}
|
||||
|
||||
fmt.Fprintf(verbwriter, "updating API with appName: %s route: %s image: %s \n", *ff.App, *ff.Route, ff.Image)
|
||||
|
||||
_, _, err := u.AppsAppRoutesPost(*ff.App, body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting routes: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractAppNameRoute(path string) (appName, route string) {
|
||||
|
||||
// The idea here is to extract the root-most directory name
|
||||
// as application name, it turns out that stdlib tools are great to
|
||||
// extract the deepest one. Thus, we revert the string and use the
|
||||
// stdlib as it is - and revert back to its normal content. Not fastest
|
||||
// ever, but it is simple.
|
||||
|
||||
rpath := reverse(path)
|
||||
rroute, rappName := filepath.Split(rpath)
|
||||
route = filepath.Dir(reverse(rroute))
|
||||
return reverse(rappName), route
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user