-
Notifications
You must be signed in to change notification settings - Fork 13
/
edit.go
171 lines (148 loc) · 4.6 KB
/
edit.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
package main
import (
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"reflect"
"strings"
"time"
"github.com/spf13/cobra"
"helm.sh/helm/v3/pkg/action"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/release"
"sigs.k8s.io/yaml"
)
var editHelp = `
This command return the values of given helm release in a text editor, and redploy the chart with the edited values.
`
type editCmd struct {
release string
out io.Writer
cfg *action.Configuration
allValues bool
editor string
timeout time.Duration
wait bool
revision int
disableDefaultIntersection bool
}
func newEditCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
edit := &editCmd{
out: out}
cmd := &cobra.Command{
Use: "edit [flags] RELEASE",
Short: "edit a release",
Long: editHelp,
RunE: func(cmd *cobra.Command, args []string) error {
edit.cfg = cfg
edit.editor = os.ExpandEnv(edit.editor)
if len(args) != 1 {
return fmt.Errorf("This command neeeds 1 argument: release name")
}
edit.release = args[0]
return edit.run()
},
}
f := cmd.Flags()
f.BoolVarP(&edit.allValues, "all", "a", false, "edit all (computed) vals")
f.IntVar(&edit.revision, "revision", 0, "edit the current chart with values from old revision")
f.DurationVar(&edit.timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)")
f.BoolVar(&edit.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.StringVarP(&edit.editor, "editor", "e", "$EDITOR", "name of the editor")
f.BoolVarP(&edit.disableDefaultIntersection, "disable-default-intersection", "m", false, "If set, user supplied values that are the same as default values won't be \"removed\" from user supplied values (careful using with -a, setting both of this params won't \"merge\" the values and all values will become user supplied values)")
return cmd
}
func (e *editCmd) vals(rel *release.Release) (map[string]interface{}, error) {
values := action.NewGetValues(e.cfg)
values.AllValues = e.allValues
values.Version = e.revision
return values.Run(rel.Name)
}
func (e *editCmd) getDefaultsIntersection(overridesMap map[string]interface{}, defaultMap map[string]interface{}) map[string]interface{} {
newMap := map[string]interface{}{}
for key, value := range overridesMap {
if _, ok := defaultMap[key]; ok {
if !reflect.DeepEqual(value, defaultMap[key]) {
innerMap, ok2 := value.(map[string]interface{})
innerMapDefault, ok2Default := defaultMap[key].(map[string]interface{})
if ok2 && ok2Default {
newMap[key] = e.getDefaultsIntersection(innerMap, innerMapDefault)
} else {
newMap[key] = value
}
}
} else {
newMap[key] = value
}
}
return newMap
}
func (e *editCmd) run() error {
getRelease := action.NewGet(e.cfg)
// getRelease.Version = 0
res, err := getRelease.Run(e.release)
if err != nil {
return err
}
tmpfile, err := ioutil.TempFile(os.TempDir(), fmt.Sprintf("helm-edit-%s", res.Name))
if err != nil {
return err
}
defer os.Remove(tmpfile.Name())
valsMap, err := e.vals(res)
if err != nil {
return err
}
vals, _ := yaml.Marshal(valsMap)
_, err = tmpfile.Write(vals)
if err != nil {
return err
}
if err := tmpfile.Close(); err != nil {
return err
}
editor := strings.Split(e.editor, " ")
cmd := exec.Command(editor[0], append(editor[1:], tmpfile.Name())...)
cmd.Stdin = os.Stdin
cmd.Stdout = e.out
cmd.Stderr = e.out
if err := cmd.Run(); err != nil {
return err
}
newValues, err := chartutil.ReadValuesFile(tmpfile.Name())
if err != nil {
return err
}
if err != nil {
return err
}
var userSuppliedVal map[string]interface{}
if e.disableDefaultIntersection {
userSuppliedVal = newValues.AsMap()
} else {
userSuppliedVal = e.getDefaultsIntersection(newValues.AsMap(), res.Chart.Values)
}
newValuesString, err := newValues.YAML()
if err != nil {
return err
}
if string(vals) != newValuesString {
upgrade := action.NewUpgrade(e.cfg)
upgrade.Wait = e.wait
upgrade.Timeout = e.timeout
res, err := upgrade.Run(
res.Name,
res.Chart,
userSuppliedVal)
if err != nil {
return err
}
fmt.Fprintf(e.out, "Release %q has been edited. Happy Helming!\n%s", e.release, res.Info.Notes)
// TODO: print the status like status command does
} else {
fmt.Fprintln(e.out, "Edit cancelled, no changes made!")
}
return nil
}