generated from TBD54566975/tbd-project-template
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e4b721f
commit ec9f01d
Showing
12 changed files
with
550 additions
and
126 deletions.
There are no files selected for viewing
311 changes: 218 additions & 93 deletions
311
backend/protos/xyz/block/ftl/v1beta1/provisioner/resource.pb.go
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package provisioner | ||
|
||
import ( | ||
"archive/tar" | ||
"context" | ||
"database/sql" | ||
"fmt" | ||
"io" | ||
"net/url" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/amacneil/dbmate/v2/pkg/dbmate" | ||
_ "github.com/go-sql-driver/mysql" | ||
|
||
"github.com/TBD54566975/ftl/backend/controller/artefacts" | ||
"github.com/TBD54566975/ftl/backend/protos/xyz/block/ftl/v1beta1/provisioner" | ||
"github.com/TBD54566975/ftl/internal/log" | ||
"github.com/TBD54566975/ftl/internal/sha256" | ||
) | ||
|
||
// NewMigrationProvisioner creates a new provisioner that provisions database migrations | ||
func NewMigrationProvisioner(registryConfig artefacts.RegistryConfig) *InMemProvisioner { | ||
return NewEmbeddedProvisioner(map[ResourceType]InMemResourceProvisionerFn{ | ||
ResourceTypeMigration: provisionMigration(registryConfig), | ||
}) | ||
} | ||
|
||
func provisionMigration(registryConfig artefacts.RegistryConfig) func(ctx context.Context, rc *provisioner.ResourceContext, module, id string) (*provisioner.Resource, error) { | ||
return func(ctx context.Context, rc *provisioner.ResourceContext, module, id string) (*provisioner.Resource, error) { | ||
migration, ok := rc.Resource.Resource.(*provisioner.Resource_Migration) | ||
if !ok { | ||
return nil, fmt.Errorf("unexpected resource type: %T", rc.Resource.Resource) | ||
} | ||
if len(rc.Dependencies) != 1 { | ||
return nil, fmt.Errorf("migrations must have exaclyt one dependency, found %v", rc.Dependencies) | ||
} | ||
registry, err := artefacts.NewOCIRegistryStorage(registryConfig) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to create OCI registry storage: %w", err) | ||
} | ||
parseSHA256, err := sha256.ParseSHA256(rc.Resource.GetMigration().Digest) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse digest %w", err) | ||
} | ||
download, err := registry.Download(ctx, parseSHA256) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to download migration: %w", err) | ||
} | ||
dir, err := extractTarToTempDir(download) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to extract tar: %w", err) | ||
} | ||
dsn := "" | ||
driver := "" | ||
|
||
resource := rc.Dependencies[0].Resource | ||
switch res := resource.(type) { | ||
case *provisioner.Resource_Postgres: | ||
dsn = res.Postgres.GetOutput().GetWriteDsn() | ||
driver = "pgx" | ||
case *provisioner.Resource_Mysql: | ||
dsn = res.Mysql.GetOutput().GetWriteDsn() | ||
driver = "mysql" | ||
} | ||
u, err := url.Parse(dsn) | ||
if err != nil { | ||
return nil, fmt.Errorf("invalid DSN: %w", err) | ||
} | ||
|
||
conn, err := sql.Open(driver, dsn) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to connect to database: %w", err) | ||
} | ||
defer conn.Close() | ||
|
||
db := dbmate.New(u) | ||
db.AutoDumpSchema = false | ||
db.Log = log.FromContext(ctx).Scope("migrate").WriterAt(log.Info) | ||
db.MigrationsDir = []string{dir} | ||
err = db.CreateAndMigrate() | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to create and migrate database: %w", err) | ||
} | ||
migration.Migration = &provisioner.MigrationResource{ | ||
Output: &provisioner.MigrationResource_MigrationResourceOutput{}, | ||
} | ||
return rc.Resource, nil | ||
} | ||
} | ||
|
||
func extractTarToTempDir(tarReader io.Reader) (tempDir string, err error) { | ||
// Create a new tar reader | ||
tr := tar.NewReader(tarReader) | ||
|
||
// Create a temporary directory | ||
tempDir, err = os.MkdirTemp("", "extracted") | ||
if err != nil { | ||
return "", fmt.Errorf("failed to create temporary directory: %w", err) | ||
} | ||
|
||
// Extract files from the tar archive | ||
for { | ||
header, err := tr.Next() | ||
if err == io.EOF { | ||
break // End of tar archive | ||
} | ||
if err != nil { | ||
return "", fmt.Errorf("failed to read tar header: %w", err) | ||
} | ||
|
||
// Construct the full path for the file | ||
targetPath := filepath.Join(tempDir, header.Name) | ||
|
||
// Create the file | ||
file, err := os.OpenFile(targetPath, os.O_CREATE|os.O_WRONLY, os.FileMode(header.Mode)) | ||
if err != nil { | ||
return "", fmt.Errorf("failed to create file: %w", err) | ||
} | ||
defer file.Close() | ||
|
||
// Copy the file content | ||
if _, err := io.Copy(file, tr); err != nil { | ||
return "", fmt.Errorf("failed to copy file content: %w", err) | ||
} | ||
} | ||
return tempDir, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.