Config-Migrate
config-migrate is a plugin for golang-migrate that enables versioned migrations for configuration files like YAML and JSON.
Configs
Config drivers apply migrations to configuration files instead of traditional databases.
Want to add a new config driver?
Currently supported config drivers:
Why use config-migrate?
This is useful when:
- You want to apply versioned updates to your application configs.
- You need a reproducible and auditable history of config changes.
- You want to manage infrastructure or system configs as part of CI/CD.
Features
- Seamless integration with
golang-migrate
- File-based locking to prevent concurrent writes
- Config merging with support for version tracking
- Supports
version, force, and drop commands
- Graceful file handling using
io.Reader / io.Writer
- Thread-safe with
sync.Mutex
- Built-in support for YAML and JSON config formats
Getting Started
Install
Make sure you have Go 1.18+ and Go modules enabled.
go get github.com/c2pc/config-migrate
Use in your Go project
- API is stable and frozen for this release (v3 & v4).
- Uses Go modules to manage dependencies.
- Supports graceful stop via
GracefulStop chan bool.
- Bring your own logger.
- Uses
io.Reader streams internally for low memory overhead.
- Thread-safe and no goroutine leaks.
📚 Go Documentation
Basic usage with config file:
import (
"github.com/golang-migrate/migrate/v4"
_ "github.com/c2pc/config-migrate/driver/json"
_ "github.com/golang-migrate/migrate/v4/source/github"
)
func main() {
m, err := migrate.New(
"github://username:token@your-repo/json-migrations",
"json://./config.json")
if err != nil {
panic(err)
}
// Apply the next 2 migration steps
m.Steps(2)
}
Advanced usage with an existing config client:
import (
"github.com/golang-migrate/migrate/v4"
"github.com/c2pc/config-migrate/driver/yaml"
"github.com/c2pc/config-migrate/config"
_ "github.com/golang-migrate/migrate/v4/source/file"
)
func main() {
driver := yaml.New(driver.Settings{
Path: "config.yml",
Perm: 0777,
})
m, err := migrate.NewWithDatabaseInstance(
"file://driver/migrations",
"yaml", driver)
if err != nil {
panic(err)
}
m.Up()
}
Dynamic Replacers
You can use dynamic placeholders in your config files and define how they should be replaced at runtime using replacer.
Built-in Example: Random
The following example registers a replacer that replaces ___random___ with random string:
package random
import (
"crypto/rand"
"github.com/c2pc/config-migrate/replacer"
)
const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
func init() {
replacer.Register("___random___", randomReplacer(16))
}
func randomReplacer(n int) func() string {
return func() string {
bytes := make([]byte, n)
_, err := rand.Read(bytes)
if err != nil {
return ""
}
for i, b := range bytes {
bytes[i] = letters[b%byte(len(letters))]
}
return string(bytes)
}
}
Writing Your Own Replacers
You can create and register your own replacers by calling replacer.Register:
package main
import (
"github.com/c2pc/config-migrate/replacer"
)
func init() {
replacer.Register("___my_placeholder___", func() string {
return "dynamic-value"
})
}
These placeholders will be replaced automatically when configs are processed during migrations.
When to Use Replacers
Use replacers when you want to inject dynamic values (like IPs, ports, timestamps, environment info) into your config files at the time of applying a migration.
you can enable comment replacer in settings UnableToReplaceComments: true
package main
import (
"embed"
"errors"
"github.com/c2pc/config-migrate/driver"
"github.com/c2pc/config-migrate/driver/yaml"
"github.com/golang-migrate/migrate/v4"
"github.com/golang-migrate/migrate/v4/source/iofs"
)
//go:embed migrations/*.yml
var fs embed.FS
func runMigration(path string) error {
//You can set a comment suffix
driver.CommentSuffix = "___comment___"
//Enable to replace comments
yamlMigr := yaml.New(driver.Settings{
Path: path,
Perm: 0777,
UnableToReplaceComments: true,
})
//Using iofs as a source
d, err := iofs.New(fs, "migrations")
if err != nil {
return err
}
//Create yaml migration
m, err := migrate.NewWithInstance("iofs", d, "yaml", yamlMigr)
if err != nil {
return err
}
//Run migrations
if err := m.Up(); errors.Is(err, migrate.ErrNoChange) {
return nil
} else if err != nil {
return err
}
return nil
}
YAML
You can add comments to your parameters. Add suffix ______ to parameter name
Set empty comment host______: to add \n to file
http______:
http_______: HTTP server configuration1
http________: HTTP server configuration2
http:
host______:
host_______: The IP address the server will bind to
host: ___ip_address___
port______:
port_______: The port the server will listen on
port: 8052
As a result we get
# HTTP server configuration1
# HTTP server configuration2
http:
# The IP address the server will bind to
host: ___ip_address___
# The port the server will listen on
port: 8052
JSON
You can add comments to your parameters. Add suffix ______ to parameter name.
Set empty comment "http______": "" to add \n to file
{
"http______": "",
"http_______": "HTTP server configuration",
"http________": "HTTP server configuration1",
"http_________": "HTTP server configuration2",
"http": {
"host______": "",
"host_______": "The IP address the server will bind to",
"host": "___ip_address___",
"port______": "",
"port_______": "The port the server will listen on",
"port": 8052
}
}
As a result we get
{
"____http": "HTTP server configuration",
"____http_": "HTTP server configuration1",
"____http__": "HTTP server configuration2",
"http": {
"____host": "The IP address the server will bind to",
"host": "___ip_address___",
"____port": "The port the server will listen on",
"port": 8052
}
}
Examples
- JSON - JSON migrations with replacers and comments
- YAML - YAML migrations with replacers and comments
- EXAMPLE - Example application with YAML migrations