| OLD | NEW |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. | 1 // Copyright 2016 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. | 3 // that can be found in the LICENSE file. |
| 4 | 4 |
| 5 package main | 5 package main |
| 6 | 6 |
| 7 import ( | 7 import ( |
| 8 "fmt" | 8 "fmt" |
| 9 "path/filepath" | 9 "path/filepath" |
| 10 "sort" | 10 "sort" |
| 11 "strconv" |
| 11 "strings" | 12 "strings" |
| 12 | 13 |
| 13 "github.com/luci/luci-go/common/errors" | 14 "github.com/luci/luci-go/common/errors" |
| 14 log "github.com/luci/luci-go/common/logging" | 15 log "github.com/luci/luci-go/common/logging" |
| 15 "github.com/luci/luci-go/deploytool/api/deploy" | 16 "github.com/luci/luci-go/deploytool/api/deploy" |
| 16 "github.com/luci/luci-go/deploytool/managedfs" | 17 "github.com/luci/luci-go/deploytool/managedfs" |
| 17 ) | 18 ) |
| 18 | 19 |
| 20 // gaeDefaultModule is the name of the default GAE module. |
| 21 var gaeDefaultModule = "default" |
| 22 |
| 19 // gaeDeployment is a consolidated AppEngine deployment configuration. It | 23 // gaeDeployment is a consolidated AppEngine deployment configuration. It |
| 20 // includes staged configurations for specifically-deployed components, as well | 24 // includes staged configurations for specifically-deployed components, as well |
| 21 // as global AppEngine state (e.g., index, queue, etc.). | 25 // as global AppEngine state (e.g., index, queue, etc.). |
| 22 type gaeDeployment struct { | 26 type gaeDeployment struct { |
| 23 // project is the cloud project that this deployment is targeting. | 27 // project is the cloud project that this deployment is targeting. |
| 24 project *layoutDeploymentCloudProject | 28 project *layoutDeploymentCloudProject |
| 25 | 29 |
| 26 // modules is the set of staged AppEngine modules that are being deploye
d. The | 30 // modules is the set of staged AppEngine modules that are being deploye
d. The |
| 27 // map is keyed on the module names. | 31 // map is keyed on the module names. |
| 28 modules map[string]*stagedGAEModule | 32 modules map[string]*stagedGAEModule |
| (...skipping 14 matching lines...) Expand all Loading... |
| 43 return &gaeDeployment{ | 47 return &gaeDeployment{ |
| 44 project: project, | 48 project: project, |
| 45 modules: make(map[string]*stagedGAEModule), | 49 modules: make(map[string]*stagedGAEModule), |
| 46 } | 50 } |
| 47 } | 51 } |
| 48 | 52 |
| 49 func (d *gaeDeployment) addModule(module *layoutDeploymentGAEModule) { | 53 func (d *gaeDeployment) addModule(module *layoutDeploymentGAEModule) { |
| 50 d.modules[module.ModuleName] = &stagedGAEModule{ | 54 d.modules[module.ModuleName] = &stagedGAEModule{ |
| 51 layoutDeploymentGAEModule: module, | 55 layoutDeploymentGAEModule: module, |
| 52 gaeDep: d, | 56 gaeDep: d, |
| 57 |
| 58 // Default no-op action stubs. |
| 59 localBuildFn: func(*work) error { return nil }, |
| 60 pushFn: func(*work) error { return nil }, |
| 53 } | 61 } |
| 54 d.moduleNames = append(d.moduleNames, module.ModuleName) | 62 d.moduleNames = append(d.moduleNames, module.ModuleName) |
| 55 } | 63 } |
| 56 | 64 |
| 57 func (d *gaeDeployment) clearModules() { | 65 func (d *gaeDeployment) clearModules() { |
| 58 d.modules, d.moduleNames = nil, nil | 66 d.modules, d.moduleNames = nil, nil |
| 59 } | 67 } |
| 60 | 68 |
| 61 func (d *gaeDeployment) stage(w *work, root *managedfs.Dir, params *deployParams
) error { | 69 func (d *gaeDeployment) stage(w *work, root *managedfs.Dir, params *deployParams
) error { |
| 62 sort.Strings(d.moduleNames) | 70 sort.Strings(d.moduleNames) |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 105 continue | 113 continue |
| 106 } | 114 } |
| 107 | 115 |
| 108 if d.versionModuleMap == nil { | 116 if d.versionModuleMap == nil { |
| 109 d.versionModuleMap = make(map[string][]string) | 117 d.versionModuleMap = make(map[string][]string) |
| 110 } | 118 } |
| 111 | 119 |
| 112 version := module.version.String() | 120 version := module.version.String() |
| 113 moduleName := module.ModuleName | 121 moduleName := module.ModuleName |
| 114 if moduleName == "" { | 122 if moduleName == "" { |
| 115 » » » moduleName = "default" | 123 » » » moduleName = gaeDefaultModule |
| 116 } | 124 } |
| 117 d.versionModuleMap[version] = append(d.versionModuleMap[version]
, moduleName) | 125 d.versionModuleMap[version] = append(d.versionModuleMap[version]
, moduleName) |
| 118 } | 126 } |
| 119 return nil | 127 return nil |
| 120 } | 128 } |
| 121 | 129 |
| 122 func (d *gaeDeployment) localBuild(w *work) error { | 130 func (d *gaeDeployment) localBuild(w *work) error { |
| 123 // During the build phase, we simply assert that builds work as a sanity | 131 // During the build phase, we simply assert that builds work as a sanity |
| 124 // check. This prevents us from having to engage remote deployment servi
ces | 132 // check. This prevents us from having to engage remote deployment servi
ces |
| 125 // only to find that some fundamental build problem has occurred. | 133 // only to find that some fundamental build problem has occurred. |
| 126 return w.RunMulti(func(workC chan<- func() error) { | 134 return w.RunMulti(func(workC chan<- func() error) { |
| 127 for _, name := range d.moduleNames { | 135 for _, name := range d.moduleNames { |
| 128 module := d.modules[name] | 136 module := d.modules[name] |
| 129 workC <- func() error { | 137 workC <- func() error { |
| 130 // Run the module's build function. | 138 // Run the module's build function. |
| 131 if module.localBuildFn == nil { | |
| 132 return nil | |
| 133 } | |
| 134 return module.localBuildFn(w) | 139 return module.localBuildFn(w) |
| 135 } | 140 } |
| 136 } | 141 } |
| 137 }) | 142 }) |
| 138 } | 143 } |
| 139 | 144 |
| 140 func (d *gaeDeployment) push(w *work) error { | 145 func (d *gaeDeployment) push(w *work) error { |
| 141 » // Run push operations on modules in parallel. | 146 » // Always push the default module first and independently, since this is
a |
| 147 » // GAE requirement for initial deployments. |
| 148 » if module := d.modules[gaeDefaultModule]; module != nil { |
| 149 » » if err := module.pushFn(w); err != nil { |
| 150 » » » return errors.Annotate(err).Reason("failed to push defau
lt module").Err() |
| 151 » » } |
| 152 » } |
| 153 |
| 154 » // Push the remaining GAE modules in parallel. |
| 142 return w.RunMulti(func(workC chan<- func() error) { | 155 return w.RunMulti(func(workC chan<- func() error) { |
| 143 for _, name := range d.moduleNames { | 156 for _, name := range d.moduleNames { |
| 157 if name == gaeDefaultModule { |
| 158 // (Pushed above) |
| 159 continue |
| 160 } |
| 161 |
| 144 module := d.modules[name] | 162 module := d.modules[name] |
| 145 » » » if module.pushFn != nil { | 163 » » » workC <- func() error { |
| 146 » » » » workC <- func() error { | 164 » » » » return module.pushFn(w) |
| 147 » » » » » return module.pushFn(w) | |
| 148 » » » » } | |
| 149 } | 165 } |
| 150 } | 166 } |
| 151 }) | 167 }) |
| 152 } | 168 } |
| 153 | 169 |
| 154 func (d *gaeDeployment) commit(w *work) error { | 170 func (d *gaeDeployment) commit(w *work) error { |
| 155 appcfg, err := w.tools.appcfg() | 171 appcfg, err := w.tools.appcfg() |
| 156 if err != nil { | 172 if err != nil { |
| 157 return errors.Annotate(err).Err() | 173 return errors.Annotate(err).Err() |
| 158 } | 174 } |
| (...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 274 base := root | 290 base := root |
| 275 | 291 |
| 276 // appYAMLPath is used in the immediate "switch" statement as a | 292 // appYAMLPath is used in the immediate "switch" statement as a |
| 277 // parameter to some inline functions. It will be populated later in thi
s | 293 // parameter to some inline functions. It will be populated later in thi
s |
| 278 // staging function, well before those inline functions are evaluated. | 294 // staging function, well before those inline functions are evaluated. |
| 279 // | 295 // |
| 280 // It is a pointer so that if, somehow, this does not end up being the c
ase, | 296 // It is a pointer so that if, somehow, this does not end up being the c
ase, |
| 281 // we will panic instead of silently using an empty string. | 297 // we will panic instead of silently using an empty string. |
| 282 var appYAMLPath *string | 298 var appYAMLPath *string |
| 283 | 299 |
| 300 // Our "__deploy" directory will be where deploy-specific artifacts are |
| 301 // blended with the current app. The name is chosen to (probably) not |
| 302 // interfere with app files. |
| 303 deployDir, err := root.EnsureDirectory("__deploy") |
| 304 if err != nil { |
| 305 return errors.Annotate(err).Reason("failed to create deploy dire
ctory").Err() |
| 306 } |
| 307 |
| 284 // Build each Component. We will delete any existing contents and leave
it | 308 // Build each Component. We will delete any existing contents and leave
it |
| 285 // unmanaged to allow our build system to put whatever files it wants in | 309 // unmanaged to allow our build system to put whatever files it wants in |
| 286 // there. | 310 // there. |
| 287 » buildDir, err := root.EnsureDirectory("build") | 311 » buildDir, err := deployDir.EnsureDirectory("build") |
| 288 if err != nil { | 312 if err != nil { |
| 289 return errors.Annotate(err).Reason("failed to create build direc
tory").Err() | 313 return errors.Annotate(err).Reason("failed to create build direc
tory").Err() |
| 290 } | 314 } |
| 291 if err := buildDir.CleanUp(); err != nil { | 315 if err := buildDir.CleanUp(); err != nil { |
| 292 return errors.Annotate(err).Reason("failed to cleanup build dire
ctory").Err() | 316 return errors.Annotate(err).Reason("failed to cleanup build dire
ctory").Err() |
| 293 } | 317 } |
| 294 buildDir.Ignore() | 318 buildDir.Ignore() |
| 295 | 319 |
| 296 // Build our Component into this directory. | 320 // Build our Component into this directory. |
| 297 if err := buildComponent(w, m.comp, buildDir); err != nil { | 321 if err := buildComponent(w, m.comp, buildDir); err != nil { |
| (...skipping 22 matching lines...) Expand all Loading... |
| 320 mainPkg := fmt.Sprintf("%s/main", m.comp.comp.title) | 344 mainPkg := fmt.Sprintf("%s/main", m.comp.comp.title) |
| 321 mainPkgParts := strings.Split(mainPkg, "/") | 345 mainPkgParts := strings.Split(mainPkg, "/") |
| 322 mainPkgDir, err := goSrcDir.EnsureDirectory(mainPkgParts[0], mai
nPkgParts[1:]...) | 346 mainPkgDir, err := goSrcDir.EnsureDirectory(mainPkgParts[0], mai
nPkgParts[1:]...) |
| 323 if err != nil { | 347 if err != nil { |
| 324 return errors.Annotate(err).Reason("failed to create dir
ectory for main package %(pkg)q"). | 348 return errors.Annotate(err).Reason("failed to create dir
ectory for main package %(pkg)q"). |
| 325 D("pkg", mainPkg).Err() | 349 D("pkg", mainPkg).Err() |
| 326 } | 350 } |
| 327 m.goPath = []string{root.String(), goPath.String()} | 351 m.goPath = []string{root.String(), goPath.String()} |
| 328 | 352 |
| 329 // Choose how to push based on whether or not this is a Managed
VM. | 353 // Choose how to push based on whether or not this is a Managed
VM. |
| 330 » » if m.ManagedVm != nil { | 354 » » if m.GetManagedVm() != nil { |
| 331 // If this is a Managed VM, symlink files from the main
package. | 355 // If this is a Managed VM, symlink files from the main
package. |
| 332 // | 356 // |
| 333 // NOTE: This has the effect of prohibiting the entry po
int package for | 357 // NOTE: This has the effect of prohibiting the entry po
int package for |
| 334 // GAE Managed VMs from importing "internal" directories
, since this stub | 358 // GAE Managed VMs from importing "internal" directories
, since this stub |
| 335 // space is outside of the main package space. | 359 // space is outside of the main package space. |
| 336 pkgPath := findGoPackage(t.GoModule.EntryPackage, m.goPa
th) | 360 pkgPath := findGoPackage(t.GoModule.EntryPackage, m.goPa
th) |
| 337 if pkgPath == "" { | 361 if pkgPath == "" { |
| 338 return errors.Reason("unable to find path for %(
package)q").D("package", t.GoModule.EntryPackage).Err() | 362 return errors.Reason("unable to find path for %(
package)q").D("package", t.GoModule.EntryPackage).Err() |
| 339 } | 363 } |
| 340 | 364 |
| (...skipping 27 matching lines...) Expand all Loading... |
| 368 | 392 |
| 369 case *deploy.AppEngineModule_StaticModule_: | 393 case *deploy.AppEngineModule_StaticModule_: |
| 370 m.pushFn = func(w *work) error { | 394 m.pushFn = func(w *work) error { |
| 371 return m.pushClassic(w, *appYAMLPath) | 395 return m.pushClassic(w, *appYAMLPath) |
| 372 } | 396 } |
| 373 } | 397 } |
| 374 | 398 |
| 375 // Build our static files map. | 399 // Build our static files map. |
| 376 // | 400 // |
| 377 // For each static files directory, symlink a generated directory immedi
ately | 401 // For each static files directory, symlink a generated directory immedi
ately |
| 378 » // under our source. | 402 » // under our deployment directory. |
| 403 » staticDir, err := deployDir.EnsureDirectory("static") |
| 404 » if err != nil { |
| 405 » » return errors.Annotate(err).Reason("failed to create static dire
ctory").Err() |
| 406 » } |
| 407 |
| 379 staticMap := make(map[string]string) | 408 staticMap := make(map[string]string) |
| 380 staticBuildPathMap := make(map[*deploy.BuildPath]string) | 409 staticBuildPathMap := make(map[*deploy.BuildPath]string) |
| 381 if handlerSet := m.Handlers; handlerSet != nil { | 410 if handlerSet := m.Handlers; handlerSet != nil { |
| 382 for _, h := range handlerSet.Handler { | 411 for _, h := range handlerSet.Handler { |
| 383 var bp *deploy.BuildPath | 412 var bp *deploy.BuildPath |
| 384 switch t := h.GetContent().(type) { | 413 switch t := h.GetContent().(type) { |
| 385 case *deploy.AppEngineModule_Handler_StaticBuildDir: | 414 case *deploy.AppEngineModule_Handler_StaticBuildDir: |
| 386 bp = t.StaticBuildDir | 415 bp = t.StaticBuildDir |
| 387 case *deploy.AppEngineModule_Handler_StaticFiles_: | 416 case *deploy.AppEngineModule_Handler_StaticFiles_: |
| 388 bp = t.StaticFiles.GetBuild() | 417 bp = t.StaticFiles.GetBuild() |
| 389 } | 418 } |
| 390 if bp == nil { | 419 if bp == nil { |
| 391 continue | 420 continue |
| 392 } | 421 } |
| 393 | 422 |
| 394 // Have we already mapped this BuildPath? | 423 // Have we already mapped this BuildPath? |
| 395 if _, ok := staticBuildPathMap[bp]; ok { | 424 if _, ok := staticBuildPathMap[bp]; ok { |
| 396 continue | 425 continue |
| 397 } | 426 } |
| 398 | 427 |
| 399 // Get the actual path for this BuildPath entry. | 428 // Get the actual path for this BuildPath entry. |
| 400 dirPath, err := m.comp.buildPath(bp) | 429 dirPath, err := m.comp.buildPath(bp) |
| 401 if err != nil { | 430 if err != nil { |
| 402 return errors.Annotate(err).Reason("cannot resol
ve static directory").Err() | 431 return errors.Annotate(err).Reason("cannot resol
ve static directory").Err() |
| 403 } | 432 } |
| 404 | 433 |
| 405 // Do we already have a static map entry for this filesy
stem source path? | 434 // Do we already have a static map entry for this filesy
stem source path? |
| 406 staticName, ok := staticMap[dirPath] | 435 staticName, ok := staticMap[dirPath] |
| 407 if !ok { | 436 if !ok { |
| 408 » » » » sd := base.File(fmt.Sprintf("_static_%d", len(st
aticMap))) | 437 » » » » sd := staticDir.File(strconv.Itoa(len(staticMap)
)) |
| 409 if err := sd.SymlinkFrom(dirPath, true); err !=
nil { | 438 if err := sd.SymlinkFrom(dirPath, true); err !=
nil { |
| 410 return errors.Annotate(err).Reason("fail
ed to symlink static content for [%(path)s]"). | 439 return errors.Annotate(err).Reason("fail
ed to symlink static content for [%(path)s]"). |
| 411 D("path", dirPath).Err() | 440 D("path", dirPath).Err() |
| 412 } | 441 } |
| 413 » » » » staticName = sd.Name() | 442 » » » » if staticName, err = root.RelPathFrom(sd.String(
)); err != nil { |
| 443 » » » » » return errors.Annotate(err).Reason("fail
ed to get relative path").Err() |
| 444 » » » » } |
| 414 staticMap[dirPath] = staticName | 445 staticMap[dirPath] = staticName |
| 415 } | 446 } |
| 416 staticBuildPathMap[bp] = staticName | 447 staticBuildPathMap[bp] = staticName |
| 417 } | 448 } |
| 418 } | 449 } |
| 419 | 450 |
| 420 // "app.yaml" / "module.yaml" | 451 // "app.yaml" / "module.yaml" |
| 421 appYAML, err := gaeBuildAppYAML(m.AppEngineModule, staticBuildPathMap) | 452 appYAML, err := gaeBuildAppYAML(m.AppEngineModule, staticBuildPathMap) |
| 422 if err != nil { | 453 if err != nil { |
| 423 return errors.Annotate(err).Reason("failed to generate module YA
ML").Err() | 454 return errors.Annotate(err).Reason("failed to generate module YA
ML").Err() |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 475 "--verbose", | 506 "--verbose", |
| 476 "update", appYAML, | 507 "update", appYAML, |
| 477 ).cwd(appPath) | 508 ).cwd(appPath) |
| 478 x = addGoEnv(m.goPath, x) | 509 x = addGoEnv(m.goPath, x) |
| 479 if err := x.check(w); err != nil { | 510 if err := x.check(w); err != nil { |
| 480 return errors.Annotate(err).Reason("failed to deploy classic GAE
module").Err() | 511 return errors.Annotate(err).Reason("failed to deploy classic GAE
module").Err() |
| 481 } | 512 } |
| 482 return nil | 513 return nil |
| 483 } | 514 } |
| 484 | 515 |
| 485 // localBuildGo performs a local build against a Go binary. | 516 // localBuildGo performs verification of a Go binary. |
| 486 func (m *stagedGAEModule) localBuildGo(w *work, mainPkg string) error { | 517 func (m *stagedGAEModule) localBuildGo(w *work, mainPkg string) error { |
| 487 gt, err := w.goTool(m.goPath) | 518 gt, err := w.goTool(m.goPath) |
| 488 if err != nil { | 519 if err != nil { |
| 489 return errors.Annotate(err).Reason("failed to get Go tool").Err(
) | 520 return errors.Annotate(err).Reason("failed to get Go tool").Err(
) |
| 490 } | 521 } |
| 491 if err := gt.build(w, "", mainPkg); err != nil { | 522 if err := gt.build(w, "", mainPkg); err != nil { |
| 492 » » return errors.Annotate(err).Reason("failed to build %(pkg)q").D(
"pkg", mainPkg).Err() | 523 » » return errors.Annotate(err).Reason("failed to local build %(pkg)
q").D("pkg", mainPkg).Err() |
| 493 } | 524 } |
| 494 return nil | 525 return nil |
| 495 } | 526 } |
| 496 | 527 |
| 497 // pushGoMVM pushes a Go Managed VM version to the AppEngine instance. | 528 // pushGoMVM pushes a Go Managed VM version to the AppEngine instance. |
| 498 func (m *stagedGAEModule) pushGoMVM(w *work, appYAMLPath string) error { | 529 func (m *stagedGAEModule) pushGoMVM(w *work, appYAMLPath string) error { |
| 499 appDir, appYAML := filepath.Split(appYAMLPath) | 530 appDir, appYAML := filepath.Split(appYAMLPath) |
| 500 | 531 |
| 501 // Deploy Managed VM. | 532 // Deploy Managed VM. |
| 502 aedeploy, err := w.tools.aedeploy(m.goPath) | 533 aedeploy, err := w.tools.aedeploy(m.goPath) |
| (...skipping 24 matching lines...) Expand all Loading... |
| 527 case log.Debug: | 558 case log.Debug: |
| 528 gcloudArgs = append(gcloudArgs, []string{"--verbosity", "debug"}
...) | 559 gcloudArgs = append(gcloudArgs, []string{"--verbosity", "debug"}
...) |
| 529 } | 560 } |
| 530 | 561 |
| 531 x := aedeploy.bootstrap(gcloud.exec(gcloudArgs[0], gcloudArgs[1:]...)).o
utputAt(logLevel).cwd(appDir) | 562 x := aedeploy.bootstrap(gcloud.exec(gcloudArgs[0], gcloudArgs[1:]...)).o
utputAt(logLevel).cwd(appDir) |
| 532 if err := x.check(w); err != nil { | 563 if err := x.check(w); err != nil { |
| 533 return errors.Annotate(err).Reason("failed to deploy managed VM"
).Err() | 564 return errors.Annotate(err).Reason("failed to deploy managed VM"
).Err() |
| 534 } | 565 } |
| 535 return nil | 566 return nil |
| 536 } | 567 } |
| OLD | NEW |