Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: migration from 14 to 15 #174

Merged
merged 5 commits into from
Aug 31, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ MIG_DIRS = $(shell ls -d fs-repo-*-to-*)
IGNORED_DIRS := $(shell cat ignored-migrations)
ACTIVE_DIRS := $(filter-out $(IGNORED_DIRS),$(MIG_DIRS))

.PHONY: all build clean cmd sharness test test_go test_12_to_13 test_13_to_14
.PHONY: all build clean cmd sharness test test_go test_13_to_14 test_14_to_15

all: build

Expand All @@ -26,7 +26,7 @@ fs-repo-migrations/fs-repo-migrations:
sharness:
make -C sharness

test: test_go sharness test_12_to_13 test_13_to_14
test: test_go sharness test_13_to_14 test_14_to_15

clean: $(subst fs-repo,clean.fs-repo,$(ACTIVE_DIRS))
@make -C sharness clean
Expand All @@ -49,3 +49,6 @@ test_12_to_13:

test_13_to_14:
@cd fs-repo-13-to-14/not-sharness && ./test.sh

test_14_to_15:
@cd fs-repo-14-to-15/not-sharness && ./test.sh
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ Here is the table showing which repo version corresponds to which Kubo version:
| 11 | 0.8.0 - 0.11.0 |
| 12 | 0.12.0 - 0.17.0 |
| 13 | 0.18.0 - 0.20.0 |
| 14 | 0.21.0 - current |
| 14 | 0.21.0 - 0.22.0 |
| 15 | 0.23.0 - current |

### How to Run Migrations

Expand Down
7 changes: 7 additions & 0 deletions fs-repo-14-to-15/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.PHONY: build clean

build:
go build -mod=vendor

clean:
go clean
59 changes: 59 additions & 0 deletions fs-repo-14-to-15/atomicfile/atomicfile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Package atomicfile provides the ability to write a file with an eventual
// rename on Close (using os.Rename). This allows for a file to always be in a
// consistent state and never represent an in-progress write.
//
// NOTE: `os.Rename` may not be atomic on your operating system.
package atomicfile

import (
"io/ioutil"
"os"
"path/filepath"
)

// File behaves like os.File, but does an atomic rename operation at Close.
type File struct {
*os.File
path string
}

// New creates a new temporary file that will replace the file at the given
// path when Closed.
func New(path string, mode os.FileMode) (*File, error) {
f, err := ioutil.TempFile(filepath.Dir(path), filepath.Base(path))
if err != nil {
return nil, err
}
if err := os.Chmod(f.Name(), mode); err != nil {
f.Close()
os.Remove(f.Name())
return nil, err
}
return &File{File: f, path: path}, nil
}

// Close the file replacing the configured file.
func (f *File) Close() error {
if err := f.File.Close(); err != nil {
os.Remove(f.File.Name())
return err
}
if err := os.Rename(f.Name(), f.path); err != nil {
return err
}
return nil
}

// Abort closes the file and removes it instead of replacing the configured
// file. This is useful if after starting to write to the file you decide you
// don't want it anymore.
func (f *File) Abort() error {
if err := f.File.Close(); err != nil {
os.Remove(f.Name())
return err
}
if err := os.Remove(f.Name()); err != nil {
return err
}
return nil
}
5 changes: 5 additions & 0 deletions fs-repo-14-to-15/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/ipfs/fs-repo-migrations/fs-repo-14-to-15

go 1.20

require github.com/ipfs/fs-repo-migrations/tools v0.0.0-20211209222258-754a2dcb82ea
2 changes: 2 additions & 0 deletions fs-repo-14-to-15/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
github.com/ipfs/fs-repo-migrations/tools v0.0.0-20211209222258-754a2dcb82ea h1:lgfk2PMrJI3bh8FflcBTXyNi3rPLqa75J7KcoUfRJmc=
github.com/ipfs/fs-repo-migrations/tools v0.0.0-20211209222258-754a2dcb82ea/go.mod h1:fADeaHKxwS+SKhc52rsL0P1MUcnyK31a9AcaG0KcfY8=
11 changes: 11 additions & 0 deletions fs-repo-14-to-15/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

import (
mg14 "github.com/ipfs/fs-repo-migrations/fs-repo-14-to-15/migration"
migrate "github.com/ipfs/fs-repo-migrations/tools/go-migrate"
)

