github.com/devcamcar/cli@v0.0.0-20181107134215-706a05759d18/commands/deploy.go (about) 1 package commands 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "path/filepath" 8 "strings" 9 "time" 10 11 client "github.com/fnproject/cli/client" 12 common "github.com/fnproject/cli/common" 13 apps "github.com/fnproject/cli/objects/app" 14 function "github.com/fnproject/cli/objects/fn" 15 trigger "github.com/fnproject/cli/objects/trigger" 16 v2Client "github.com/fnproject/fn_go/clientv2" 17 clientApps "github.com/fnproject/fn_go/clientv2/apps" 18 modelsV2 "github.com/fnproject/fn_go/modelsv2" 19 "github.com/urfave/cli" 20 ) 21 22 // DeployCommand returns deploy cli.command 23 func DeployCommand() cli.Command { 24 cmd := deploycmd{} 25 var flags []cli.Flag 26 flags = append(flags, cmd.flags()...) 27 return cli.Command{ 28 Name: "deploy", 29 Usage: "\tDeploys a function to the functions server (bumps, build, pushes and updates functions and/or triggers).", 30 Aliases: []string{"dp"}, 31 Before: func(cxt *cli.Context) error { 32 provider, err := client.CurrentProvider() 33 if err != nil { 34 return err 35 } 36 cmd.clientV2 = provider.APIClientv2() 37 return nil 38 }, 39 Category: "DEVELOPMENT COMMANDS", 40 Description: "This command deploys one or all (--all) functions to the function server.", 41 ArgsUsage: "[function-subdirectory]", 42 Flags: flags, 43 Action: cmd.deploy, 44 } 45 } 46 47 type deploycmd struct { 48 appName string 49 clientV2 *v2Client.Fn 50 51 wd string 52 verbose bool 53 local bool 54 noCache bool 55 registry string 56 all bool 57 noBump bool 58 } 59 60 func (p *deploycmd) flags() []cli.Flag { 61 return []cli.Flag{ 62 cli.StringFlag{ 63 Name: "app", 64 Usage: "App name to deploy to", 65 Destination: &p.appName, 66 }, 67 cli.BoolFlag{ 68 Name: "verbose, v", 69 Usage: "Verbose mode", 70 Destination: &p.verbose, 71 }, 72 cli.BoolFlag{ 73 Name: "no-cache", 74 Usage: "Don't use Docker cache for the build", 75 Destination: &p.noCache, 76 }, 77 cli.BoolFlag{ 78 Name: "local, skip-push", // todo: deprecate skip-push 79 Usage: "Do not push Docker built images onto Docker Hub - useful for local development.", 80 Destination: &p.local, 81 }, 82 cli.StringFlag{ 83 Name: "registry", 84 Usage: "Set the Docker owner for images and optionally the registry. This will be prefixed to your function name for pushing to Docker registries.\r eg: `--registry username` will set your Docker Hub owner. `--registry registry.hub.docker.com/username` will set the registry and owner. ", 85 Destination: &p.registry, 86 }, 87 cli.BoolFlag{ 88 Name: "all", 89 Usage: "If in root directory containing `app.yaml`, this will deploy all functions", 90 Destination: &p.all, 91 }, 92 cli.BoolFlag{ 93 Name: "no-bump", 94 Usage: "Do not bump the version, assuming external version management", 95 Destination: &p.noBump, 96 }, 97 cli.StringSliceFlag{ 98 Name: "build-arg", 99 Usage: "Set build time variables", 100 }, 101 cli.StringFlag{ 102 Name: "working-dir,w", 103 Usage: "Specify the working directory to deploy a function, must be the full path.", 104 }, 105 } 106 } 107 108 // deploy deploys a function or a set of functions for an app 109 // By default this will deploy a single function, either the function in the current directory 110 // or if an arg is passed in, a function in the path representing that arg, relative to the 111 // current working directory. 112 // 113 // If user passes in --all flag, it will deploy all functions in an app. An app must have an `app.yaml` 114 // file in it's root directory. The functions will be deployed based on the directory structure 115 // on the file system (can be overridden using the `path` arg in each `func.yaml`. The index/root function 116 // is the one that lives in the same directory as the app.yaml. 117 func (p *deploycmd) deploy(c *cli.Context) error { 118 appName := "" 119 dir := common.GetDir(c) 120 121 appf, err := common.LoadAppfile(dir) 122 123 if err != nil { 124 if _, ok := err.(*common.NotFoundError); ok { 125 if p.all { 126 return err 127 } 128 // otherwise, it's ok 129 } else { 130 return err 131 } 132 } else { 133 appName = appf.Name 134 } 135 if p.appName != "" { 136 // flag overrides all 137 appName = p.appName 138 } 139 140 if appName == "" { 141 return errors.New("App name must be provided, try `--app APP_NAME`") 142 } 143 144 if p.all { 145 return p.deployAll(c, appName, appf) 146 } 147 return p.deploySingle(c, appName, appf) 148 } 149 150 // deploySingle deploys a single function, either the current directory or if in the context 151 // of an app and user provides relative path as the first arg, it will deploy that function. 152 func (p *deploycmd) deploySingle(c *cli.Context, appName string, appf *common.AppFile) error { 153 var dir string 154 wd := common.GetWd() 155 156 if c.String("working-dir") != "" { 157 dir = c.String("working-dir") 158 } else { 159 // if we're in the context of an app, first arg is path to the function 160 path := c.Args().First() 161 if path != "" { 162 fmt.Printf("Deploying function at: /%s\n", path) 163 } 164 dir = filepath.Join(wd, path) 165 } 166 167 err := os.Chdir(dir) 168 if err != nil { 169 return err 170 } 171 defer os.Chdir(wd) 172 173 ffV, err := common.ReadInFuncFile() 174 if err != nil { 175 return err 176 } 177 178 switch common.GetFuncYamlVersion(ffV) { 179 case common.LatestYamlVersion: 180 fpath, ff, err := common.FindAndParseFuncFileV20180708(dir) 181 if err != nil { 182 return err 183 } 184 if appf != nil { 185 if dir == wd { 186 setFuncInfoV20180708(ff, appf.Name) 187 } 188 } 189 190 if appf != nil { 191 err = p.updateAppConfig(appf) 192 if err != nil { 193 return fmt.Errorf("Failed to update app config: %v", err) 194 } 195 } 196 197 return p.deployFuncV20180708(c, appName, wd, fpath, ff) 198 default: 199 return fmt.Errorf("routes are no longer supported, please use the migrate command to update your metadata") 200 } 201 } 202 203 // deployAll deploys all functions in an app. 204 func (p *deploycmd) deployAll(c *cli.Context, appName string, appf *common.AppFile) error { 205 if appf != nil { 206 err := p.updateAppConfig(appf) 207 if err != nil { 208 return fmt.Errorf("Failed to update app config: %v", err) 209 } 210 } 211 212 var dir string 213 wd := common.GetWd() 214 215 if c.String("dir") != "" { 216 dir = c.String("dir") 217 } else { 218 dir = wd 219 } 220 221 var funcFound bool 222 err := common.WalkFuncsV20180708(dir, func(path string, ff *common.FuncFileV20180708, err error) error { 223 if err != nil { // probably some issue with funcfile parsing, can decide to handle this differently if we'd like 224 return err 225 } 226 dir := filepath.Dir(path) 227 if dir == wd { 228 setFuncInfoV20180708(ff, appName) 229 } else { 230 // change dirs 231 err = os.Chdir(dir) 232 if err != nil { 233 return err 234 } 235 p2 := strings.TrimPrefix(dir, wd) 236 if ff.Name == "" { 237 ff.Name = strings.Replace(p2, "/", "-", -1) 238 if strings.HasPrefix(ff.Name, "-") { 239 ff.Name = ff.Name[1:] 240 } 241 // todo: should we prefix appname too? 242 } 243 } 244 245 err = p.deployFuncV20180708(c, appName, wd, path, ff) 246 if err != nil { 247 return fmt.Errorf("deploy error on %s: %v", path, err) 248 } 249 250 now := time.Now() 251 os.Chtimes(path, now, now) 252 funcFound = true 253 return nil 254 }) 255 if err != nil { 256 return err 257 } 258 259 if !funcFound { 260 return errors.New("No functions found to deploy") 261 } 262 263 return nil 264 } 265 266 func (p *deploycmd) deployFuncV20180708(c *cli.Context, appName, baseDir, funcfilePath string, funcfile *common.FuncFileV20180708) error { 267 if appName == "" { 268 return errors.New("App name must be provided, try `--app APP_NAME`") 269 } 270 271 if funcfile.Name == "" { 272 funcfile.Name = filepath.Base(filepath.Dir(funcfilePath)) // todo: should probably make a copy of ff before changing it 273 } 274 fmt.Printf("Deploying %s to app: %s\n", funcfile.Name, appName) 275 276 var err error 277 if !p.noBump { 278 funcfile2, err := common.BumpItV20180708(funcfilePath, common.Patch) 279 if err != nil { 280 return err 281 } 282 funcfile.Version = funcfile2.Version 283 // TODO: this whole funcfile handling needs some love, way too confusing. Only bump makes permanent changes to it. 284 } 285 286 buildArgs := c.StringSlice("build-arg") 287 _, err = common.BuildFuncV20180708(c.GlobalBool("verbose"), funcfilePath, funcfile, buildArgs, p.noCache) 288 if err != nil { 289 return err 290 } 291 292 if !p.local { 293 if err := common.DockerPushV20180708(funcfile); err != nil { 294 return err 295 } 296 } 297 298 return p.updateFunction(c, appName, funcfile) 299 } 300 301 func setRootFuncInfo(ff *common.FuncFile, appName string) { 302 if ff.Name == "" { 303 fmt.Println("Setting name") 304 ff.Name = fmt.Sprintf("%s-root", appName) 305 } 306 if ff.Path == "" { 307 // then in root dir, so this will be deployed at / 308 ff.Path = "/" 309 } 310 } 311 312 func setFuncInfoV20180708(ff *common.FuncFileV20180708, appName string) { 313 if ff.Name == "" { 314 fmt.Println("Setting name") 315 ff.Name = fmt.Sprintf("%s-root", appName) 316 } 317 } 318 319 func (p *deploycmd) updateFunction(c *cli.Context, appName string, ff *common.FuncFileV20180708) error { 320 fmt.Printf("Updating function %s using image %s...\n", ff.Name, ff.ImageNameV20180708()) 321 322 fn := &modelsV2.Fn{} 323 if err := function.WithFuncFileV20180708(ff, fn); err != nil { 324 return fmt.Errorf("Error getting function with funcfile: %s", err) 325 } 326 327 app, err := apps.GetAppByName(p.clientV2, appName) 328 if err != nil { 329 app = &modelsV2.App{ 330 Name: appName, 331 } 332 333 err = apps.CreateApp(p.clientV2, app) 334 if err != nil { 335 return err 336 } 337 app, err = apps.GetAppByName(p.clientV2, appName) 338 if err != nil { 339 return err 340 } 341 } 342 343 fnRes, err := function.GetFnByName(p.clientV2, app.ID, ff.Name) 344 if err != nil { 345 fn.Name = ff.Name 346 err := function.CreateFn(p.clientV2, appName, fn) 347 if err != nil { 348 return err 349 } 350 } else { 351 fn.ID = fnRes.ID 352 err = function.PutFn(p.clientV2, fn.ID, fn) 353 if err != nil { 354 return err 355 } 356 } 357 358 if fnRes == nil { 359 fn, err = function.GetFnByName(p.clientV2, app.ID, ff.Name) 360 if err != nil { 361 return err 362 } 363 } 364 365 if len(ff.Triggers) != 0 { 366 for _, t := range ff.Triggers { 367 trig := &modelsV2.Trigger{ 368 AppID: app.ID, 369 FnID: fn.ID, 370 Name: t.Name, 371 Source: t.Source, 372 Type: t.Type, 373 } 374 375 trigs, err := trigger.GetTriggerByName(p.clientV2, app.ID, fn.ID, t.Name) 376 if err != nil { 377 err = trigger.CreateTrigger(p.clientV2, trig) 378 if err != nil { 379 return err 380 } 381 } else { 382 trig.ID = trigs.ID 383 err = trigger.PutTrigger(p.clientV2, trig) 384 if err != nil { 385 return err 386 } 387 } 388 } 389 } 390 391 return nil 392 } 393 394 395 func (p *deploycmd) updateAppConfig(appf *common.AppFile) error { 396 app, err := apps.GetAppByName(p.clientV2, appf.Name) 397 if err != nil { 398 switch err.(type) { 399 case apps.NameNotFoundError: 400 param := clientApps.NewCreateAppParams() 401 param.Body = &modelsV2.App{ 402 Name: appf.Name, 403 Config: appf.Config, 404 Annotations: appf.Annotations, 405 } 406 if _, err = p.clientV2.Apps.CreateApp(param); err != nil { 407 return err 408 } 409 return nil 410 default: 411 return err 412 } 413 } 414 param := clientApps.NewUpdateAppParams() 415 param.AppID = app.ID 416 param.Body = &modelsV2.App{ 417 Config: appf.Config, 418 Annotations: appf.Annotations, 419 } 420 421 if _, err = p.clientV2.Apps.UpdateApp(param); err != nil { 422 return err 423 } 424 return nil 425 426 }