/
deploy.go
150 lines (135 loc) · 4.02 KB
/
deploy.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// Copyright 2014-2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package operation
import (
"fmt"
"github.com/juju/errors"
corecharm "gopkg.in/juju/charm.v5"
"gopkg.in/juju/charm.v5/hooks"
"github.com/juju/juju/worker/uniter/charm"
"github.com/juju/juju/worker/uniter/hook"
)
// deploy implements charm install and charm upgrade operations.
type deploy struct {
DoesNotRequireMachineLock
kind Kind
charmURL *corecharm.URL
revert bool
resolved bool
callbacks Callbacks
deployer charm.Deployer
abort <-chan struct{}
}
// String is part of the Operation interface.
func (d *deploy) String() string {
verb := "upgrade to"
prefix := ""
switch {
case d.kind == Install:
verb = "install"
case d.revert:
prefix = "switch "
case d.resolved:
prefix = "continue "
}
return fmt.Sprintf("%s%s %s", prefix, verb, d.charmURL)
}
// Prepare downloads and verifies the charm, and informs the state server
// that the unit will be using it. If the supplied state indicates that a
// hook was pending, that hook is recorded in the returned state.
// Prepare is part of the Operation interface.
func (d *deploy) Prepare(state State) (*State, error) {
if err := d.checkAlreadyDone(state); err != nil {
return nil, errors.Trace(err)
}
if d.revert {
if err := d.deployer.NotifyRevert(); err != nil {
return nil, errors.Trace(err)
}
}
if d.resolved {
if err := d.deployer.NotifyResolved(); err != nil {
return nil, errors.Trace(err)
}
}
info, err := d.callbacks.GetArchiveInfo(d.charmURL)
if err != nil {
return nil, errors.Trace(err)
}
if err := d.deployer.Stage(info, d.abort); err != nil {
return nil, errors.Trace(err)
}
// note: yes, this *should* be in Prepare, not Execute. Before we can safely
// write out local state referencing the charm url (by returning the new
// State to the Executor, below), we have to register our interest in that
// charm on the state server. If we neglected to do so, the operation could
// race with a new service-charm-url change on the state server, and lead to
// failures on resume in which we try to obtain archive info for a charm that
// has already been removed from the state server.
if err := d.callbacks.SetCurrentCharm(d.charmURL); err != nil {
return nil, errors.Trace(err)
}
return d.getState(state, Pending), nil
}
// Execute installs or upgrades the prepared charm, and preserves any hook
// recorded in the supplied state.
// Execute is part of the Operation interface.
func (d *deploy) Execute(state State) (*State, error) {
if err := d.deployer.Deploy(); err == charm.ErrConflict {
return nil, NewDeployConflictError(d.charmURL)
} else if err != nil {
return nil, errors.Trace(err)
}
return d.getState(state, Done), nil
}
// Commit restores state for any interrupted hook, or queues an install or
// upgrade-charm hook if no hook was interrupted.
func (d *deploy) Commit(state State) (*State, error) {
if err := d.callbacks.InitializeMetricsCollector(); err != nil {
return nil, errors.Trace(err)
}
change := &stateChange{
Kind: RunHook,
}
if hookInfo := d.interruptedHook(state); hookInfo != nil {
change.Hook = hookInfo
change.Step = Pending
} else {
change.Hook = &hook.Info{Kind: deployHookKinds[d.kind]}
change.Step = Queued
}
return change.apply(state), nil
}
func (d *deploy) checkAlreadyDone(state State) error {
if state.Kind != d.kind {
return nil
}
if *state.CharmURL != *d.charmURL {
return nil
}
if state.Step == Done {
return ErrSkipExecute
}
return nil
}
func (d *deploy) getState(state State, step Step) *State {
return stateChange{
Kind: d.kind,
Step: step,
CharmURL: d.charmURL,
Hook: d.interruptedHook(state),
}.apply(state)
}
func (d *deploy) interruptedHook(state State) *hook.Info {
switch state.Kind {
case RunHook, Upgrade:
return state.Hook
}
return nil
}
// deployHookKinds determines what kind of hook should be queued after a
// given deployment operation.
var deployHookKinds = map[Kind]hooks.Kind{
Install: hooks.Install,
Upgrade: hooks.UpgradeCharm,
}