/
deploy.go
144 lines (128 loc) · 3.94 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
// Copyright 2014-2015 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package operation
import (
"fmt"
"github.com/juju/charm/v12/hooks"
"github.com/juju/errors"
"github.com/juju/juju/worker/uniter/charm"
"github.com/juju/juju/worker/uniter/hook"
"github.com/juju/juju/worker/uniter/remotestate"
)
// deploy implements charm install and charm upgrade operations.
type deploy struct {
DoesNotRequireMachineLock
kind Kind
charmURL string
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 controller
// 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)
}
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 controller. If we neglected to do so, the operation could
// race with a new application-charm-url change on the controller, and lead to
// failures on resume in which we try to obtain archive info for a charm that
// has already been removed from the controller.
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) {
change := &stateChange{
Kind: RunHook,
}
if hookInfo := d.interruptedHook(state); hookInfo != nil {
change.Hook = hookInfo
change.Step = Pending
change.HookStep = state.HookStep
} else {
change.Hook = &hook.Info{Kind: deployHookKinds[d.kind]}
change.Step = Queued
}
return change.apply(state), nil
}
// RemoteStateChanged is called when the remote state changed during execution
// of the operation.
func (d *deploy) RemoteStateChanged(snapshot remotestate.Snapshot) {
}
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),
HookStep: state.HookStep,
}.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,
}