-
Notifications
You must be signed in to change notification settings - Fork 221
/
config.go
135 lines (113 loc) · 3.46 KB
/
config.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
package machine
import (
"context"
"encoding/json"
"fmt"
"regexp"
"slices"
"strings"
"github.com/google/go-cmp/cmp"
"github.com/superfly/flyctl/api"
"github.com/superfly/flyctl/helpers"
"github.com/superfly/flyctl/internal/prompt"
"github.com/superfly/flyctl/iostreams"
)
type ErrNoConfigChangesFound struct{}
func (e *ErrNoConfigChangesFound) Error() string {
return "no config changes found"
}
func ConfirmConfigChanges(ctx context.Context, machine *api.Machine, targetConfig api.MachineConfig, customPrompt string) (bool, error) {
var (
io = iostreams.FromContext(ctx)
colorize = io.ColorScheme()
)
diff := configCompare(ctx, *machine.Config, targetConfig)
if diff == "" {
return false, &ErrNoConfigChangesFound{}
}
if customPrompt != "" {
fmt.Fprintf(io.Out, customPrompt)
} else {
fmt.Fprintf(io.Out, "Configuration changes to be applied to machine: %s (%s)\n", colorize.Bold(machine.ID), colorize.Bold(machine.Name))
}
fmt.Fprintf(io.Out, "\n%s\n", diff)
const msg = "Apply changes?"
switch confirmed, err := prompt.Confirmf(ctx, msg); {
case err == nil:
if !confirmed {
return false, nil
}
case prompt.IsNonInteractive(err):
return false, prompt.NonInteractiveError("yes flag must be specified when not running interactively")
default:
return false, err
}
return true, nil
}
// CloneConfig deep-copies a MachineConfig.
// If CloneConfig is called on a nil config, nil is returned.
func CloneConfig(orig *api.MachineConfig) *api.MachineConfig {
if orig == nil {
return nil
}
return helpers.Clone(orig)
}
var cmpOptions = cmp.Options{
cmp.FilterValues(
func(x, y []byte) bool { return json.Valid(x) && json.Valid(y) },
cmp.Transformer("parseJSON",
func(in []byte) (out string) {
return string(in)
})),
}
func configCompare(ctx context.Context, original api.MachineConfig, new api.MachineConfig) string {
io := iostreams.FromContext(ctx)
colorize := io.ColorScheme()
origBytes, _ := json.MarshalIndent(original, "", " ")
newBytes, _ := json.MarshalIndent(new, "", " ")
if cmp.Equal(origBytes, newBytes, cmpOptions) {
return ""
}
diff := cmp.Diff(origBytes, newBytes, cmpOptions)
diffSlice := strings.Split(diff, "\n")
var str string
additionReg := regexp.MustCompile(`^\+.*`)
deletionReg := regexp.MustCompile(`^\-.*`)
// Highlight additions/deletions
for _, val := range diffSlice {
vB := []byte(val)
if additionReg.Match(vB) {
str += colorize.Green(val) + "\n"
} else if deletionReg.Match(vB) {
str += colorize.Red(val) + "\n"
} else {
str += val + "\n"
}
}
// Cleanup output
delim := "\"\"\""
rx := regexp.MustCompile(`(?s)` + regexp.QuoteMeta(delim) + `(.*?)` + regexp.QuoteMeta(delim))
match := rx.FindStringSubmatch(str)
if len(match) > 0 {
return strings.Trim(match[1], "\n")
}
// We know the objects are different, if we can't cleanup return the best we have got
return str
}
// MergeFiles merges the files parsed from the command line or fly.toml into the machine configuration.
func MergeFiles(machineConf *api.MachineConfig, files []*api.File) {
for _, f := range files {
idx := slices.IndexFunc(machineConf.Files, func(i *api.File) bool {
return i.GuestPath == f.GuestPath
})
switch {
case idx == -1:
machineConf.Files = append(machineConf.Files, f)
continue
case f.RawValue == nil && f.SecretName == nil:
machineConf.Files = slices.Delete(machineConf.Files, idx, idx+1)
default:
machineConf.Files = slices.Replace(machineConf.Files, idx, idx+1, f)
}
}
}