Skip to content

Commit

Permalink
feat: cli
Browse files Browse the repository at this point in the history
  • Loading branch information
alanshaw committed Jan 27, 2025
1 parent 0531194 commit 7fa6d07
Show file tree
Hide file tree
Showing 13 changed files with 611 additions and 27 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
build/bin
node_modules
frontend/dist
fam
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fam:
go build -o ./fam ./cmd

.PHONY: clean-fam

clean-fam:
rm -f ./fam
4 changes: 3 additions & 1 deletion bucket/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ type ClockBucket[T any] interface {
// Networker allows for syncing state with remote servers.
type Networker interface {
// Remotes retrieves the list of configured remotes.
Remotes(ctx context.Context) (Bucket[Remote], error)
Remotes(ctx context.Context) (Bucket[peer.AddrInfo], error)
// Remote returns a named instance of a remote.
Remote(ctx context.Context, name string) (Remote, error)
}

type Remote interface {
Expand Down
19 changes: 16 additions & 3 deletions bucket/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,31 @@ import (
"iter"

"github.com/ipld/go-ipld-prime"
"github.com/libp2p/go-libp2p/core/peer"
"github.com/storacha/fam/block"
)

type NetworkClockBucket[T any] struct {
bucket ClockBucket[T]
remotes Bucket[Remote]
remotes Bucket[peer.AddrInfo]
}

func (cb *NetworkClockBucket[T]) Remotes(ctx context.Context) (Bucket[Remote], error) {
func (cb *NetworkClockBucket[T]) Remotes(ctx context.Context) (Bucket[peer.AddrInfo], error) {
return cb.remotes, nil
}

func (cb *NetworkClockBucket[T]) Remote(ctx context.Context, name string) (Remote, error) {
rems, err := cb.Remotes(ctx)
if err != nil {
return nil, err
}
info, err := rems.Get(ctx, name)
if err != nil {
return nil, err
}
return &ClockRemote{cb.bucket, info}, nil
}

func (cb *NetworkClockBucket[T]) Head(ctx context.Context) ([]ipld.Link, error) {
return cb.bucket.Head(ctx)
}
Expand Down Expand Up @@ -46,6 +59,6 @@ func (cb *NetworkClockBucket[T]) Entries(ctx context.Context, opts ...EntriesOpt
}

// NewNetworkClockBucket creates a new [ClockBucket[T]] that is also a [Networker].
func NewNetworkClockBucket[T any](bucket ClockBucket[T], remotes Bucket[Remote]) (*NetworkClockBucket[T], error) {
func NewNetworkClockBucket[T any](bucket ClockBucket[T], remotes Bucket[peer.AddrInfo]) (*NetworkClockBucket[T], error) {
return &NetworkClockBucket[T]{bucket, remotes}, nil
}
32 changes: 16 additions & 16 deletions bucket/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,14 @@ func (r *ClockRemote) Pull(ctx context.Context) error {
return errors.New("not implemented")
}

// NewRemoteBucket creates a new bucket that stores remotes.
func NewRemoteBucket(clock Clock, bucket Bucket[ipld.Link]) Bucket[Remote] {
// NewRemoteBucket creates a new bucket that stores remote address info.
func NewRemoteBucket(clock Clock, bucket Bucket[ipld.Link]) Bucket[peer.AddrInfo] {
idbb := NewIdentityBytesBucket(bucket)
ipbb := NewCborBucket(idbb)
return NewIpldNodeBucket(ipbb, func(n ipld.Node) (Remote, error) {
addr, err := bindAddrInfo(n)
if err != nil {
return nil, err
}
return &ClockRemote{clock, addr}, nil
}, func(r Remote) (ipld.Node, error) {
addr, err := r.Address(context.Background())
if err != nil {
return nil, err
}
return unbindAddrInfo(addr)
return NewIpldNodeBucket(ipbb, func(n ipld.Node) (peer.AddrInfo, error) {
return bindAddrInfo(n)
}, func(info peer.AddrInfo) (ipld.Node, error) {
return unbindAddrInfo(info)
})
}

Expand Down Expand Up @@ -111,8 +103,8 @@ func unbindAddrInfo(addr peer.AddrInfo) (ipld.Node, error) {
if err != nil {
return nil, fmt.Errorf("assembling peer ID value: %w", err)
}

la, err := nb.BeginList(int64(len(addr.Addrs)))
nb2 := np.NewBuilder()
la, err := nb2.BeginList(int64(len(addr.Addrs)))
if err != nil {
return nil, fmt.Errorf("beginning value list: %w", err)
}
Expand All @@ -126,6 +118,14 @@ func unbindAddrInfo(addr peer.AddrInfo) (ipld.Node, error) {
if err != nil {
return nil, fmt.Errorf("finishing addresses list: %w", err)
}
err = ma.AssembleKey().AssignString("addrs")
if err != nil {
return nil, fmt.Errorf("assembling addrs key: %w", err)
}
err = ma.AssembleValue().AssignNode(nb2.Build())
if err != nil {
return nil, fmt.Errorf("assembling addrs value: %w", err)
}
err = ma.Finish()
if err != nil {
return nil, fmt.Errorf("finishing map: %w", err)
Expand Down
193 changes: 193 additions & 0 deletions cmd/bucket/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
package bucket

import (
"context"
"errors"
"fmt"

logging "github.com/ipfs/go-log/v2"
"github.com/storacha/fam/bucket"
"github.com/storacha/fam/cmd/bucket/remote"
"github.com/storacha/fam/cmd/util"
"github.com/storacha/go-ucanto/core/delegation"
"github.com/storacha/go-ucanto/did"
"github.com/storacha/go-ucanto/principal/ed25519/signer"
"github.com/storacha/go-ucanto/ucan"
"github.com/urfave/cli/v2"
)

var log = logging.Logger("bucket")

var Command = &cli.Command{
Name: "bucket",
Usage: "Print buckets",
Action: func(cCtx *cli.Context) error {
datadir := util.EnsureDataDir(cCtx.String("datadir"))
userdata := util.UserDataStore(context.Background(), datadir)
buckets, err := userdata.Buckets(context.Background())
if err != nil {
log.Fatal(err)
}
curr := util.GetCurrent(datadir)
count := 0
for id := range buckets {
if id == curr {
fmt.Printf("* %s\n", id)
} else {
fmt.Printf(" %s\n", id)
}
count++
}
fmt.Printf("%d total\n", count)
return nil
},
Subcommands: []*cli.Command{
{
Name: "import",
Usage: "Import a bucket",
Args: true,
ArgsUsage: "<grant>",
Action: func(cCtx *cli.Context) error {
datadir := util.EnsureDataDir(cCtx.String("datadir"))
userdata := util.UserDataStore(context.Background(), datadir)
arg := cCtx.Args().Get(0)
// TODO: remove
if arg == "" {
issuer, _ := signer.Generate()
audience, _ := userdata.ID(context.Background())
d, _ := delegation.Delegate(
issuer,
audience,
[]ucan.Capability[ucan.NoCaveats]{
ucan.NewCapability("space/blob/*", issuer.DID().String(), ucan.NoCaveats{}),
ucan.NewCapability("clock/*", issuer.DID().String(), ucan.NoCaveats{}),
},
)
arg, _ = delegation.Format(d)
}
proof, err := delegation.Parse(arg)
if err != nil {
log.Fatal(err)
}
id, err := userdata.AddBucket(context.Background(), proof)
if err != nil {
log.Fatal(err)
}
fmt.Println(id)
curr := util.GetCurrent(datadir)
if curr == did.Undef {
util.SetCurrent(datadir, id)
}
return nil
},
},
{
Name: "pull",
Usage: "Pull changes from a remote",
Args: true,
ArgsUsage: "<remote>",
Action: func(cCtx *cli.Context) error {
datadir := util.EnsureDataDir(cCtx.String("datadir"))
userdata := util.UserDataStore(context.Background(), datadir)
curr := util.GetCurrent(datadir)
if curr == did.Undef {
return fmt.Errorf("no bucket selected, use `fam bucket use <did>`")
}
bk, err := userdata.Bucket(context.Background(), curr)
if err != nil {
log.Fatal(err)
}
if nbk, ok := bk.(bucket.Networker); ok {
name := cCtx.Args().Get(0)
remote, err := nbk.Remote(context.Background(), name)
if err != nil {
if errors.Is(err, bucket.ErrNotFound) {
return fmt.Errorf("remote not found: %s", name)
}
log.Fatal(err)
}
err = remote.Pull(context.Background())
if err != nil {
log.Fatal(err)
}
root, err := bk.Root(context.Background())
if err != nil {
log.Fatal(err)
}
fmt.Println(root.String())
} else {
return fmt.Errorf("bucket is not a networker")
}
return nil
},
},
{
Name: "push",
Usage: "Push local changes from a remote",
Args: true,
ArgsUsage: "<remote>",
Action: func(cCtx *cli.Context) error {
datadir := util.EnsureDataDir(cCtx.String("datadir"))
userdata := util.UserDataStore(context.Background(), datadir)
curr := util.GetCurrent(datadir)
if curr == did.Undef {
return fmt.Errorf("no bucket selected, use `fam bucket use <did>`")
}
bk, err := userdata.Bucket(context.Background(), curr)
if err != nil {
log.Fatal(err)
}
if nbk, ok := bk.(bucket.Networker); ok {
name := cCtx.Args().Get(0)
remote, err := nbk.Remote(context.Background(), name)
if err != nil {
if errors.Is(err, bucket.ErrNotFound) {
return fmt.Errorf("remote not found: %s", name)
}
log.Fatal(err)
}
err = remote.Push(context.Background())
if err != nil {
log.Fatal(err)
}
root, err := bk.Root(context.Background())
if err != nil {
log.Fatal(err)
}
fmt.Println(root.String())
} else {
return fmt.Errorf("bucket is not a networker")
}
return nil
},
},
remote.Command,
{
Name: "use",
Usage: "Use a bucket",
Args: true,
ArgsUsage: "<id>",
Action: func(cCtx *cli.Context) error {
datadir := util.EnsureDataDir(cCtx.String("datadir"))
userdata := util.UserDataStore(context.Background(), datadir)
buckets, err := userdata.Buckets(context.Background())
if err != nil {
log.Fatal(err)
}
if len(buckets) == 0 {
return fmt.Errorf("no buckets, use `fam bucket import`")
}
id, err := did.Parse(cCtx.Args().Get(0))
if err != nil {
return fmt.Errorf("parsing bucket DID: \"%s\"", cCtx.Args().Get(0))
}
if _, ok := buckets[id]; !ok {
return fmt.Errorf("bucket not found: %s", id)
}
util.SetCurrent(datadir, id)
fmt.Printf("* %s\n", id)
return nil
},
},
},
}
Loading

0 comments on commit 7fa6d07

Please sign in to comment.