ariga / atlas-go-sdk Goto Github PK
View Code? Open in Web Editor NEWAn SDK for building atlasgo.io providers
License: Apache License 2.0
An SDK for building atlasgo.io providers
License: Apache License 2.0
Current version: v0.3.0
For example, here's my code:
// Run `atlas migrate apply` on a SQLite database under /tmp.
res, err := client.MigrateApply(context.Background(), &atlasexec.MigrateApplyParams{
Env: "prod",
})
fmt.Println("res: ", res)
repr.Println(err)
I have exported the ATLAS_TOKEN, ATLAS_PROJECT and DATABASE_URL env vars.
Irrespective of whether the actual migration operation is successful or not, res
is always nil
and err
is always populated with
atlasexec.cliError{
stdout: "...logs of what the 'atlas migrate apply' command outputs"
}
Expectation is that on successful migration (even if that means no new migration files are to be applied), the res
variable should be populated with a useful success message & the err
variable should be nil
.
If a migration fails, this module seems to silently report success.
Why?
Even if the atlas
command returns a non-zero exit code, the Apply
and similar methods always return a nil error, as long as it can parse the output of the command as JSON:
atlas-go-sdk/atlasexec/atlas.go
Lines 303 to 307 in a226c45
It seems that API users have to check the .Error
property on what one would expect to be the success output to check for errors, in addition to the "normal" error
return.
This is confusing and error-prone.
params.Format
is missing, thwarting us from providing a migration tool agnostic API.
// SchemaApplySlice runs the 'schema apply' command for multiple targets.
func (c *Client) SchemaApplySlice(ctx context.Context, params *SchemaApplyParams) ([]*SchemaApply, error) {
args := []string{"schema", "apply", "--format", "{{ json . }}"}
if params.Env != "" {
args = append(args, "--env", params.Env)
}
if params.ConfigURL != "" {
args = append(args, "--config", params.ConfigURL)
}
if params.URL != "" {
args = append(args, "--url", params.URL)
}
if params.To != "" {
args = append(args, "--to", params.To)
}
if params.DryRun {
args = append(args, "--dry-run")
} else {
args = append(args, "--auto-approve")
}
if params.TxMode != "" {
args = append(args, "--tx-mode", params.TxMode)
}
if params.DevURL != "" {
args = append(args, "--dev-url", params.DevURL)
}
if len(params.Schema) > 0 {
args = append(args, "--schema", strings.Join(params.Schema, ","))
}
if len(params.Exclude) > 0 {
args = append(args, "--exclude", strings.Join(params.Exclude, ","))
}
args = append(args, params.Vars.AsArgs()...)
return jsonDecodeErr[SchemaApply](newSchemaApplyError)(c.runCommand(ctx, args))
}
Full function:
// runCommand runs the given command and returns its output.
func (c *Client) runCommand(ctx context.Context, args []string) (io.Reader, error) {
var stdout, stderr bytes.Buffer
cmd := exec.CommandContext(ctx, c.execPath, args...)
cmd.Dir = c.workingDir
// Set ATLAS_NO_UPDATE_NOTIFIER=1 to disable the update notifier.
// use os.Environ() to avoid overriding the user's environment.
cmd.Env = append(os.Environ(), "ATLAS_NO_UPDATE_NOTIFIER=1")
cmd.Stderr = &stderr
cmd.Stdout = &stdout
if err := cmd.Run(); err != nil {
return nil, cliError{
stderr: strings.TrimSpace(stderr.String()),
stdout: strings.TrimSpace(stdout.String()),
}
}
return &stdout, nil
}
Problem:
if err := cmd.Run(); err != nil {
return nil, cliError{
stderr: strings.TrimSpace(stderr.String()),
stdout: strings.TrimSpace(stdout.String()),
}
}
This error handler throws away the err
value from cmd.Run()
, assuming that the error comes from atlas, not the execution itself.
I ended up debugging just to find out that I forgot to install atlas. This should be fixed by deferring to the exec error when it is populated, or maybe including both.
the struct atlasexec.ApplyParams doesn't have field for the flag --allow-dirty
, which is present in the cli tool
'atlas migrate apply' reads the migration state of the connected database and computes what migrations are pending.
It then attempts to apply the pending migration files in the correct order onto the database.
The first argument denotes the maximum number of migration files to apply.
As a safety measure 'atlas migrate apply' will abort with an error, if:
- the migration directory is not in sync with the 'atlas.sum' file
- the migration and database history do not match each other
If run with the "--dry-run" flag, atlas will not execute any SQL.
Usage:
atlas migrate apply [flags] [amount]
Examples:
atlas migrate apply -u "mysql://user:pass@localhost:3306/dbname"
atlas migrate apply --dir "file:///path/to/migration/directory" --url "mysql://user:pass@localhost:3306/dbname" 1
atlas migrate apply --env dev 1
atlas migrate apply --dry-run --env dev 1
Flags:
-u, --url string [driver://username:password@address/dbname?param=value] select a resource using the URL format
--dir string select migration directory using URL format (default "file://migrations")
--format string Go template to use to format the output
--revisions-schema string name of the schema the revisions table resides in
--dry-run print SQL without executing it
--lock-timeout duration set how long to wait for the database lock (default 10s)
--baseline string start the first migration after the given baseline version
--tx-mode string set transaction mode [none, file, all] (default "file")
--allow-dirty allow start working on a non-clean database
-h, --help help for apply
Global Flags:
-c, --config string select config (project) file using URL format (default "file://atlas.hcl")
--env string set which env from the config file to use
--var <name>=<value> input variables (default [])
Given
package main
import (
"context"
"embed"
"log"
"ariga.io/atlas-go-sdk/atlasexec"
)
//go:embed migrations
var migrations embed.FS
func main() {
workdir, err := atlasexec.NewWorkingDir()
if err != nil {
log.Fatalln(err)
}
defer workdir.Close()
if err := workdir.CopyFS("", migrations); err != nil {
log.Fatalln(err)
}
client, err := atlasexec.NewClient(workdir.Path(), "atlas")
if err != nil {
log.Fatalln(err)
}
_, err = client.MigrateApply(context.TODO(), &atlasexec.MigrateApplyParams{URL: "postgres://user:e%7D#?0@postgres:6666/main?sslmode=disable"})
if err != nil {
log.Fatalln(err)
}
}
When
go run script.go
Then
2024/07/22 15:22:43 Error: sql/sqlclient: parse open url: invalid port ":e%7D" after host
Expected: no errors, and parsing of URI with special chars on password is successful
Hello,
The snippet at https://atlasgo.io/integrations/go-sdk to run migrations suggests we should use atlasexec.NewWorkingDir
, however while that's available in master, that's not available in the latest published version (0.1.0).
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.