master
go 159 lines 4.71 KB
Raw
1 package migrations
2
3 import (
4 "context"
5 "fmt"
6 "log"
7 "os"
8
9 lockfile "github.com/ipfs/go-fs-lock"
10 "github.com/ipfs/kubo/repo/fsrepo/migrations/common"
11 mg16 "github.com/ipfs/kubo/repo/fsrepo/migrations/fs-repo-16-to-17/migration"
12 mg17 "github.com/ipfs/kubo/repo/fsrepo/migrations/fs-repo-17-to-18/migration"
13 )
14
15 // embeddedMigrations contains all embedded migrations
16 // Using a slice to maintain order and allow for future range-based operations
17 var embeddedMigrations = []common.Migration{
18 mg16.Migration,
19 mg17.Migration,
20 }
21
22 // migrationsByName provides quick lookup by name
23 var migrationsByName = make(map[string]common.Migration)
24
25 func init() {
26 for _, m := range embeddedMigrations {
27 migrationsByName["fs-repo-"+m.Versions()] = m
28 }
29 }
30
31 // RunEmbeddedMigration runs an embedded migration if available
32 func RunEmbeddedMigration(ctx context.Context, migrationName string, ipfsDir string, revert bool) error {
33 migration, exists := migrationsByName[migrationName]
34 if !exists {
35 return fmt.Errorf("embedded migration %s not found", migrationName)
36 }
37
38 if revert && !migration.Reversible() {
39 return fmt.Errorf("migration %s is not reversible", migrationName)
40 }
41
42 logger := log.New(os.Stdout, "", 0)
43 logger.Printf("Running embedded migration %s...", migrationName)
44
45 opts := common.Options{
46 Path: ipfsDir,
47 Verbose: true,
48 }
49
50 var err error
51 if revert {
52 err = migration.Revert(opts)
53 } else {
54 err = migration.Apply(opts)
55 }
56
57 if err != nil {
58 return fmt.Errorf("embedded migration %s failed: %w", migrationName, err)
59 }
60
61 logger.Printf("Embedded migration %s completed successfully", migrationName)
62 return nil
63 }
64
65 // HasEmbeddedMigration checks if a migration is available as embedded
66 func HasEmbeddedMigration(migrationName string) bool {
67 _, exists := migrationsByName[migrationName]
68 return exists
69 }
70
71 // RunEmbeddedMigrations runs all needed embedded migrations from current version to target version.
72 //
73 // This function migrates an IPFS repository using embedded migrations that are built into the Kubo binary.
74 // Embedded migrations are available for repo version 17+ and provide fast, network-free migration execution.
75 //
76 // Parameters:
77 // - ctx: Context for cancellation and deadlines
78 // - targetVer: Target repository version to migrate to
79 // - ipfsDir: Path to the IPFS repository directory
80 // - allowDowngrade: Whether to allow downgrade migrations (reduces target version)
81 //
82 // Returns:
83 // - nil on successful migration
84 // - error if migration fails, repo path is invalid, or no embedded migrations are available
85 //
86 // Behavior:
87 // - Validates that ipfsDir contains a valid IPFS repository
88 // - Determines current repository version automatically
89 // - Returns immediately if already at target version
90 // - Prevents downgrades unless allowDowngrade is true
91 // - Runs all necessary migrations in sequence (e.g., 16→17→18 if going from 16 to 18)
92 // - Creates backups and uses atomic operations to prevent corruption
93 //
94 // Error conditions:
95 // - Repository path is invalid or inaccessible
96 // - Current version cannot be determined
97 // - Downgrade attempted with allowDowngrade=false
98 // - No embedded migrations available for the version range
99 // - Individual migration fails during execution
100 //
101 // Example:
102 //
103 // err := RunEmbeddedMigrations(ctx, 17, "/path/to/.ipfs", false)
104 // if err != nil {
105 // // Handle migration failure, may need to fall back to external migrations
106 // }
107 func RunEmbeddedMigrations(ctx context.Context, targetVer int, ipfsDir string, allowDowngrade bool) error {
108 ipfsDir, err := CheckIpfsDir(ipfsDir)
109 if err != nil {
110 return err
111 }
112
113 // Acquire lock once for all embedded migrations to prevent concurrent access
114 lk, err := lockfile.Lock(ipfsDir, "repo.lock")
115 if err != nil {
116 return fmt.Errorf("failed to acquire repo lock: %w", err)
117 }
118 defer lk.Close()
119
120 fromVer, err := RepoVersion(ipfsDir)
121 if err != nil {
122 return fmt.Errorf("could not get repo version: %w", err)
123 }
124
125 if fromVer == targetVer {
126 return nil
127 }
128
129 revert := fromVer > targetVer
130 if revert && !allowDowngrade {
131 return fmt.Errorf("downgrade not allowed from %d to %d", fromVer, targetVer)
132 }
133
134 logger := log.New(os.Stdout, "", 0)
135 logger.Print("Looking for embedded migrations.")
136
137 migrations, _, err := findMigrations(ctx, fromVer, targetVer)
138 if err != nil {
139 return err
140 }
141
142 embeddedCount := 0
143 for _, migrationName := range migrations {
144 if HasEmbeddedMigration(migrationName) {
145 err = RunEmbeddedMigration(ctx, migrationName, ipfsDir, revert)
146 if err != nil {
147 return err
148 }
149 embeddedCount++
150 }
151 }
152
153 if embeddedCount == 0 {
154 return fmt.Errorf("no embedded migrations found for version %d to %d", fromVer, targetVer)
155 }
156
157 logger.Printf("Success: fs-repo migrated to version %d using embedded migrations.\n", targetVer)
158 return nil
159 }