Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1331)

Side by Side Diff: deploytool/cmd/deploy.go

Issue 2182213002: deploytool: Add README.md, migrate docs to it. (Closed) Base URL: https://github.com/luci/luci-go@master
Patch Set: Rename to "luci_deploy" Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « deploytool/cmd/config.go ('k') | deploytool/cmd/deploy_appengine.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « deploytool/cmd/config.go ('k') | deploytool/cmd/deploy_appengine.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698