func main() {
m := mg14.Migration{}
Jorropo marked this conversation as resolved.
Show resolved Hide resolved
migrate.Main(m)
}
248 changes: 248 additions & 0 deletions fs-repo-14-to-15/migration/migration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
// package mg14 contains the code to perform 14-15 repository migration in Kubo.
// This handles the following:
// - Replaces bootstrap QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ from /quic to /quic-v1
// - Removes /quic only addresses from .Addresses.Swarm
package mg14

import (
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"strings"

migrate "github.com/ipfs/fs-repo-migrations/tools/go-migrate"
mfsr "github.com/ipfs/fs-repo-migrations/tools/mfsr"
lock "github.com/ipfs/fs-repo-migrations/tools/repolock"
log "github.com/ipfs/fs-repo-migrations/tools/stump"

"github.com/ipfs/fs-repo-migrations/fs-repo-14-to-15/atomicfile"
)

const backupSuffix = ".14-to-15.bak"

// Migration implements the migration described above.
type Migration struct{}

// Versions returns the current version string for this migration.
func (m Migration) Versions() string {
return "14-to-15"
}

// Reversible returns true, as we keep old config around
func (m Migration) Reversible() bool {
return true
}

// Apply update the config.
func (m Migration) Apply(opts migrate.Options) error {
log.Verbose = opts.Verbose
log.Log("applying %s repo migration", m.Versions())

log.VLog("locking repo at %q", opts.Path)
lk, err := lock.Lock2(opts.Path)
if err != nil {
return err
}
defer lk.Close()

repo := mfsr.RepoPath(opts.Path)

log.VLog(" - verifying version is '14'")
if err := repo.CheckVersion("14"); err != nil {
return err
}

log.Log("> Upgrading config to new format")

path := filepath.Join(opts.Path, "config")
in, err := os.Open(path)
if err != nil {
return err
}

// make backup
backup, err := atomicfile.New(path+backupSuffix, 0600)
if err != nil {
return err
}
if _, err := backup.ReadFrom(in); err != nil {
panicOnError(backup.Abort())
return err
}
if _, err := in.Seek(0, io.SeekStart); err != nil {
panicOnError(backup.Abort())
return err
}

// Create a temp file to write the output to on success
out, err := atomicfile.New(path, 0600)
if err != nil {
panicOnError(backup.Abort())
panicOnError(in.Close())
return err
}

if err := convert(in, out); err != nil {
panicOnError(out.Abort())
panicOnError(backup.Abort())
panicOnError(in.Close())
return err
}

if err := in.Close(); err != nil {
panicOnError(out.Abort())
panicOnError(backup.Abort())
}

if err := repo.WriteVersion("15"); err != nil {
log.Error("failed to update version file to 15")
// There was an error so abort writing the output and clean up temp file
panicOnError(out.Abort())
panicOnError(backup.Abort())
return err
} else {
// Write the output and clean up temp file
panicOnError(out.Close())
panicOnError(backup.Close())
}

log.Log("updated version file")

log.Log("Migration 14 to 15 succeeded")
return nil
}

// panicOnError is reserved for checks we can't solve transactionally if an error occurs
func panicOnError(e error) {
if e != nil {
panic(fmt.Errorf("error can't be dealt with transactionally: %w", e))
}
}

func (m Migration) Revert(opts migrate.Options) error {
log.Verbose = opts.Verbose
log.Log("reverting migration")
lk, err := lock.Lock2(opts.Path)
if err != nil {
return err
}
defer lk.Close()

repo := mfsr.RepoPath(opts.Path)
if err := repo.CheckVersion("15"); err != nil {
return err
}

cfg := filepath.Join(opts.Path, "config")
if err := os.Rename(cfg+backupSuffix, cfg); err != nil {
return err
}

if err := repo.WriteVersion("14"); err != nil {
return err
}
if opts.Verbose {
log.Log("lowered version number to 14")
}

return nil
}

