| OLD | NEW |
| (Empty) |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. | |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 | |
| 3 // that can be found in the LICENSE file. | |
| 4 | |
| 5 package main | |
| 6 | |
| 7 import ( | |
| 8 "sort" | |
| 9 | |
| 10 "github.com/luci/luci-go/common/cli" | |
| 11 "github.com/luci/luci-go/common/errors" | |
| 12 "github.com/luci/luci-go/common/flag/flagenum" | |
| 13 log "github.com/luci/luci-go/common/logging" | |
| 14 "github.com/luci/luci-go/deploytool/managedfs" | |
| 15 | |
| 16 "github.com/maruel/subcommands" | |
| 17 ) | |
| 18 | |
| 19 type deployStage int | |
| 20 | |
| 21 const ( | |
| 22 // deployStaging is the staging stage, where local filesystem and build | |
| 23 // artifacts are constructed. Build scripts are executed, and generated
files | |
| 24 // and directories are created. This will leave the user with a director
y that | |
| 25 // can be used to build from. | |
| 26 deployStaging deployStage = iota | |
| 27 // deployLocalBuild performs a local build of the components, offering b
oth a | |
| 28 // sanity check prior to engaging remote services and creating build art
ifacts | |
| 29 // ready to push remotely. | |
| 30 deployLocalBuild deployStage = iota | |
| 31 // deployPush pushes local build artifacts to the remote service, but do
esn't | |
| 32 // enable them. | |
| 33 deployPush deployStage = iota | |
| 34 // deployCommit commits the pushed artifacts to the remote service, acti
vating | |
| 35 // them. | |
| 36 deployCommit deployStage = iota | |
| 37 ) | |
| 38 | |
| 39 var deployStageFlagEnum = flagenum.Enum{ | |
| 40 "stage": deployStaging, | |
| 41 "localbuild": deployLocalBuild, | |
| 42 "push": deployPush, | |
| 43 "commit": deployCommit, | |
| 44 } | |
| 45 | |
| 46 func (s *deployStage) Set(v string) error { return deployStageFlagEnum.FlagSet(s
, v) } | |
| 47 func (s deployStage) String() string { return deployStageFlagEnum.FlagStrin
g(s) } | |
| 48 | |
| 49 type deployParams struct { | |
| 50 // stage is the highest deployment stage to deploy through. | |
| 51 stage deployStage | |
| 52 | |
| 53 // ignoreCurrentVersion, if true, instructs depoyment logic to proceed w
ith | |
| 54 // deployment even if the currently-deployed version matches the current | |
| 55 // source vrsion. | |
| 56 ignoreCurrentVersion bool | |
| 57 // commitTainted, if true, instructs the deploy tool to perform commit | |
| 58 // operations on tainted Components. | |
| 59 commitTainted bool | |
| 60 } | |
| 61 | |
| 62 var cmdDeploy = subcommands.Command{ | |
| 63 UsageLine: "deploy [DEPLOYMENT.[.COMPONENT]]...", | |
| 64 ShortDesc: "Deploys some or all of a deployment.", | |
| 65 LongDesc: "Deploys a Deployment. If specific projects are named, only t
hose projects will be deployed.", | |
| 66 CommandRun: func() subcommands.CommandRun { | |
| 67 var cmd cmdDeployRun | |
| 68 cmd.dp.params.stage = deployCommit | |
| 69 | |
| 70 fs := cmd.GetFlags() | |
| 71 fs.BoolVar(&cmd.checkout, "checkout", false, | |
| 72 "Refresh the checkout prior to deployment.") | |
| 73 fs.Var(&cmd.dp.params.stage, "stage", | |
| 74 "Run the deployment sequence up through this stage ["+de
ployStageFlagEnum.Choices()+"].") | |
| 75 fs.BoolVar(&cmd.dp.params.commitTainted, "commit-tainted", false
, | |
| 76 "Install new deployments even if their source is tained.
") | |
| 77 fs.BoolVar(&cmd.dp.params.ignoreCurrentVersion, "ignore-current-
version", false, | |
| 78 "Push/install new deployments even if the currently-depl
oyed version would not change or could not be parsed.") | |
| 79 return &cmd | |
| 80 }, | |
| 81 } | |
| 82 | |
| 83 type cmdDeployRun struct { | |
| 84 subcommands.CommandRunBase | |
| 85 | |
| 86 // checkout, if true, refreshes the checkout prior to deployment. | |
| 87 checkout bool | |
| 88 | |
| 89 dp deploymentPlan | |
| 90 } | |
| 91 | |
| 92 func (cmd *cmdDeployRun) Run(app subcommands.Application, args []string) int { | |
| 93 a, c := app.(*application), cli.GetContext(app, cmd) | |
| 94 | |
| 95 err := a.runWork(c, func(w *work) error { | |
| 96 // Perform our planned checkout. | |
| 97 if cmd.checkout { | |
| 98 // Perform a full checkout. | |
| 99 if err := checkout(w, &a.layout, false); err != nil { | |
| 100 return errors.Annotate(err).Reason("failed to ch
eckout sources").Err() | |
| 101 } | |
| 102 } | |
| 103 | |
| 104 // Load our frozen checkout. | |
| 105 if err := a.layout.loadFrozenLayout(w); err != nil { | |
| 106 return errors.Annotate(err).Reason("failed to load froze
n checkout").Err() | |
| 107 } | |
| 108 return nil | |
| 109 }) | |
| 110 if err != nil { | |
| 111 logError(c, err, "Failed to perform checkout.") | |
| 112 return 1 | |
| 113 } | |
| 114 | |
| 115 // Figure out what we're going to deploy. | |
| 116 for _, arg := range args { | |
| 117 matched := false | |
| 118 err := a.layout.matchDeploymentComponent(arg, func(dep *layoutDe
ployment, comp *layoutDeploymentComponent) { | |
| 119 matched = true | |
| 120 | |
| 121 if comp != nil { | |
| 122 cmd.dp.addProjectComponent(comp) | |
| 123 } else { | |
| 124 cmd.dp.addDeployment(dep) | |
| 125 } | |
| 126 }) | |
| 127 if err != nil { | |
| 128 logError(c, err, "Failed during matching.") | |
| 129 return 1 | |
| 130 } | |
| 131 if !matched { | |
| 132 log.Fields{ | |
| 133 "target": arg, | |
| 134 }.Errorf(c, "Deploy target did not match any configured
deployments or components.") | |
| 135 return 1 | |
| 136 } | |
| 137 } | |
| 138 | |
| 139 err = a.runWork(c, func(w *work) error { | |
| 140 if err := cmd.dp.initialize(w, &a.layout); err != nil { | |
| 141 return errors.Annotate(err).Reason("failed to initialize
").Err() | |
| 142 } | |
| 143 | |
| 144 return cmd.dp.deploy(w) | |
| 145 }) | |
| 146 if err != nil { | |
| 147 logError(c, err, "Failed to perform deployment.") | |
| 148 return 1 | |
| 149 } | |
| 150 return 0 | |
| 151 } | |
| 152 | |
| 153 type deploymentPlan struct { | |
| 154 // comps maps DEPLOYMENT/PROJECT/COMPONENT to its specific deployable pr
oject | |
| 155 // component. | |
| 156 comps map[string]*layoutDeploymentComponent | |
| 157 | |
| 158 // deployments tracks which deployments we've specified. | |
| 159 deployments map[title]struct{} | |
| 160 | |
| 161 // params are the deployment parameters to forward to deployment operati
ons. | |
| 162 params deployParams | |
| 163 | |
| 164 // reg is the registry of deployable Components. | |
| 165 reg deploymentRegistry | |
| 166 | |
| 167 // layout is the deployment layout. It is initialized during "initialize
". | |
| 168 layout *deployLayout | |
| 169 } | |
| 170 | |
| 171 func (dp *deploymentPlan) addProjectComponent(comp *layoutDeploymentComponent) { | |
| 172 // Mark this specific component. | |
| 173 if dp.comps == nil { | |
| 174 dp.comps = make(map[string]*layoutDeploymentComponent) | |
| 175 } | |
| 176 dp.comps[comp.String()] = comp | |
| 177 | |
| 178 // Mark that we've used this deployment. | |
| 179 if dp.deployments == nil { | |
| 180 dp.deployments = make(map[title]struct{}) | |
| 181 } | |
| 182 dp.deployments[comp.dep.title] = struct{}{} | |
| 183 } | |
| 184 | |
| 185 func (dp *deploymentPlan) addDeployment(dep *layoutDeployment) { | |
| 186 for _, comp := range dep.components { | |
| 187 dp.addProjectComponent(comp) | |
| 188 } | |
| 189 } | |
| 190 | |
| 191 func (dp *deploymentPlan) deployedComponents() []string { | |
| 192 comps := make([]string, 0, len(dp.comps)) | |
| 193 for path := range dp.comps { | |
| 194 comps = append(comps, path) | |
| 195 } | |
| 196 sort.Strings(comps) | |
| 197 return comps | |
| 198 } | |
| 199 | |
| 200 func (dp *deploymentPlan) initialize(w *work, l *deployLayout) error { | |
| 201 dp.layout = l | |
| 202 | |
| 203 // Load all deployable Components from checkout. | |
| 204 for _, depName := range l.deploymentNames { | |
| 205 dep := l.deployments[depName] | |
| 206 | |
| 207 if _, ok := dp.deployments[dep.title]; !ok { | |
| 208 continue | |
| 209 } | |
| 210 | |
| 211 // Load the application components. We will iterate through them
in the | |
| 212 // order in which they are defined in the Application's protobuf
. | |
| 213 for _, compName := range dep.componentNames { | |
| 214 comp := dep.components[compName] | |
| 215 | |
| 216 // Only pass the registrar if this component is being de
ployed. | |
| 217 var pReg componentRegistrar | |
| 218 if _, ok := dp.comps[comp.String()]; ok { | |
| 219 pReg = &dp.reg | |
| 220 } | |
| 221 if err := comp.loadSourceComponent(pReg); err != nil { | |
| 222 return errors.Annotate(err).Reason("failed to lo
ad component %(comp)q"). | |
| 223 D("comp", comp.String()).Err() | |
| 224 } | |
| 225 } | |
| 226 return nil | |
| 227 } | |
| 228 return nil | |
| 229 } | |
| 230 | |
| 231 func (dp *deploymentPlan) deploy(w *work) error { | |
| 232 // Create our working root and staging/build subdirectories. | |
| 233 fs, err := dp.layout.workingFilesystem() | |
| 234 if err != nil { | |
| 235 return errors.Annotate(err).Reason("failed to create working dir
ectory").Err() | |
| 236 } | |
| 237 | |
| 238 stagingDir, err := fs.Base().EnsureDirectory("staging") | |
| 239 if err != nil { | |
| 240 return errors.Annotate(err).Reason("failed to create staging dir
ectory").Err() | |
| 241 } | |
| 242 | |
| 243 // Stage: Staging | |
| 244 if err := dp.reg.stage(w, stagingDir, &dp.params); err != nil { | |
| 245 return errors.Annotate(err).Reason("failed to stage").Err() | |
| 246 } | |
| 247 if dp.params.stage <= deployStaging { | |
| 248 return nil | |
| 249 } | |
| 250 | |
| 251 // Stage: Local Build | |
| 252 if err := dp.reg.localBuild(w); err != nil { | |
| 253 return errors.Annotate(err).Reason("failed to perform local buil
d").Err() | |
| 254 } | |
| 255 if dp.params.stage <= deployLocalBuild { | |
| 256 return nil | |
| 257 } | |
| 258 | |
| 259 // Stage: Push | |
| 260 if err := dp.reg.push(w); err != nil { | |
| 261 return errors.Annotate(err).Reason("failed to push components").
Err() | |
| 262 } | |
| 263 if dp.params.stage <= deployPush { | |
| 264 return nil | |
| 265 } | |
| 266 | |
| 267 // Stage: Commit | |
| 268 if err := dp.reg.commit(w); err != nil { | |
| 269 return errors.Annotate(err).Reason("failed to commit").Err() | |
| 270 } | |
| 271 return nil | |
| 272 } | |
| 273 | |
| 274 // deploymentRegistry is the registry of all prepared deployments. | |
| 275 // | |
| 276 // While deployment is specified and staged at a component level, actual | |
| 277 // deployment is allowed to happen at a practical level (e.g., a single | |
| 278 // cloud project's AppEngine configuration, a single Google Container Engine | |
| 279 // cluster, etc.). | |
| 280 // | |
| 281 // This will track individual components, as well as integrate them into larger | |
| 282 // action plans. | |
| 283 type deploymentRegistry struct { | |
| 284 // components is a map of components that have been deployed. | |
| 285 components map[string]*layoutDeploymentComponent | |
| 286 // componentNames is the sorted list of component names in components. | |
| 287 componentNames []string | |
| 288 | |
| 289 // gaeProjects maps Google AppEngine deployment state to specific GAE | |
| 290 // projects. | |
| 291 gaeProjects map[string]*gaeDeployment | |
| 292 // gaeProjectNames is the sorted list of registered GAE project names. | |
| 293 gaeProjectNames []string | |
| 294 | |
| 295 // gkeProjects is the set of Google Container Engine deployments for a g
iven | |
| 296 // cloud project. | |
| 297 gkeProjects map[string]*containerEngineDeployment | |
| 298 // gkeProjectNames is the sorted list of registered GKE project names. | |
| 299 gkeProjectNames []string | |
| 300 } | |
| 301 | |
| 302 func (reg *deploymentRegistry) addComponent(comp *layoutDeploymentComponent) { | |
| 303 name := comp.comp.Name | |
| 304 if _, has := reg.components[name]; has { | |
| 305 return | |
| 306 } | |
| 307 | |
| 308 if reg.components == nil { | |
| 309 reg.components = make(map[string]*layoutDeploymentComponent) | |
| 310 } | |
| 311 reg.components[name] = comp | |
| 312 reg.componentNames = append(reg.componentNames, name) | |
| 313 } | |
| 314 | |
| 315 func (reg *deploymentRegistry) addGAEModule(module *layoutDeploymentGAEModule) { | |
| 316 // Get/create the AppEngine project for this module. | |
| 317 cloudProjectName := module.comp.dep.cloudProject.Name | |
| 318 gaeD := reg.gaeProjects[cloudProjectName] | |
| 319 if gaeD == nil { | |
| 320 gaeD = makeGAEDeployment(module.comp.dep.cloudProject) | |
| 321 | |
| 322 if reg.gaeProjects == nil { | |
| 323 reg.gaeProjects = make(map[string]*gaeDeployment) | |
| 324 } | |
| 325 reg.gaeProjects[cloudProjectName] = gaeD | |
| 326 reg.gaeProjectNames = append(reg.gaeProjectNames, cloudProjectNa
me) | |
| 327 } | |
| 328 | |
| 329 gaeD.addModule(module) | |
| 330 reg.addComponent(module.comp) | |
| 331 } | |
| 332 | |
| 333 func (reg *deploymentRegistry) addGKEPod(pb *layoutDeploymentGKEPodBinding) { | |
| 334 // Get/create the AppEngine project for this module. | |
| 335 cloudProjectName := pb.cluster.cloudProject.Name | |
| 336 gkeD := reg.gkeProjects[cloudProjectName] | |
| 337 if gkeD == nil { | |
| 338 gkeD = &containerEngineDeployment{ | |
| 339 project: pb.cluster.cloudProject, | |
| 340 clusters: make(map[string]*containerEngineDeploymentClus
ter), | |
| 341 } | |
| 342 | |
| 343 if reg.gkeProjects == nil { | |
| 344 reg.gkeProjects = make(map[string]*containerEngineDeploy
ment) | |
| 345 } | |
| 346 reg.gkeProjects[cloudProjectName] = gkeD | |
| 347 reg.gkeProjectNames = append(reg.gkeProjectNames, cloudProjectNa
me) | |
| 348 } | |
| 349 | |
| 350 // Register this pod in its cluster. | |
| 351 gkeD.addCluster(pb.cluster).attachPod(pb) | |
| 352 } | |
| 353 | |
| 354 // appEngineModulesOnly clears all deployment parameters that are not AppEngine | |
| 355 // modules. | |
| 356 func (reg *deploymentRegistry) appEngineModulesOnly() { | |
| 357 reg.gkeProjects, reg.gkeProjectNames = nil, nil | |
| 358 } | |
| 359 | |
| 360 // stage performs staging, preparing deployment structures on the local | |
| 361 // filesystem. | |
| 362 func (reg *deploymentRegistry) stage(w *work, stageDir *managedfs.Dir, params *d
eployParams) error { | |
| 363 // Sort our name lists. | |
| 364 sort.Strings(reg.componentNames) | |
| 365 sort.Strings(reg.gaeProjectNames) | |
| 366 sort.Strings(reg.gkeProjectNames) | |
| 367 | |
| 368 // All components can be independently staged in parallel. | |
| 369 // | |
| 370 // We will still enumerate over them in sorted order for determinism. | |
| 371 return w.RunMulti(func(workC chan<- func() error) { | |
| 372 // AppEngine Projects | |
| 373 for _, name := range reg.gaeProjectNames { | |
| 374 proj := reg.gaeProjects[name] | |
| 375 workC <- func() error { | |
| 376 depDir, err := stageDir.EnsureDirectory(string(p
roj.project.dep.title), "appengine") | |
| 377 if err != nil { | |
| 378 return errors.Annotate(err).Reason("fail
ed to create deployment directory for %(deployment)q"). | |
| 379 D("deployment", proj.project.dep
.title).Err() | |
| 380 } | |
| 381 return proj.stage(w, depDir, params) | |
| 382 } | |
| 383 } | |
| 384 | |
| 385 // Container Engine Projects | |
| 386 for _, name := range reg.gkeProjectNames { | |
| 387 proj := reg.gkeProjects[name] | |
| 388 workC <- func() error { | |
| 389 depDir, err := stageDir.EnsureDirectory(string(p
roj.project.dep.title), "container_engine") | |
| 390 if err != nil { | |
| 391 return errors.Annotate(err).Reason("fail
ed to create deployment directory for %(deployment)q"). | |
| 392 D("deployment", proj.project.dep
.title).Err() | |
| 393 } | |
| 394 return proj.stage(w, depDir, params) | |
| 395 } | |
| 396 } | |
| 397 }) | |
| 398 } | |
| 399 | |
| 400 // build performs the build stage on staged components. | |
| 401 func (reg *deploymentRegistry) localBuild(w *work) error { | |
| 402 // Enumerate over them in sorted order for determinism. | |
| 403 return w.RunMulti(func(workC chan<- func() error) { | |
| 404 // AppEngine Projects | |
| 405 for _, name := range reg.gaeProjectNames { | |
| 406 proj := reg.gaeProjects[name] | |
| 407 workC <- func() error { | |
| 408 return proj.localBuild(w) | |
| 409 } | |
| 410 } | |
| 411 | |
| 412 // Container Engine Projects | |
| 413 for _, name := range reg.gkeProjectNames { | |
| 414 proj := reg.gkeProjects[name] | |
| 415 workC <- func() error { | |
| 416 return proj.localBuild(w) | |
| 417 } | |
| 418 } | |
| 419 }) | |
| 420 } | |
| 421 | |
| 422 // push builds and pushes the deployments to remote services, but does not | |
| 423 // commit to them. | |
| 424 func (reg *deploymentRegistry) push(w *work) error { | |
| 425 // All components can be independently pushed in parallel. | |
| 426 // | |
| 427 // We will still enumerate over them in sorted order for determinism. | |
| 428 return w.RunMulti(func(workC chan<- func() error) { | |
| 429 // AppEngine Projects | |
| 430 for _, name := range reg.gaeProjectNames { | |
| 431 proj := reg.gaeProjects[name] | |
| 432 workC <- func() error { | |
| 433 return proj.push(w) | |
| 434 } | |
| 435 } | |
| 436 | |
| 437 // Container Engine Projects | |
| 438 for _, name := range reg.gkeProjectNames { | |
| 439 proj := reg.gkeProjects[name] | |
| 440 workC <- func() error { | |
| 441 return proj.push(w) | |
| 442 } | |
| 443 } | |
| 444 }) | |
| 445 } | |
| 446 | |
| 447 // commit commits the deployment to the remote services. | |
| 448 func (reg *deploymentRegistry) commit(w *work) error { | |
| 449 // All components can be independently committed in parallel. | |
| 450 // | |
| 451 // We will still enumerate over them in sorted order for determinism. | |
| 452 return w.RunMulti(func(workC chan<- func() error) { | |
| 453 // AppEngine Projects | |
| 454 for _, name := range reg.gaeProjectNames { | |
| 455 proj := reg.gaeProjects[name] | |
| 456 workC <- func() error { | |
| 457 return proj.commit(w) | |
| 458 } | |
| 459 } | |
| 460 | |
| 461 // Container Engine Projects | |
| 462 for _, name := range reg.gkeProjectNames { | |
| 463 proj := reg.gkeProjects[name] | |
| 464 workC <- func() error { | |
| 465 return proj.commit(w) | |
| 466 } | |
| 467 } | |
| 468 }) | |
| 469 } | |
| OLD | NEW |