Track database flavor (vendor and version) in .skeema files
This commit adds a new "flavor" option, which in Skeema v1.0.x is just informational (no functional impact). `skeema init`, `skeema pull`, and `skeema add-environment` now automatically populate this option in host-level .skeema files, to persist the database's vendor (mysql/percona/mariadb) and major.minor versions. This information may be used in new features beginning in Skeema v1.1.
This commit is contained in:
Родитель
d5db1f4db0
Коммит
6f660d90c6
|
@ -43,7 +43,7 @@ func AddEnvHandler(cfg *mybase.Config) error {
|
|||
}
|
||||
|
||||
hostOptionFile, err := dir.OptionFile()
|
||||
if err != nil || hostOptionFile == nil {
|
||||
if err != nil {
|
||||
return NewExitValue(CodeBadInput, "Unable to read .skeema file for %s: %s", dir, err)
|
||||
}
|
||||
|
||||
|
@ -80,6 +80,9 @@ func AddEnvHandler(cfg *mybase.Config) error {
|
|||
} else {
|
||||
hostOptionFile.SetOptionValue(environment, "port", strconv.Itoa(inst.Port))
|
||||
}
|
||||
if flavor := inst.Flavor(); flavor != tengo.FlavorUnknown {
|
||||
hostOptionFile.SetOptionValue(environment, "flavor", flavor.String())
|
||||
}
|
||||
if cfg.OnCLI("user") {
|
||||
hostOptionFile.SetOptionValue(environment, "user", cfg.Get("user"))
|
||||
}
|
||||
|
|
|
@ -123,6 +123,9 @@ func InitHandler(cfg *mybase.Config) error {
|
|||
if cfg.OnCLI("user") {
|
||||
hostOptionFile.SetOptionValue(environment, "user", cfg.Get("user"))
|
||||
}
|
||||
if flavor := inst.Flavor(); flavor != tengo.FlavorUnknown {
|
||||
hostOptionFile.SetOptionValue(environment, "flavor", flavor.String())
|
||||
}
|
||||
if cfg.OnCLI("ignore-schema") {
|
||||
hostOptionFile.SetOptionValue(environment, "ignore-schema", cfg.Get("ignore-schema"))
|
||||
}
|
||||
|
|
24
cmd_pull.go
24
cmd_pull.go
|
@ -69,11 +69,8 @@ func PullHandler(cfg *mybase.Config) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
optionFile, err := t.Dir.OptionFile()
|
||||
if err != nil {
|
||||
if optionFile, err := t.Dir.OptionFile(); err != nil {
|
||||
log.Warnf("Unable to update character set and/or collation for %s/.skeema: %s", t.Dir, err)
|
||||
} else if optionFile == nil {
|
||||
log.Warnf("Unable to update character set and/or collation for %s/.skeema: cannot read file", t.Dir)
|
||||
} else {
|
||||
if instCharSet != t.SchemaFromInstance.CharSet {
|
||||
optionFile.SetOptionValue("", "default-character-set", t.SchemaFromInstance.CharSet)
|
||||
|
@ -246,6 +243,25 @@ func findNewSchemas(dir *Dir) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Update the instance dir's .skeema option file if the instance's current
|
||||
// flavor does not match what's in the file
|
||||
if instFlavor := instance.Flavor(); instFlavor.String() != dir.Config.Get("flavor") {
|
||||
if optionFile, err := dir.OptionFile(); err != nil {
|
||||
log.Warnf("Unable to update flavor in %s/.skeema: %s", dir, err)
|
||||
} else {
|
||||
if instFlavor == tengo.FlavorUnknown {
|
||||
optionFile.UnsetOptionValue(dir.section, "flavor")
|
||||
} else {
|
||||
optionFile.SetOptionValue(dir.section, "flavor", instFlavor.String())
|
||||
}
|
||||
if err := optionFile.Write(true); err != nil {
|
||||
log.Warnf("Unable to update flavor in %s: %s", optionFile.Path(), err)
|
||||
} else {
|
||||
log.Infof("Wrote %s -- updated flavor to %s", optionFile.Path(), instFlavor.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subdirHasSchema := make(map[string]bool)
|
||||
for _, subdir := range subdirs {
|
||||
// We only want to evaluate subdirs that explicitly define the schema option
|
||||
|
|
|
@ -30,6 +30,7 @@ func AddGlobalOptions(cmd *mybase.Command) {
|
|||
cmd.AddOption(mybase.StringOption("ignore-table", 0, "", "Ignore tables that match regex").Hidden())
|
||||
cmd.AddOption(mybase.StringOption("default-character-set", 0, "", "Schema-level default character set").Hidden())
|
||||
cmd.AddOption(mybase.StringOption("default-collation", 0, "", "Schema-level default collation").Hidden())
|
||||
cmd.AddOption(mybase.StringOption("flavor", 0, "", "Database server expressed in format vendor:major.minor, for use in vendor/version specific syntax").Hidden())
|
||||
|
||||
// Visible global options
|
||||
cmd.AddOption(mybase.StringOption("user", 'u', "root", "Username to connect to database host"))
|
||||
|
|
6
dir.go
6
dir.go
|
@ -120,7 +120,7 @@ func (dir *Dir) HasOptionFile() bool {
|
|||
// .skeema option file in the currently-selected environment section.
|
||||
func (dir *Dir) HasHost() bool {
|
||||
optionFile, err := dir.OptionFile()
|
||||
if err != nil || optionFile == nil {
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_, ok := optionFile.OptionValue("host")
|
||||
|
@ -131,7 +131,7 @@ func (dir *Dir) HasHost() bool {
|
|||
// .skeema option file in the currently-selected environment section.
|
||||
func (dir *Dir) HasSchema() bool {
|
||||
optionFile, err := dir.OptionFile()
|
||||
if err != nil || optionFile == nil {
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_, ok := optionFile.OptionValue("schema")
|
||||
|
@ -633,6 +633,8 @@ func (dir *Dir) TargetTemplate(instance *tengo.Instance) Target {
|
|||
// the dir's .skeema file, if one exists. The file will be read and parsed; any
|
||||
// errors in either process will be returned. The section specified by
|
||||
// dir.section will automatically be selected for use in the file if it exists.
|
||||
// If there is no option file in this dir, it is considered an error (return is
|
||||
// nil File and non-nil error).
|
||||
func (dir *Dir) OptionFile() (*mybase.File, error) {
|
||||
f := mybase.NewFile(dir.Path, ".skeema")
|
||||
if err := f.Read(); err != nil {
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
* [dry-run](#dry-run)
|
||||
* [exact-match](#exact-match)
|
||||
* [first-only](#first-only)
|
||||
* [flavor](#flavor)
|
||||
* [foreign-key-checks](#foreign-key-checks)
|
||||
* [host](#host)
|
||||
* [host-wrapper](#host-wrapper)
|
||||
|
@ -330,6 +331,20 @@ Ordinarily, for individual directories that map to multiple instances and/or mul
|
|||
|
||||
In a sharded environment, this option can be useful to examine or execute a change only on one shard, before pushing it out on all shards. Alternatively, for more complex control, a similar effect can be achieved by using environment names. For example, you could create an environment called "production-canary" with [host](#host) configured to map to a subset of the instances in the "production" environment.
|
||||
|
||||
### flavor
|
||||
|
||||
Commands | *all*
|
||||
--- | :---
|
||||
**Default** | *empty string*
|
||||
**Type** | string
|
||||
**Restrictions** | Should only appear in a .skeema option file that also contains [host](#host)
|
||||
|
||||
This option indicates the database server vendor and version corresponding to the first [host](#host) defined in this directory. The value is formatted as "vendor:major.minor", for example "mysql:5.6", "percona:5.7", or "mariadb:10.1".
|
||||
|
||||
This option is automatically populated in host-level .skeema files by `skeema init`, `skeema pull`, and `skeema add-environment` beginning in Skeema v1.0.3.
|
||||
|
||||
Currently, this option is just informational and has no functional effect whatsoever in Skeema v1.0.x. In future releases, it may be used for purposes such as optionally offloading the [temporary schema operations](faq.md#temporary-schema-usage) to a local Docker container; the [flavor](#flavor) value will then be used to ensure the correct Docker image is used.
|
||||
|
||||
### foreign-key-checks
|
||||
|
||||
Commands | push
|
||||
|
|
|
@ -14,7 +14,7 @@ schema to the filesystem, and apply online schema changes by modifying files.`
|
|||
|
||||
// Globals overridden by GoReleaser's ldflags
|
||||
var (
|
||||
version = "1.0.2-dev"
|
||||
version = "1.0.3-dev"
|
||||
commit = "unknown"
|
||||
date = "unknown"
|
||||
)
|
||||
|
|
|
@ -123,18 +123,20 @@ func (s *SkeemaIntegrationSuite) TestAddEnvHandler(t *testing.T) {
|
|||
origFile := getOptionFile(t, "mydb", cfg)
|
||||
|
||||
// valid dir should succeed and add the section to the .skeema file
|
||||
cfg = s.handleCommand(t, CodeSuccess, ".", "skeema add-environment --host my.staging.db.com --dir mydb staging")
|
||||
// Intentionally using a low connection timeout here to avoid delaying the
|
||||
// test with the invalid hostname
|
||||
cfg = s.handleCommand(t, CodeSuccess, ".", "skeema add-environment --host my.staging.invalid --dir mydb staging --connect-options='timeout=10ms'")
|
||||
file := getOptionFile(t, "mydb", cfg)
|
||||
origFile.SetOptionValue("staging", "host", "my.staging.db.com")
|
||||
origFile.SetOptionValue("staging", "host", "my.staging.invalid")
|
||||
origFile.SetOptionValue("staging", "port", "3306")
|
||||
if !origFile.SameContents(file) {
|
||||
t.Fatalf("File contents of %s do not match expectation", file.Path())
|
||||
}
|
||||
|
||||
// Nonstandard port should work properly; ditto for user option persisting
|
||||
cfg = s.handleCommand(t, CodeSuccess, ".", "skeema add-environment --host my.ci.db.com -P 3307 -ufoobar --dir mydb ci")
|
||||
cfg = s.handleCommand(t, CodeSuccess, ".", "skeema add-environment --host my.ci.invalid -P 3307 -ufoobar --dir mydb ci --connect-options='timeout=10ms'")
|
||||
file = getOptionFile(t, "mydb", cfg)
|
||||
origFile.SetOptionValue("ci", "host", "my.ci.db.com")
|
||||
origFile.SetOptionValue("ci", "host", "my.ci.invalid")
|
||||
origFile.SetOptionValue("ci", "port", "3307")
|
||||
origFile.SetOptionValue("ci", "user", "foobar")
|
||||
if !origFile.SameContents(file) {
|
||||
|
@ -163,8 +165,10 @@ func (s *SkeemaIntegrationSuite) TestPullHandler(t *testing.T) {
|
|||
|
||||
// Revert db back to previous state, and pull again to test the opposite
|
||||
// behaviors: delete dir for new schema, remove charset/collation from .skeema,
|
||||
// etc
|
||||
// etc. Also edit the host .skeema file to remove flavor, to test logic that
|
||||
// adds/updates flavor on pull.
|
||||
s.cleanData(t, "setup.sql")
|
||||
writeFile(t, "mydb/.skeema", strings.Replace(readFile(t, "mydb/.skeema"), "flavor", "#flavor", 1))
|
||||
cfg = s.handleCommand(t, CodeSuccess, ".", "skeema pull")
|
||||
s.verifyFiles(t, cfg, "../golden/init")
|
||||
|
||||
|
|
|
@ -213,9 +213,15 @@ func (s *SkeemaIntegrationSuite) verifyFiles(t *testing.T, cfg *mybase.Config, d
|
|||
aOptionFile.SetOptionValue(section, "port", forcedValue)
|
||||
}
|
||||
}
|
||||
// Force flavor of a to match the DockerizedInstance's flavor
|
||||
for _, section := range aOptionFile.SectionsWithOption("flavor") {
|
||||
aOptionFile.SetOptionValue(section, "flavor", s.d.Flavor().String())
|
||||
}
|
||||
|
||||
if !aOptionFile.SameContents(bOptionFile) {
|
||||
t.Errorf("File contents do not match between %s and %s", aOptionFile.Path(), bOptionFile.Path())
|
||||
fmt.Println("Expected:\n", readFile(t, aOptionFile.Path()))
|
||||
fmt.Println("Actual:\n", readFile(t, bOptionFile.Path()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
[production]
|
||||
host=127.0.0.1
|
||||
port={DYNAMICALLY INSERTED BY TEST}
|
||||
flavor={DYNAMICALLY INSERTED BY TEST}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
[production]
|
||||
host=127.0.0.1
|
||||
port={DYNAMICALLY INSERTED BY TEST}
|
||||
flavor={DYNAMICALLY INSERTED BY TEST}
|
||||
ignore-schema=^archives$
|
||||
ignore-table=^_
|
|
@ -1,3 +1,4 @@
|
|||
[production]
|
||||
host=127.0.0.1
|
||||
port={DYNAMICALLY INSERTED BY TEST}
|
||||
flavor={DYNAMICALLY INSERTED BY TEST}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
[production]
|
||||
host=127.0.0.1
|
||||
port={DYNAMICALLY INSERTED BY TEST}
|
||||
flavor={DYNAMICALLY INSERTED BY TEST}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
[production]
|
||||
host=127.0.0.1
|
||||
port={DYNAMICALLY INSERTED BY TEST}
|
||||
flavor={DYNAMICALLY INSERTED BY TEST}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
[production]
|
||||
host=127.0.0.1
|
||||
port={DYNAMICALLY INSERTED BY TEST}
|
||||
flavor={DYNAMICALLY INSERTED BY TEST}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
[production]
|
||||
host=127.0.0.1
|
||||
port={DYNAMICALLY INSERTED BY TEST}
|
||||
flavor={DYNAMICALLY INSERTED BY TEST}
|
||||
ignore-schema=^archives$
|
||||
ignore-table=^_
|
|
@ -1,3 +1,4 @@
|
|||
[production]
|
||||
host=127.0.0.1
|
||||
port={DYNAMICALLY INSERTED BY TEST}
|
||||
flavor={DYNAMICALLY INSERTED BY TEST}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
[production]
|
||||
host=127.0.0.1
|
||||
port={DYNAMICALLY INSERTED BY TEST}
|
||||
flavor={DYNAMICALLY INSERTED BY TEST}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
[production]
|
||||
host=127.0.0.1
|
||||
port={DYNAMICALLY INSERTED BY TEST}
|
||||
flavor={DYNAMICALLY INSERTED BY TEST}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
[production]
|
||||
host=127.0.0.1
|
||||
port={DYNAMICALLY INSERTED BY TEST}
|
||||
flavor={DYNAMICALLY INSERTED BY TEST}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
[production]
|
||||
host=127.0.0.1
|
||||
port={DYNAMICALLY INSERTED BY TEST}
|
||||
flavor={DYNAMICALLY INSERTED BY TEST}
|
||||
ignore-schema=^archives$
|
||||
ignore-table=^_
|
|
@ -1,3 +1,4 @@
|
|||
[production]
|
||||
host=127.0.0.1
|
||||
port={DYNAMICALLY INSERTED BY TEST}
|
||||
flavor={DYNAMICALLY INSERTED BY TEST}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
[production]
|
||||
host=127.0.0.1
|
||||
port={DYNAMICALLY INSERTED BY TEST}
|
||||
flavor={DYNAMICALLY INSERTED BY TEST}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
[production]
|
||||
host=127.0.0.1
|
||||
port={DYNAMICALLY INSERTED BY TEST}
|
||||
flavor={DYNAMICALLY INSERTED BY TEST}
|
||||
|
|
Загрузка…
Ссылка в новой задаче