forked from skeema/skeema
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cmd_push.go
142 lines (126 loc) · 5.99 KB
/
cmd_push.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
package main
import (
"context"
"sync"
log "github.com/sirupsen/logrus"
"github.com/skeema/mybase"
"github.com/skeema/skeema/internal/applier"
"github.com/skeema/skeema/internal/fs"
"github.com/skeema/skeema/internal/linter"
"github.com/skeema/skeema/internal/workspace"
"golang.org/x/sync/errgroup"
)
func init() {
summary := "Alter objects on DBs to reflect the filesystem representation"
desc := "Modifies the schemas on database instance(s) to match the corresponding " +
"filesystem representation of them. This essentially performs the same diff logic " +
"as `skeema diff`, but then actually runs the generated DDL. You should generally " +
"run `skeema diff` first to see what changes will be applied.\n\n" +
"You may optionally pass an environment name as a CLI arg. This will affect " +
"which section of .skeema config files is used for processing. For example, " +
"running `skeema push staging` will apply config directives from the " +
"[staging] section of config files, as well as any sectionless directives at the " +
"top of the file. If no environment name is supplied, the default is \"production\".\n\n" +
"An exit code of 0 will be returned if the operation was fully successful; 1 if " +
"at least one table could not be updated due to use of unsupported features, or if " +
"the --dry-run option was used and differences were found; or 2+ if a fatal error " +
"occurred."
cmd := mybase.NewCommand("push", summary, desc, PushHandler)
cmd.AddOptions("SQL generation",
mybase.BoolOption("exact-match", 0, false, "Follow *.sql table definitions exactly, even for differences with no functional impact"),
mybase.BoolOption("compare-metadata", 0, false, "For stored programs, detect changes to creation-time sql_mode or DB collation"),
mybase.BoolOption("alter-validate-virtual", 0, false, "Apply a WITH VALIDATION clause to ALTER TABLEs affecting virtual columns"),
mybase.StringOption("alter-lock", 0, "", `Apply a LOCK clause to all ALTER TABLEs (valid values: "none", "shared", "exclusive")`),
mybase.StringOption("alter-algorithm", 0, "", `Apply an ALGORITHM clause to all ALTER TABLEs (valid values: "inplace", "copy", "instant", "nocopy")`),
mybase.StringOption("partitioning", 0, "keep", `Specify handling of partitioning status on the database side (valid values: "keep", "remove", "modify")`),
)
cmd.AddOptions("External tool",
mybase.StringOption("alter-wrapper", 'x', "", "External bin to shell out to for ALTER TABLE; see manual for template vars"),
mybase.StringOption("alter-wrapper-min-size", 0, "0", "Ignore --alter-wrapper for tables smaller than this size in bytes"),
mybase.StringOption("ddl-wrapper", 'X', "", "Like --alter-wrapper, but applies to all DDL types (CREATE, DROP, ALTER)"),
)
cmd.AddOptions("linter rule",
mybase.BoolOption("lint", 0, true, "Check modified objects for problems before proceeding"),
)
linter.AddCommandOptions(cmd)
cmd.AddOptions("safety",
mybase.BoolOption("verify", 0, true, "Test all generated ALTER statements on temp schema to verify correctness"),
mybase.BoolOption("allow-unsafe", 0, false, "Permit running ALTER or DROP operations that are potentially destructive"),
mybase.BoolOption("dry-run", 0, false, "Output DDL but don't run it; equivalent to `skeema diff`"),
mybase.BoolOption("foreign-key-checks", 0, false, "Force the server to check referential integrity of any new foreign key"),
mybase.StringOption("safe-below-size", 0, "0", "Always permit destructive operations for tables below this size in bytes"),
)
cmd.AddOptions("sharding",
mybase.BoolOption("first-only", '1', false, "For dirs mapping to multiple instances or schemas, just run against the first per dir"),
mybase.BoolOption("brief", 'q', false, "<overridden by diff command>").Hidden(),
mybase.StringOption("concurrent-instances", 'c', "1", "Perform operations on this number of instances concurrently"),
)
workspace.AddCommandOptions(cmd)
cmd.AddArg("environment", "production", false)
CommandSuite.AddSubCommand(cmd)
clonePushOptionsToDiff()
}
// PushHandler is the handler method for `skeema push`
func PushHandler(cfg *mybase.Config) error {
// Set up some config overrides relating to --brief output mode:
// * --brief only affects `skeema diff` (aka `skeema push --dry-run`)
// * --brief automatically uses --skip-verify --skip-lint --allow-unsafe
// * --brief omits INFO-level logging, unless --debug was used
if !cfg.GetBool("dry-run") {
cfg.SetRuntimeOverride("brief", "0")
} else if cfg.GetBool("brief") {
cfg.SetRuntimeOverride("verify", "0")
cfg.SetRuntimeOverride("lint", "0")
cfg.SetRuntimeOverride("allow-unsafe", "1")
if !cfg.GetBool("debug") {
log.SetLevel(log.WarnLevel)
}
}
dir, err := fs.ParseDir(".", cfg)
if err != nil {
return err
}
concurrency, err := dir.Config.GetInt("concurrent-instances")
if err != nil {
return NewExitValue(CodeBadConfig, err.Error())
} else if concurrency < 1 {
return NewExitValue(CodeBadConfig, "concurrent-instances cannot be less than 1")
}
printer := applier.NewPrinter(dir.Config)
g, ctx := errgroup.WithContext(context.Background())
g.SetLimit(concurrency)
groups, skipCount := applier.TargetGroupsForDir(dir)
sum := applier.Result{SkipCount: skipCount}
var sumLock sync.Mutex
for n := range groups {
tg := groups[n] // avoid loop iteration variable in closure below
g.Go(func() error {
defer panicHandler()
for _, t := range tg {
select {
case <-ctx.Done():
return nil // Exit early if context cancelled
default:
result, err := applier.ApplyTarget(t, printer)
if err != nil {
return err
}
sumLock.Lock()
sum.Merge(result)
sumLock.Unlock()
}
}
return nil
})
}
if err := g.Wait(); err != nil {
return err
} else if sum.SkipCount > 0 {
return NewExitValue(CodeFatalError, sum.Summary())
} else if sum.UnsupportedCount > 0 {
return NewExitValue(CodePartialError, sum.Summary())
} else if dir.Config.GetBool("dry-run") && sum.Differences {
return NewExitValue(CodeDifferencesFound, "")
}
return nil
}