// convert converts the config from one version to another
func convert(in io.Reader, out io.Writer) error {
confMap := make(map[string]any)
if err := json.NewDecoder(in).Decode(&confMap); err != nil {
return err
}

// Upgrade bootstrapper QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ from /quic to /quic-v1
if b, ok := confMap["Bootstrap"]; ok {
bootstrap, ok := b.([]interface{})
if !ok {
return fmt.Errorf("invalid type for .Bootstrap got %T expected json array", b)
}

for i, v := range bootstrap {
if v == "/ip4/104.131.131.82/udp/4001/quic/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ" {
bootstrap[i] = "/ip4/104.131.131.82/udp/4001/quic-v1/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ"
}
}
}

// Remove /quic only addresses from .Addresses.Swarm
if a, ok := confMap["Addresses"]; ok {
addresses, ok := a.(map[string]any)
if !ok {
return fmt.Errorf("invalid type for .Addresses got %T expected json map", a)
}

if s, ok := addresses["Swarm"]; ok {
swarm, ok := s.([]interface{})
if !ok {
return fmt.Errorf("invalid type for .Addresses.Swarm got %T expected json array", s)
}

var newSwarm []interface{}
for _, v := range swarm {
if addr, ok := v.(string); ok {
if !strings.Contains(addr, "/quic") || strings.Contains(addr, "/quic-v1") {
newSwarm = append(newSwarm, addr)
}
} else {
newSwarm = append(newSwarm, v)
}
}
addresses["Swarm"] = newSwarm
}
}

// Remove legacy Gateway.HTTPHeaders values that were hardcoded since years ago, but no longer necessary
// (but leave as-is if user made any changes)
// https://github.com/ipfs/kubo/issues/10005
Comment on lines +206 to +208
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ Added cleanup of Gateway.HTTPHeaders. It was a long time due.
Details and rationale in ipfs/kubo#10005

if a, ok := confMap["Gateway"]; ok {
addresses, ok := a.(map[string]any)
if !ok {
return fmt.Errorf("invalid type for .Gateway got %T expected json map", a)
}

if s, ok := addresses["HTTPHeaders"]; ok {
headers, ok := s.(map[string]any)
if !ok {
return fmt.Errorf("invalid type for .Gateway.HTTPHeaders got %T expected json map", s)
}

if acaos, ok := headers["Access-Control-Allow-Origin"].([]interface{}); ok && len(acaos) == 1 && acaos[0] == "*" {
delete(headers, "Access-Control-Allow-Origin")
} else {
return fmt.Errorf("invalid type for .Gateway.HTTPHeaders[Access-Control-Allow-Origin] got %T expected json array", headers["Access-Control-Allow-Origin"])
}

if acams, ok := headers["Access-Control-Allow-Methods"].([]interface{}); ok && len(acams) == 1 && acams[0] == "GET" {
delete(headers, "Access-Control-Allow-Methods")
} else {
return fmt.Errorf("invalid type for .Gateway.HTTPHeaders[Access-Control-Allow-Methods] got %T expected json array", headers["Access-Control-Allow-Methods"])
}
if acahs, ok := headers["Access-Control-Allow-Headers"].([]interface{}); ok && len(acahs) == 3 {
if acahs[0] == "X-Requested-With" && acahs[1] == "Range" && acahs[2] == "User-Agent" {
delete(headers, "Access-Control-Allow-Headers")
}
} else {
return fmt.Errorf("invalid type for .Gateway.HTTPHeaders[Access-Control-Allow-Headers] got %T expected json array", headers["Access-Control-Allow-Headers"])
}
}
}

// Save new config
fixed, err := json.MarshalIndent(confMap, "", " ")
if err != nil {
return err
}

if _, err := out.Write(fixed); err != nil {
return err
}
_, err = out.Write([]byte("\n"))
return err
}
1 change: 1 addition & 0 deletions fs-repo-14-to-15/not-sharness/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
repotest
23 changes: 23 additions & 0 deletions fs-repo-14-to-15/not-sharness/repotest-golden/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"Addresses": {
"Swarm": [
"/ip4/0.0.0.0/tcp/4001",
"/ip6/::/tcp/4001",
"/ip4/0.0.0.0/udp/4001/quic-v1",
"/ip4/0.0.0.0/udp/4001/quic-v1/webtransport",
"/ip6/::/udp/4001/quic-v1",
"/ip6/::/udp/4001/quic-v1/webtransport"
]
},
"Bootstrap": [
"/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt",
"/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
"/ip4/104.131.131.82/udp/4001/quic-v1/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ",
"/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN",
"/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa",
"/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb"
],
"Gateway": {
"HTTPHeaders": {}
}
}
Loading
Loading