forked from helm/helm
-
Notifications
You must be signed in to change notification settings - Fork 0
/
upgrade.go
314 lines (267 loc) · 12.2 KB
/
upgrade.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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package main
import (
"fmt"
"io"
"strings"
"github.com/spf13/cobra"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/helm"
"k8s.io/helm/pkg/renderutil"
storageerrors "k8s.io/helm/pkg/storage/errors"
)
const upgradeDesc = `
This command upgrades a release to a specified version of a chart and/or updates chart values.
Required arguments are release and chart. The chart argument can be one of:
- a chart reference('stable/mariadb'); use '--version' and '--devel' flags for versions other than latest,
- a path to a chart directory,
- a packaged chart,
- a fully qualified URL.
To customize the chart values, use any of
- '--values'/'-f' to pass in a yaml file holding settings,
- '--set' to provide one or more key=val pairs directly,
- '--set-string' to provide key=val forcing val to be stored as a string,
- '--set-file' to provide key=path to read a single large value from a file at path.
To edit or append to the existing customized values, add the
'--reuse-values' flag, otherwise any existing customized values are ignored.
If no chart value arguments are provided on the command line, any existing customized values are carried
forward. If you want to revert to just the values provided in the chart, use the '--reset-values' flag.
You can specify any of the chart value flags multiple times. The priority will be given to the last
(right-most) value specified. For example, if both myvalues.yaml and override.yaml contained a key
called 'Test', the value set in override.yaml would take precedence:
$ helm upgrade -f myvalues.yaml -f override.yaml redis ./redis
Note that the key name provided to the '--set', '--set-string' and '--set-file' flags can reference
structure elements. Examples:
- mybool=TRUE
- livenessProbe.timeoutSeconds=10
- metrics.annotations[0]=hey,metrics.annotations[1]=ho
which sets the top level key mybool to true, the nested timeoutSeconds to 10, and two array values, respectively.
Note that the value side of the key=val provided to '--set' and '--set-string' flags will pass through
shell evaluation followed by yaml type parsing to produce the final value. This may alter inputs with
special characters in unexpected ways, for example
$ helm upgrade --set pwd=3jk$o2,z=f\30.e redis ./redis
results in "pwd: 3jk" and "z: f30.e". Use single quotes to avoid shell evaluation and argument delimiters,
and use backslash to escape yaml special characters:
$ helm upgrade --set pwd='3jk$o2z=f\\30.e' redis ./redis
which results in the expected "pwd: 3jk$o2z=f\30.e". If a single quote occurs in your value then follow
your shell convention for escaping it; for example in bash:
$ helm upgrade --set pwd='3jk$o2z=f\\30with'\''quote'
which results in "pwd: 3jk$o2z=f\30with'quote".
`
type upgradeCmd struct {
release string
chart string
out io.Writer
client helm.Interface
dryRun bool
recreate bool
force bool
disableHooks bool
valueFiles valueFiles
values []string
stringValues []string
fileValues []string
verify bool
keyring string
install bool
namespace string
version string
timeout int64
resetValues bool
reuseValues bool
wait bool
atomic bool
repoURL string
username string
password string
devel bool
subNotes bool
description string
certFile string
keyFile string
caFile string
}
func newUpgradeCmd(client helm.Interface, out io.Writer) *cobra.Command {
upgrade := &upgradeCmd{
out: out,
client: client,
}
cmd := &cobra.Command{
Use: "upgrade [RELEASE] [CHART]",
Short: "upgrade a release",
Long: upgradeDesc,
PreRunE: func(_ *cobra.Command, _ []string) error { return setupConnection() },
RunE: func(cmd *cobra.Command, args []string) error {
if err := checkArgsLength(len(args), "release name", "chart path"); err != nil {
return err
}
if upgrade.version == "" && upgrade.devel {
debug("setting version to >0.0.0-0")
upgrade.version = ">0.0.0-0"
}
upgrade.release = args[0]
upgrade.chart = args[1]
upgrade.client = ensureHelmClient(upgrade.client)
upgrade.wait = upgrade.wait || upgrade.atomic
return upgrade.run()
},
}
f := cmd.Flags()
settings.AddFlagsTLS(f)
f.VarP(&upgrade.valueFiles, "values", "f", "specify values in a YAML file or a URL(can specify multiple)")
f.BoolVar(&upgrade.dryRun, "dry-run", false, "simulate an upgrade")
f.BoolVar(&upgrade.recreate, "recreate-pods", false, "performs pods restart for the resource if applicable")
f.BoolVar(&upgrade.force, "force", false, "force resource update through delete/recreate if needed")
f.StringArrayVar(&upgrade.values, "set", []string{}, "set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&upgrade.stringValues, "set-string", []string{}, "set STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
f.StringArrayVar(&upgrade.fileValues, "set-file", []string{}, "set values from respective files specified via the command line (can specify multiple or separate values with commas: key1=path1,key2=path2)")
f.BoolVar(&upgrade.disableHooks, "disable-hooks", false, "disable pre/post upgrade hooks. DEPRECATED. Use no-hooks")
f.BoolVar(&upgrade.disableHooks, "no-hooks", false, "disable pre/post upgrade hooks")
f.BoolVar(&upgrade.verify, "verify", false, "verify the provenance of the chart before upgrading")
f.StringVar(&upgrade.keyring, "keyring", defaultKeyring(), "path to the keyring that contains public signing keys")
f.BoolVarP(&upgrade.install, "install", "i", false, "if a release by this name doesn't already exist, run an install")
f.StringVar(&upgrade.namespace, "namespace", "", "namespace to install the release into (only used if --install is set). Defaults to the current kube config namespace")
f.StringVar(&upgrade.version, "version", "", "specify the exact chart version to use. If this is not specified, the latest version is used")
f.Int64Var(&upgrade.timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&upgrade.resetValues, "reset-values", false, "when upgrading, reset the values to the ones built into the chart")
f.BoolVar(&upgrade.reuseValues, "reuse-values", false, "when upgrading, reuse the last release's values and merge in any overrides from the command line via --set and -f. If '--reset-values' is specified, this is ignored.")
f.BoolVar(&upgrade.wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful. It will wait for as long as --timeout")
f.BoolVar(&upgrade.atomic, "atomic", false, "if set, upgrade process rolls back changes made in case of failed upgrade, also sets --wait flag")
f.StringVar(&upgrade.repoURL, "repo", "", "chart repository url where to locate the requested chart")
f.StringVar(&upgrade.username, "username", "", "chart repository username where to locate the requested chart")
f.StringVar(&upgrade.password, "password", "", "chart repository password where to locate the requested chart")
f.StringVar(&upgrade.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
f.StringVar(&upgrade.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
f.StringVar(&upgrade.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&upgrade.devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored.")
f.BoolVar(&upgrade.subNotes, "render-subchart-notes", false, "render subchart notes along with parent")
f.StringVar(&upgrade.description, "description", "", "specify the description to use for the upgrade, rather than the default")
f.MarkDeprecated("disable-hooks", "use --no-hooks instead")
// set defaults from environment
settings.InitTLS(f)
return cmd
}
func (u *upgradeCmd) run() error {
chartPath, err := locateChartPath(u.repoURL, u.username, u.password, u.chart, u.version, u.verify, u.keyring, u.certFile, u.keyFile, u.caFile)
if err != nil {
return err
}
releaseHistory, err := u.client.ReleaseHistory(u.release, helm.WithMaxHistory(1))
if u.install {
// If a release does not exist, install it. If another error occurs during
// the check, ignore the error and continue with the upgrade.
//
// The returned error is a grpc.rpcError that wraps the message from the original error.
// So we're stuck doing string matching against the wrapped error, which is nested somewhere
// inside of the grpc.rpcError message.
if err == nil {
if u.namespace == "" {
u.namespace = defaultNamespace()
}
previousReleaseNamespace := releaseHistory.Releases[0].Namespace
if previousReleaseNamespace != u.namespace {
fmt.Fprintf(u.out,
"WARNING: Namespace %q doesn't match with previous. Release will be deployed to %s\n",
u.namespace, previousReleaseNamespace,
)
}
}
if err != nil && strings.Contains(err.Error(), storageerrors.ErrReleaseNotFound(u.release).Error()) {
fmt.Fprintf(u.out, "Release %q does not exist. Installing it now.\n", u.release)
ic := &installCmd{
chartPath: chartPath,
client: u.client,
out: u.out,
name: u.release,
valueFiles: u.valueFiles,
dryRun: u.dryRun,
verify: u.verify,
disableHooks: u.disableHooks,
keyring: u.keyring,
values: u.values,
stringValues: u.stringValues,
fileValues: u.fileValues,
namespace: u.namespace,
timeout: u.timeout,
wait: u.wait,
description: u.description,
atomic: u.atomic,
}
return ic.run()
}
}
rawVals, err := vals(u.valueFiles, u.values, u.stringValues, u.fileValues, u.certFile, u.keyFile, u.caFile)
if err != nil {
return err
}
// Check chart requirements to make sure all dependencies are present in /charts
if ch, err := chartutil.Load(chartPath); err == nil {
if req, err := chartutil.LoadRequirements(ch); err == nil {
if err := renderutil.CheckDependencies(ch, req); err != nil {
return err
}
} else if err != chartutil.ErrRequirementsNotFound {
return fmt.Errorf("cannot load requirements: %v", err)
}
} else {
return prettyError(err)
}
resp, err := u.client.UpdateRelease(
u.release,
chartPath,
helm.UpdateValueOverrides(rawVals),
helm.UpgradeDryRun(u.dryRun),
helm.UpgradeRecreate(u.recreate),
helm.UpgradeForce(u.force),
helm.UpgradeDisableHooks(u.disableHooks),
helm.UpgradeTimeout(u.timeout),
helm.ResetValues(u.resetValues),
helm.ReuseValues(u.reuseValues),
helm.UpgradeSubNotes(u.subNotes),
helm.UpgradeWait(u.wait),
helm.UpgradeDescription(u.description))
if err != nil {
fmt.Fprintf(u.out, "UPGRADE FAILED\nROLLING BACK\nError: %v\n", prettyError(err))
if u.atomic {
rollback := &rollbackCmd{
out: u.out,
client: u.client,
name: u.release,
dryRun: u.dryRun,
recreate: u.recreate,
force: u.force,
timeout: u.timeout,
wait: u.wait,
description: "",
revision: releaseHistory.Releases[0].Version,
disableHooks: u.disableHooks,
}
if err := rollback.run(); err != nil {
return err
}
}
return fmt.Errorf("UPGRADE FAILED: %v", prettyError(err))
}
if settings.Debug {
printRelease(u.out, resp.Release)
}
fmt.Fprintf(u.out, "Release %q has been upgraded. Happy Helming!\n", u.release)
// Print the status like status command does
status, err := u.client.ReleaseStatus(u.release)
if err != nil {
return prettyError(err)
}
PrintStatus(u.out, status)
return nil
}