diff --git a/commands/build.go b/commands/build.go index e0a04d4c..aa9aa329 100644 --- a/commands/build.go +++ b/commands/build.go @@ -74,6 +74,9 @@ func (b *buildcmd) build(c *cli.Context) error { return err } + buildArgs := c.StringSlice("build-arg") + verbose := c.GlobalBool("verbose") + switch common.GetFuncYamlVersion(ffV) { case common.LatestYamlVersion: fpath, ff, err := common.FindAndParseFuncFileV20180708(dir) @@ -81,8 +84,7 @@ func (b *buildcmd) build(c *cli.Context) error { return err } - buildArgs := c.StringSlice("build-arg") - ff, err = common.BuildFuncV20180708(c.GlobalBool("verbose"), fpath, ff, buildArgs, b.noCache) + ff, err = common.BuildFuncV20180708(buildArgs, verbose, b.noCache, fpath, ff) if err != nil { return err } @@ -96,8 +98,7 @@ func (b *buildcmd) build(c *cli.Context) error { return err } - buildArgs := c.StringSlice("build-arg") - ff, err = common.BuildFunc(c.GlobalBool("verbose"), fpath, ff, buildArgs, b.noCache) + ff, err = common.BuildFunc(buildArgs, verbose, b.noCache, fpath, ff) if err != nil { return err } diff --git a/commands/deploy.go b/commands/deploy.go index a9f9d308..275de0da 100644 --- a/commands/deploy.go +++ b/commands/deploy.go @@ -3,22 +3,14 @@ package commands import ( "errors" "fmt" - "os" "path/filepath" - "strings" - "time" - client "github.com/fnproject/cli/client" - common "github.com/fnproject/cli/common" - apps "github.com/fnproject/cli/objects/app" - function "github.com/fnproject/cli/objects/fn" - route "github.com/fnproject/cli/objects/route" - trigger "github.com/fnproject/cli/objects/trigger" + "github.com/fnproject/cli/client" + "github.com/fnproject/cli/common" fnclient "github.com/fnproject/fn_go/client" clientApps "github.com/fnproject/fn_go/client/apps" v2Client "github.com/fnproject/fn_go/clientv2" - models "github.com/fnproject/fn_go/models" - modelsV2 "github.com/fnproject/fn_go/modelsv2" + "github.com/fnproject/fn_go/models" "github.com/urfave/cli" ) @@ -147,229 +139,42 @@ func (p *deploycmd) deploy(c *cli.Context) error { return errors.New("App name must be provided, try `--app APP_NAME`") } + deployConfig := &DeployConfig{ + c.StringSlice("build-arg"), + c.GlobalBool("verbose"), + p.noBump, + p.local, + p.noCache} + + appDir := c.String("dir") + if p.all { - return p.deployAll(c, appName, appf) + return DeployAll(p.client, deployConfig, appDir, appName, appf) } - return p.deploySingle(c, appName, appf) + + return p.deploySingle(c, deployConfig, appName, appf) } // deploySingle deploys a single function, either the current directory or if in the context // of an app and user provides relative path as the first arg, it will deploy that function. -func (p *deploycmd) deploySingle(c *cli.Context, appName string, appf *common.AppFile) error { +func (p *deploycmd) deploySingle(c *cli.Context, deployConfig *DeployConfig, appName string, appf *common.AppFile) error { var dir string wd := common.GetWd() - if c.String("working-dir") != "" { - dir = c.String("working-dir") - } else { - // if we're in the context of an app, first arg is path to the function - path := c.Args().First() - if path != "" { - fmt.Printf("Deploying function at: /%s\n", path) - } - dir = filepath.Join(wd, path) - } - - err := os.Chdir(dir) - if err != nil { - return err - } - defer os.Chdir(wd) - - ffV, err := common.ReadInFuncFile() - if err != nil { - return err - } - - switch common.GetFuncYamlVersion(ffV) { - case common.LatestYamlVersion: - fpath, ff, err := common.FindAndParseFuncFileV20180708(dir) - if err != nil { - return err - } - if appf != nil { - if dir == wd { - setFuncInfoV20180708(ff, appf.Name) - } - } - - if appf != nil { - err = p.updateAppConfig(appf) - if err != nil { - return fmt.Errorf("Failed to update app config: %v", err) - } - } - - return p.deployFuncV20180708(c, appName, wd, fpath, ff) - default: - fpath, ff, err := common.FindAndParseFuncfile(dir) - if err != nil { - return err - } - if appf != nil { - if dir == wd { - setRootFuncInfo(ff, appf.Name) - } - } - - if appf != nil { - err = p.updateAppConfig(appf) - if err != nil { - return fmt.Errorf("Failed to update app config: %v", err) - } - } - - return p.deployFunc(c, appName, wd, fpath, ff) - } -} - -// deployAll deploys all functions in an app. -func (p *deploycmd) deployAll(c *cli.Context, appName string, appf *common.AppFile) error { - if appf != nil { - err := p.updateAppConfig(appf) - if err != nil { - return fmt.Errorf("Failed to update app config: %v", err) - } - } - - var dir string - wd := common.GetWd() + fpath := c.Args().First() + workingDir := c.String("working-dir") - if c.String("dir") != "" { - dir = c.String("dir") + if workingDir != "" { + dir = workingDir } else { - dir = wd - } - - var funcFound bool - err := common.WalkFuncs(dir, func(path string, ff *common.FuncFile, err error) error { - if err != nil { // probably some issue with funcfile parsing, can decide to handle this differently if we'd like - return err - } - dir := filepath.Dir(path) - if dir == wd { - setRootFuncInfo(ff, appName) - } else { - // change dirs - err = os.Chdir(dir) - if err != nil { - return err - } - p2 := strings.TrimPrefix(dir, wd) - if ff.Name == "" { - ff.Name = strings.Replace(p2, "/", "-", -1) - if strings.HasPrefix(ff.Name, "-") { - ff.Name = ff.Name[1:] - } - // todo: should we prefix appname too? - } - if ff.Path == "" { - ff.Path = p2 - } - } - - err = p.deployFunc(c, appName, wd, path, ff) - if err != nil { - return fmt.Errorf("deploy error on %s: %v", path, err) - } - - now := time.Now() - os.Chtimes(path, now, now) - funcFound = true - return nil - }) - if err != nil { - return err - } - - if !funcFound { - return errors.New("No functions found to deploy") - } - - return nil -} - -// deployFunc performs several actions to deploy to a functions server. -// Parse func.yaml file, bump version, build image, push to registry, and -// finally it will update function's route. Optionally, -// the route can be overriden inside the func.yaml file. -func (p *deploycmd) deployFunc(c *cli.Context, appName, baseDir, funcfilePath string, funcfile *common.FuncFile) error { - if appName == "" { - return errors.New("App name must be provided, try `--app APP_NAME`") - } - dir := filepath.Dir(funcfilePath) - // get name from directory if it's not defined - if funcfile.Name == "" { - funcfile.Name = filepath.Base(filepath.Dir(funcfilePath)) // todo: should probably make a copy of ff before changing it - } - if funcfile.Path == "" { - if dir == "." { - funcfile.Path = "/" - } else { - funcfile.Path = "/" + filepath.Base(dir) - } - - } - fmt.Printf("Deploying %s to app: %s at path: %s\n", funcfile.Name, appName, funcfile.Path) - - var err error - if !p.noBump { - funcfile2, err := common.BumpIt(funcfilePath, common.Patch) - if err != nil { - return err - } - funcfile.Version = funcfile2.Version - // TODO: this whole funcfile handling needs some love, way too confusing. Only bump makes permanent changes to it. - } - - buildArgs := c.StringSlice("build-arg") - _, err = common.BuildFunc(c.GlobalBool("verbose"), funcfilePath, funcfile, buildArgs, p.noCache) - if err != nil { - return err - } - - if !p.local { - if err := common.DockerPush(funcfile); err != nil { - return err - } - } - - return p.updateRoute(c, appName, funcfile) -} - -func (p *deploycmd) deployFuncV20180708(c *cli.Context, appName, baseDir, funcfilePath string, funcfile *common.FuncFileV20180708) error { - if appName == "" { - return errors.New("App name must be provided, try `--app APP_NAME`") - } - - if funcfile.Name == "" { - funcfile.Name = filepath.Base(filepath.Dir(funcfilePath)) // todo: should probably make a copy of ff before changing it - } - fmt.Printf("Deploying %s to app: %s\n", funcfile.Name, appName) - - var err error - if !p.noBump { - funcfile2, err := common.BumpItV20180708(funcfilePath, common.Patch) - if err != nil { - return err - } - funcfile.Version = funcfile2.Version - // TODO: this whole funcfile handling needs some love, way too confusing. Only bump makes permanent changes to it. - } - - buildArgs := c.StringSlice("build-arg") - _, err = common.BuildFuncV20180708(c.GlobalBool("verbose"), funcfilePath, funcfile, buildArgs, p.noCache) - if err != nil { - return err - } - - if !p.local { - if err := common.DockerPushV20180708(funcfile); err != nil { - return err + // if we're in the context of an app, first arg is path to the function + if fpath != "" { + fmt.Printf("Deploying function at: /%s\n", fpath) } + dir = filepath.Join(wd, fpath) } - return p.updateFunction(c, appName, funcfile) + return DeploySingle(p.client, p.clientV2, deployConfig, dir, appName, appf) } func setRootFuncInfo(ff *common.FuncFile, appName string) { @@ -390,96 +195,7 @@ func setFuncInfoV20180708(ff *common.FuncFileV20180708, appName string) { } } -func (p *deploycmd) updateRoute(c *cli.Context, appName string, ff *common.FuncFile) error { - fmt.Printf("Updating route %s using image %s...\n", ff.Path, ff.ImageName()) - rt := &models.Route{} - if err := route.WithFuncFile(ff, rt); err != nil { - return fmt.Errorf("Error getting route with funcfile: %s", err) - } - return route.PutRoute(p.client, appName, ff.Path, rt) -} - -func (p *deploycmd) updateFunction(c *cli.Context, appName string, ff *common.FuncFileV20180708) error { - fmt.Printf("Updating function %s using image %s...\n", ff.Name, ff.ImageNameV20180708()) - fn := &modelsV2.Fn{} - if err := function.WithFuncFileV20180708(ff, fn); err != nil { - return fmt.Errorf("Error getting route with funcfile: %s", err) - } - - app, err := apps.GetAppByName(appName) - if err != nil { - app = &models.App{ - Name: appName, - } - - err = apps.CreateApp(p.client, app) - if err != nil { - return err - } - app, err = apps.GetAppByName(appName) - if err != nil { - return err - } - } - - fnRes, err := function.GetFnByName(p.clientV2, app.ID, ff.Name) - if err != nil { - fn.Name = ff.Name - err := function.CreateFn(p.clientV2, appName, fn) - if err != nil { - return err - } - } else { - fn.ID = fnRes.ID - err = function.PutFn(p.clientV2, fn.ID, fn) - if err != nil { - return err - } - } - - if fnRes == nil { - fn, err = function.GetFnByName(p.clientV2, app.ID, ff.Name) - if err != nil { - return err - } - } - - if len(ff.Triggers) != 0 { - for _, t := range ff.Triggers { - trig := &modelsV2.Trigger{ - AppID: app.ID, - FnID: fn.ID, - Name: t.Name, - Source: t.Source, - Type: t.Type, - } - - trigs, err := trigger.GetTriggerByName(p.clientV2, app.ID, fn.ID, t.Name) - if err != nil { - err = trigger.CreateTrigger(p.clientV2, trig) - if err != nil { - return err - } - } else { - trig.ID = trigs.ID - err = trigger.PutTrigger(p.clientV2, trig) - if err != nil { - return err - } - } - } - } - - return nil -} -func expandEnvConfig(configs map[string]string) map[string]string { - for k, v := range configs { - configs[k] = os.ExpandEnv(v) - } - return configs -} - -func (p *deploycmd) updateAppConfig(appf *common.AppFile) error { +func UpdateAppConfig(client *fnclient.Fn, appf *common.AppFile) error { param := clientApps.NewPatchAppsAppParams() param.App = appf.Name param.Body = &models.AppWrapper{ @@ -489,7 +205,7 @@ func (p *deploycmd) updateAppConfig(appf *common.AppFile) error { }, } - _, err := p.client.Apps.PatchAppsApp(param) + _, err := client.Apps.PatchAppsApp(param) if err != nil { postParams := clientApps.NewPostAppsParams() //XXX switch to put when v2.0 Fn postParams.Body = &models.AppWrapper{ @@ -500,7 +216,7 @@ func (p *deploycmd) updateAppConfig(appf *common.AppFile) error { }, } - _, err = p.client.Apps.PostApps(postParams) + _, err = client.Apps.PostApps(postParams) if err != nil { return err } diff --git a/commands/deploy_api.go b/commands/deploy_api.go new file mode 100644 index 00000000..9f9bcd4c --- /dev/null +++ b/commands/deploy_api.go @@ -0,0 +1,211 @@ +package commands + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/fnproject/cli/common" + fnclient "github.com/fnproject/fn_go/client" + v2Client "github.com/fnproject/fn_go/clientv2" +) + +type DeployConfig struct { + BuildArgs []string + Verbose bool + NoBump bool + IsLocal bool + NoCache bool +} + +func DeploySingle(clientV1 *fnclient.Fn, clientV2 *v2Client.Fn, deployConfig *DeployConfig, dir, appName string, appf *common.AppFile) error { + wd := common.GetWd() + + err := os.Chdir(dir) + if err != nil { + return err + } + defer os.Chdir(dir) + + ffV, err := common.ReadInFuncFile() + if err != nil { + return err + } + + switch common.GetFuncYamlVersion(ffV) { + case common.LatestYamlVersion: + fpath, ff, err := common.FindAndParseFuncFileV20180708(dir) + if err != nil { + return err + } + if appf != nil { + if dir == wd { + setFuncInfoV20180708(ff, appf.Name) + } + } + + if appf != nil { + err = UpdateAppConfig(clientV1, appf) + if err != nil { + return fmt.Errorf("Failed to update app config: %v", err) + } + } + + return DeployFuncV20180708(clientV1, clientV2, deployConfig, appName, fpath, ff) + default: + fpath, ff, err := common.FindAndParseFuncfile(dir) + if err != nil { + return err + } + if appf != nil { + if dir == wd { + setRootFuncInfo(ff, appf.Name) + } + } + + if appf != nil { + err = UpdateAppConfig(clientV1, appf) + if err != nil { + return fmt.Errorf("Failed to update app config: %v", err) + } + } + + return DeployFunc(clientV1, deployConfig, appName, fpath, ff) + } +} + +func DeployAll(client *fnclient.Fn, deployConfig *DeployConfig, appDir, appName string, appf *common.AppFile) error { + if appf != nil { + err := UpdateAppConfig(client, appf) + if err != nil { + return fmt.Errorf("failed to update app config: %v", err) + } + } + var dir string + wd := common.GetWd() + + if appDir != "" { + dir = appDir + } else { + dir = wd + } + var funcFound bool + err := common.WalkFuncs(dir, func(path string, ff *common.FuncFile, err error) error { + if err != nil { // probably some issue with funcfile parsing, can decide to handle this differently if we'd like + return err + } + dir := filepath.Dir(path) + if dir == wd { + setRootFuncInfo(ff, appName) + } else { + // change dirs + err = os.Chdir(dir) + if err != nil { + return err + } + p2 := strings.TrimPrefix(dir, wd) + if ff.Name == "" { + ff.Name = strings.Replace(p2, "/", "-", -1) + if strings.HasPrefix(ff.Name, "-") { + ff.Name = ff.Name[1:] + } + // todo: should we prefix appname too? + } + if ff.Path == "" { + ff.Path = p2 + } + } + + err = DeployFunc(client, deployConfig, appName, path, ff) + if err != nil { + return fmt.Errorf("deploy error on %s: %v", path, err) + } + + now := time.Now() + os.Chtimes(path, now, now) + funcFound = true + return nil + }) + if err != nil { + return err + } + + return nil +} + +// DeployFunc performs several actions to deploy to a functions server. +// Parse func.yaml file, bump version, build image, push to registry, and +// finally it will update function's route. Optionally, +// the route can be overriden inside the func.yaml file. +func DeployFunc(client *fnclient.Fn, deployConfig *DeployConfig, appName, funcfilePath string, funcfile *common.FuncFile) error { + dir := filepath.Dir(funcfilePath) + // get name from directory if it's not defined + if funcfile.Name == "" { + funcfile.Name = filepath.Base(filepath.Dir(funcfilePath)) // todo: should probably make a copy of ff before changing it + } + if funcfile.Path == "" { + if dir == "." { + funcfile.Path = "/" + } else { + funcfile.Path = "/" + filepath.Base(dir) + } + + } + + var err error + if !deployConfig.NoBump { + funcfile2, err := common.BumpIt(funcfilePath, common.Patch) + if err != nil { + return err + } + funcfile.Version = funcfile2.Version + // TODO: this whole funcfile handling needs some love, way too confusing. Only bump makes permanent changes to it. + } + + // p.noCache + _, err = common.BuildFunc(deployConfig.BuildArgs, deployConfig.Verbose, deployConfig.NoCache, funcfilePath, funcfile) + if err != nil { + return err + } + + // p.local + if !deployConfig.IsLocal { + if err := common.DockerPush(funcfile); err != nil { + return err + } + } + + return updateRoute(client, appName, funcfile) +} + +func DeployFuncV20180708(clientV1 *fnclient.Fn, clientV2 *v2Client.Fn, deployConfig *DeployConfig, appName, funcfilePath string, funcfile *common.FuncFileV20180708) error { + if funcfile.Name == "" { + funcfile.Name = filepath.Base(filepath.Dir(funcfilePath)) // todo: should probably make a copy of ff before changing it + } + + var err error + if !deployConfig.NoBump { + funcfile2, err := common.BumpItV20180708(funcfilePath, common.Patch) + if err != nil { + return err + } + funcfile.Version = funcfile2.Version + // TODO: this whole funcfile handling needs some love, way too confusing. Only bump makes permanent changes to it. + } + + _, err = common.BuildFuncV20180708(deployConfig.BuildArgs, deployConfig.Verbose, + deployConfig.NoCache, funcfilePath, funcfile) + if err != nil { + return err + } + + if !deployConfig.IsLocal { + if err := common.DockerPushV20180708(funcfile); err != nil { + return err + } + } + + return updateFunction(clientV1, clientV2, appName, funcfile) +} diff --git a/commands/functions_api.go b/commands/functions_api.go new file mode 100644 index 00000000..a586347e --- /dev/null +++ b/commands/functions_api.go @@ -0,0 +1,96 @@ +package commands + +import ( + "fmt" + + "github.com/fnproject/cli/common" + apps "github.com/fnproject/cli/objects/app" + function "github.com/fnproject/cli/objects/fn" + "github.com/fnproject/cli/objects/route" + "github.com/fnproject/cli/objects/trigger" + fnclient "github.com/fnproject/fn_go/client" + v2Client "github.com/fnproject/fn_go/clientv2" + "github.com/fnproject/fn_go/models" + modelsV2 "github.com/fnproject/fn_go/modelsv2" +) + +func updateFunction(clientV1 *fnclient.Fn, clientV2 *v2Client.Fn, appName string, ff *common.FuncFileV20180708) error { + fn := &modelsV2.Fn{} + if err := function.WithFuncFileV20180708(ff, fn); err != nil { + return fmt.Errorf("Error getting route with funcfile: %s", err) + } + + app, err := apps.GetAppByName(appName) + if err != nil { + app = &models.App{ + Name: appName, + } + + err = apps.CreateApp(clientV1, app) + if err != nil { + return err + } + app, err = apps.GetAppByName(appName) + if err != nil { + return err + } + } + + fnRes, err := function.GetFnByName(clientV2, app.ID, ff.Name) + if err != nil { + fn.Name = ff.Name + err := function.CreateFn(clientV2, appName, fn) + if err != nil { + return err + } + } else { + fn.ID = fnRes.ID + err = function.PutFn(clientV2, fn.ID, fn) + if err != nil { + return err + } + } + + if fnRes == nil { + fn, err = function.GetFnByName(clientV2, app.ID, ff.Name) + if err != nil { + return err + } + } + + if len(ff.Triggers) != 0 { + for _, t := range ff.Triggers { + trig := &modelsV2.Trigger{ + AppID: app.ID, + FnID: fn.ID, + Name: t.Name, + Source: t.Source, + Type: t.Type, + } + + trigs, err := trigger.GetTriggerByName(clientV2, app.ID, fn.ID, t.Name) + if err != nil { + err = trigger.CreateTrigger(clientV2, trig) + if err != nil { + return err + } + } else { + trig.ID = trigs.ID + err = trigger.PutTrigger(clientV2, trig) + if err != nil { + return err + } + } + } + } + + return nil +} + +func updateRoute(client *fnclient.Fn, appName string, ff *common.FuncFile) error { + rt := &models.Route{} + if err := route.WithFuncFile(ff, rt); err != nil { + return fmt.Errorf("Error getting route with funcfile: %s", err) + } + return route.PutRoute(client, appName, ff.Path, rt) +} diff --git a/common/common.go b/common/common.go index a766e722..318a9085 100644 --- a/common/common.go +++ b/common/common.go @@ -56,7 +56,7 @@ func GetDir(c *cli.Context) string { } // BuildFunc bumps version and builds function. -func BuildFunc(verbose bool, fpath string, funcfile *FuncFile, buildArg []string, noCache bool) (*FuncFile, error) { +func BuildFunc(buildArg []string, verbose, noCache bool, fpath string, funcfile *FuncFile) (*FuncFile, error) { var err error if funcfile.Version == "" { funcfile, err = BumpIt(fpath, Patch) @@ -77,7 +77,7 @@ func BuildFunc(verbose bool, fpath string, funcfile *FuncFile, buildArg []string } // BuildFunc bumps version and builds function. -func BuildFuncV20180708(verbose bool, fpath string, funcfile *FuncFileV20180708, buildArg []string, noCache bool) (*FuncFileV20180708, error) { +func BuildFuncV20180708(buildArg []string, verbose, noCache bool, fpath string, funcfile *FuncFileV20180708) (*FuncFileV20180708, error) { var err error if funcfile.Version == "" { diff --git a/run/run.go b/run/run.go index 9d320fff..6e9d7690 100644 --- a/run/run.go +++ b/run/run.go @@ -146,7 +146,10 @@ func PreRun(c *cli.Context) (string, *common.FuncFile, []string, error) { } buildArgs := c.StringSlice("build-arg") - _, err = common.BuildFunc(c.GlobalBool("verbose"), fpath, ff, buildArgs, c.Bool("no-cache")) + verbose := c.GlobalBool("verbose") + noCache := c.Bool("no-cache") + + _, err = common.BuildFunc(buildArgs, verbose, noCache, fpath, ff) if err != nil { return fpath, nil, nil, err } @@ -207,7 +210,10 @@ func PreRunV20180708(c *cli.Context) (string, *common.FuncFileV20180708, []strin } buildArgs := c.StringSlice("build-arg") - _, err = common.BuildFuncV20180708(c.GlobalBool("verbose"), fpath, ff, buildArgs, c.Bool("no-cache")) + verbose := c.GlobalBool("verbose") + noCache := c.Bool("no-cache") + + _, err = common.BuildFuncV20180708(buildArgs, verbose, noCache, fpath, ff) if err != nil { return fpath, nil, nil, err }