fnctl: improve bump call and link version to the publish process (#267)

* fnctl: improve bump call and link version to the publish process

* fnctl: update README.md with latest changes for bump command
This commit is contained in:
C Cirello
2016-11-11 17:44:16 +01:00
committed by Seif Lotfy سيف لطفي
parent 610e6aaa4d
commit a4d44798dd
6 changed files with 133 additions and 39 deletions

View File

@@ -130,6 +130,7 @@ An example of a function file:
app: myapp
image: iron/hello
route: "/custom/route"
version: 0.0.1
type: sync
memory: 128
config:
@@ -150,6 +151,9 @@ route updated to use it.
`route` (optional) allows you to overwrite the calculated route from the path
position. You may use it to override the calculated route.
`version` represents current version of the function. When publishing, it is
appended to the image as a tag.
`type` (optional) allows you to set the type of the route. `sync`, for functions
whose response are sent back to the requester; or `async`, for functions that
are started and return a task ID to customer while it executes in background.
@@ -190,9 +194,8 @@ path result
/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.
`fnctl bump` will scan all IronFunctions whose `version` key in function file
follows [semver](http://semver.org/) rules and bump their version according.
`fnctl push` will scan all IronFunctions and push their images to Docker Hub,
and update their routes accordingly.

View File

@@ -1,12 +1,10 @@
package main
import (
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
bumper "github.com/giantswarm/semver-bump/bump"
"github.com/giantswarm/semver-bump/storage"
@@ -15,8 +13,6 @@ import (
var (
initialVersion = "0.0.1"
errVersionFileNotFound = errors.New("no VERSION file found for this function")
)
func bump() cli.Command {
@@ -48,26 +44,56 @@ func (b *bumpcmd) walker(path string, info os.FileInfo, err error, w io.Writer)
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)
funcfile, err := parsefuncfile(path)
if err != nil {
return err
}
version := bumper.NewSemverBumper(s, versionfile)
if funcfile.Version == "" {
img, ver := imageversion(funcfile.Image)
if ver == "" {
return nil
}
funcfile.Image = img
funcfile.Version = ver
} else if funcfile.Version != "" && strings.Contains(funcfile.Image, ":") {
return fmt.Errorf("cannot do version bump: this function has tag in its image name and version at same time. image: %s. version: %s", funcfile.Image, funcfile.Version)
}
s, err := storage.NewVersionStorage("local", funcfile.Version)
if err != nil {
return err
}
version := bumper.NewSemverBumper(s, "")
newver, err := version.BumpPatchVersion("", "")
if err != nil {
return err
}
if err := ioutil.WriteFile(versionfile, []byte(newver.String()), 0666); err != nil {
return err
funcfile.Version = newver.String()
return storefuncfile(path, funcfile)
}
func imageversion(image string) (name, ver string) {
tagpos := strings.Index(image, ":")
if tagpos == -1 {
return image, initialVersion
}
return nil
imgname, imgver := image[:tagpos], image[tagpos+1:]
s, err := storage.NewVersionStorage("local", imgver)
if err != nil {
return imgname, ""
}
version := bumper.NewSemverBumper(s, "")
v, err := version.GetCurrentVersion()
if err != nil {
return imgname, ""
}
return imgname, v.String()
}

29
fnctl/bump_test.go Normal file
View File

@@ -0,0 +1,29 @@
package main
import "testing"
func TestImageversion(t *testing.T) {
type args struct {
image string
}
tests := []struct {
name string
args args
wantName string
wantVer string
}{
{"tag absent", args{"owner/imagename"}, "owner/imagename", initialVersion},
{"non semver tag", args{"owner/imagename:tag"}, "owner/imagename", ""},
{"semver tag (M.m.p)", args{"owner/imagename:0.0.1"}, "owner/imagename", "0.0.1"},
{"semver tag (vM.m.p)", args{"owner/imagename:v0.0.1"}, "owner/imagename", "0.0.1"},
}
for _, tt := range tests {
gotName, gotVer := imageversion(tt.args.image)
if gotName != tt.wantName {
t.Errorf("%q. imageversion() gotName = %v, want %v", tt.name, gotName, tt.wantName)
}
if gotVer != tt.wantVer {
t.Errorf("%q. imageversion() gotVer = %v, want %v", tt.name, gotVer, tt.wantVer)
}
}
}

View File

@@ -35,27 +35,47 @@ var (
)
type funcfile struct {
App *string `yaml:"app,omitempty",json:"app,omitempty"`
Image string `yaml:"image,omitempty",json:"image,omitempty"`
Route *string `yaml:"route,omitempty",json:"route,omitempty"`
Type *string `yaml:"type,omitempty",json:"type,omitempty"`
Memory *int64 `yaml:"memory,omitempty",json:"memory,omitempty"`
Config map[string]string `yaml:"config,omitempty",json:"config,omitempty"`
Build []string `yaml:"build,omitempty",json:"build,omitempty"`
App *string `yaml:"app,omitempty",json:"app,omitempty"`
Image string `yaml:"image,omitempty",json:"image,omitempty"`
Version string `yaml:"version,omitempty",json:"version,omitempty"`
Route *string `yaml:"route,omitempty",json:"route,omitempty"`
Type *string `yaml:"type,omitempty",json:"type,omitempty"`
Memory *int64 `yaml:"memory,omitempty",json:"memory,omitempty"`
Config map[string]string `yaml:"config,omitempty",json:"config,omitempty"`
Build []string `yaml:"build,omitempty",json:"build,omitempty"`
}
func (ff *funcfile) FullImage() string {
image := ff.Image
if ff.Version != "" {
image = fmt.Sprintf("%s:%s", image, ff.Version)
}
return image
}
func parsefuncfile(path string) (*funcfile, error) {
ext := filepath.Ext(path)
switch ext {
case ".json":
return funcfileJSON(path)
return decodeFuncfileJSON(path)
case ".yaml", ".yml":
return funcfileYAML(path)
return decodeFuncfileYAML(path)
}
return nil, errUnexpectedFileFormat
}
func funcfileJSON(path string) (*funcfile, error) {
func storefuncfile(path string, ff *funcfile) error {
ext := filepath.Ext(path)
switch ext {
case ".json":
return encodeFuncfileJSON(path, ff)
case ".yaml", ".yml":
return encodeFuncfileYAML(path, ff)
}
return errUnexpectedFileFormat
}
func decodeFuncfileJSON(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)
@@ -65,7 +85,7 @@ func funcfileJSON(path string) (*funcfile, error) {
return ff, err
}
func funcfileYAML(path string) (*funcfile, error) {
func decodeFuncfileYAML(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)
@@ -75,6 +95,22 @@ func funcfileYAML(path string) (*funcfile, error) {
return ff, err
}
func encodeFuncfileJSON(path string, ff *funcfile) error {
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("could not open %s for encoding. Error: %v", path, err)
}
return json.NewEncoder(f).Encode(ff)
}
func encodeFuncfileYAML(path string, ff *funcfile) error {
b, err := yaml.Marshal(ff)
if err != nil {
return fmt.Errorf("could not encode function file. Error: %v", err)
}
return ioutil.WriteFile(path, b, os.FileMode(0644))
}
func isvalid(path string, info os.FileInfo) bool {
if info.IsDir() {
return false
@@ -143,7 +179,7 @@ func (c *commoncmd) scan(walker func(path string, info os.FileInfo, err error, w
var walked bool
w := tabwriter.NewWriter(os.Stdout, 0, 8, 0, '\t', 0)
w := tabwriter.NewWriter(os.Stdout, 0, 8, 1, ' ', 0)
fmt.Fprint(w, "path", "\t", "result", "\n")
err := filepath.Walk(c.wd, func(path string, info os.FileInfo, err error) error {
@@ -222,7 +258,7 @@ func (c commoncmd) buildfunc(path string) (*funcfile, error) {
return nil, err
}
if err := c.dockerbuild(path, funcfile.Image); err != nil {
if err := c.dockerbuild(path, funcfile); err != nil {
return nil, err
}
@@ -244,8 +280,8 @@ func (c commoncmd) localbuild(path string, steps []string) error {
return nil
}
func (c commoncmd) dockerbuild(path, image string) error {
cmd := exec.Command("docker", "build", "-t", image, filepath.Dir(path))
func (c commoncmd) dockerbuild(path string, ff *funcfile) error {
cmd := exec.Command("docker", "build", "-t", ff.FullImage(), filepath.Dir(path))
cmd.Stderr = c.verbwriter
cmd.Stdout = c.verbwriter
if err := cmd.Run(); err != nil {

View File

@@ -71,7 +71,7 @@ func (p *publishcmd) publish(path string) error {
return nil
}
if err := p.dockerpush(funcfile.Image); err != nil {
if err := p.dockerpush(funcfile); err != nil {
return err
}
@@ -82,8 +82,8 @@ func (p *publishcmd) publish(path string) error {
return nil
}
func (p publishcmd) dockerpush(image string) error {
cmd := exec.Command("docker", "push", image)
func (p publishcmd) dockerpush(ff *funcfile) error {
cmd := exec.Command("docker", "push", ff.FullImage())
cmd.Stderr = p.verbwriter
cmd.Stdout = p.verbwriter
if err := cmd.Run(); err != nil {
@@ -114,7 +114,7 @@ func (p *publishcmd) route(path string, ff *funcfile) error {
body := functions.RouteWrapper{
Route: functions.Route{
Path: *ff.Route,
Image: ff.Image,
Image: ff.FullImage(),
Memory: *ff.Memory,
Type_: *ff.Type,
Config: expandEnvConfig(ff.Config),

View File

@@ -52,7 +52,7 @@ func (p *pushcmd) push(path string) error {
return err
}
if err := p.dockerpush(funcfile.Image); err != nil {
if err := p.dockerpush(funcfile); err != nil {
return err
}