make publish more configurable and add test for repub
License: MIT Signed-off-by: Jeromy <jeromyj@gmail.com>
Jeromy committed
Sep 30, 2015 at 11:03 UTC
e5512b411532b93696ced59dff88b1f0d87ea331
9 files changed
+200
-31
cmd/ipfs/main.go
+6
-3
@@ -173,10 +173,13 @@ func (i *cmdInvocation) Run(ctx context.Context) (output io.Reader, err error) {
173
if err != nil {
174
return nil, err
175
}
176
- if debug || u.GetenvBool("DEBUG") || os.Getenv("IPFS_LOGGING") == "debug" {
176
+ if debug || os.Getenv("IPFS_LOGGING") == "debug" {
177
u.Debug = true
178
logging.SetDebugLogging()
179
}
180
+ if u.GetenvBool("DEBUG") {
181
+ u.Debug = true
182
+ }
183
184
res, err := callCommand(ctx, i.req, Root, i.cmd)
185
if err != nil {
@@ -668,6 +671,6 @@ func apiClientForAddr(addr ma.Multiaddr) (cmdsHttp.Client, error) {
671
}
672
673
func isConnRefused(err error) bool {
671
- return strings.Contains(err.Error(), "connection refused") ||
672
- strings.Contains(err.Error(), "target machine actively refused it")
674
+ return strings.Contains(err.Error(), "connection refused") ||
675
+ strings.Contains(err.Error(), "target machine actively refused it")
676
}
core/commands/publish.go
+38
-23
@@ -5,6 +5,7 @@ import (
5
"fmt"
6
"io"
7
"strings"
8
+ "time"
9
10
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
11
@@ -46,8 +47,11 @@ Publish an <ipfs-path> to another public key (not implemented):
47
},
48
49
Arguments: []cmds.Argument{
49
- cmds.StringArg("name", false, false, "The IPNS name to publish to. Defaults to your node's peerID"),
50
- cmds.StringArg("ipfs-path", true, false, "IPFS path of the obejct to be published at <name>").EnableStdin(),
50
+ cmds.StringArg("ipfs-path", true, false, "IPFS path of the obejct to be published").EnableStdin(),
51
+ },
52
+ Options: []cmds.Option{
53
+ cmds.BoolOption("resolve", "resolve given path before publishing (default=true)"),
54
+ cmds.StringOption("lifetime", "t", "time duration that the record will be valid for (default: 24hrs)"),
55
},
56
Run: func(req cmds.Request, res cmds.Response) {
57
log.Debug("Begin Publish")
@@ -65,32 +69,34 @@ Publish an <ipfs-path> to another public key (not implemented):
69
}
70
}
71
68
- args := req.Arguments()
72
+ pstr := req.Arguments()[0]
73
74
if n.Identity == "" {
75
res.SetError(errors.New("Identity not loaded!"), cmds.ErrNormal)
76
return
77
}
78
75
- var name string
76
- var pstr string
79
+ popts := &publishOpts{
80
+ verifyExists: true,
81
+ pubValidTime: time.Hour * 24,
82
+ }
83
78
- switch len(args) {
79
- case 2:
80
- name = args[0]
81
- pstr = args[1]
82
- if name != n.Identity.Pretty() {
83
- res.SetError(errors.New("keychains not yet implemented"), cmds.ErrNormal)
84
+ verif, found, _ := req.Option("resolve").Bool()
85
+ if found {
86
+ popts.verifyExists = verif
87
+ }
88
+ validtime, found, _ := req.Option("lifetime").String()
89
+ if found {
90
+ d, err := time.ParseDuration(validtime)
91
+ if err != nil {
92
+ res.SetError(fmt.Errorf("error parsing lifetime option: %s", err), cmds.ErrNormal)
93
return
94
}
86
- case 1:
87
- // name = n.Identity.Pretty()
88
- pstr = args[0]
95
+
96
+ popts.pubValidTime = d
97
}
98
91
- // TODO n.Keychain.Get(name).PrivKey
92
- // TODO(cryptix): is req.Context().Context a child of n.Context()?
93
- output, err := publish(req.Context(), n, n.PrivateKey, path.Path(pstr))
99
+ output, err := publish(req.Context(), n, n.PrivateKey, path.Path(pstr), popts)
100
if err != nil {
101
res.SetError(err, cmds.ErrNormal)
102
return
@@ -107,14 +113,23 @@ Publish an <ipfs-path> to another public key (not implemented):
113
Type: IpnsEntry{},
114
}
115
110
-func publish(ctx context.Context, n *core.IpfsNode, k crypto.PrivKey, ref path.Path) (*IpnsEntry, error) {
111
- // First, verify the path exists
112
- _, err := core.Resolve(ctx, n, ref)
113
- if err != nil {
114
- return nil, err
116
+type publishOpts struct {
117
+ verifyExists bool
118
+ pubValidTime time.Duration
119
+}
120
+
121
+func publish(ctx context.Context, n *core.IpfsNode, k crypto.PrivKey, ref path.Path, opts *publishOpts) (*IpnsEntry, error) {
122
+
123
+ if opts.verifyExists {
124
+ // verify the path exists
125
+ _, err := core.Resolve(ctx, n, ref)
126
+ if err != nil {
127
+ return nil, err
128
+ }
129
}
130
117
- err = n.Namesys.Publish(ctx, k, ref)
131
+ eol := time.Now().Add(opts.pubValidTime)
132
+ err := n.Namesys.PublishWithEOL(ctx, k, ref, eol)
133
if err != nil {
134
return nil, err
135
}
core/core.go
+23
-3
@@ -56,6 +56,7 @@ import (
56
pin "github.com/ipfs/go-ipfs/pin"
57
repo "github.com/ipfs/go-ipfs/repo"
58
config "github.com/ipfs/go-ipfs/repo/config"
59
+ u "github.com/ipfs/go-ipfs/util"
60
)
61
62
const IpnsValidatorTag = "ipns"
@@ -229,26 +230,45 @@ func (n *IpfsNode) startOnlineServicesWithHost(ctx context.Context, host p2phost
230
n.Namesys = namesys.NewNameSystem(n.Routing)
231
232
// setup ipns republishing
232
- n.IpnsRepub = ipnsrp.NewRepublisher(n.Routing, n.Repo.Datastore(), n.Peerstore)
233
- n.IpnsRepub.AddName(n.Identity)
233
+ err = n.setupIpnsRepublisher()
234
+ if err != nil {
235
+ return err
236
+ }
237
238
+ return nil
239
+}
240
+
241
+func (n *IpfsNode) setupIpnsRepublisher() error {
242
cfg, err := n.Repo.Config()
243
if err != nil {
244
return err
245
}
246
+
247
+ n.IpnsRepub = ipnsrp.NewRepublisher(n.Routing, n.Repo.Datastore(), n.Peerstore)
248
+ n.IpnsRepub.AddName(n.Identity)
249
+
250
if cfg.Ipns.RepublishPeriod != "" {
251
d, err := time.ParseDuration(cfg.Ipns.RepublishPeriod)
252
if err != nil {
253
return fmt.Errorf("failure to parse config setting IPNS.RepublishPeriod: %s", err)
254
}
255
245
- if d < time.Minute || d > (time.Hour*24) {
256
+ if !u.Debug && (d < time.Minute || d > (time.Hour*24)) {
257
return fmt.Errorf("config setting IPNS.RepublishPeriod is not between 1min and 1day: %s", d)
258
}
259
260
n.IpnsRepub.Interval = d
261
}
262
263
+ if cfg.Ipns.RecordLifetime != "" {
264
+ d, err := time.ParseDuration(cfg.Ipns.RepublishPeriod)
265
+ if err != nil {
266
+ return fmt.Errorf("failure to parse config setting IPNS.RecordLifetime: %s", err)
267
+ }
268
+
269
+ n.IpnsRepub.RecordLifetime = d
270
+ }
271
+
272
n.Process().Go(n.IpnsRepub.Run)
273
274
return nil
core/corehttp/gateway_test.go
+5
@@ -7,6 +7,7 @@ import (
7
"net/http/httptest"
8
"strings"
9
"testing"
10
+ "time"
11
12
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
13
core "github.com/ipfs/go-ipfs/core"
@@ -37,6 +38,10 @@ func (m mockNamesys) Publish(ctx context.Context, name ci.PrivKey, value path.Pa
38
return errors.New("not implemented for mockNamesys")
39
}
40
41
+func (m mockNamesys) PublishWithEOL(ctx context.Context, name ci.PrivKey, value path.Path, _ time.Time) error {
42
+ return errors.New("not implemented for mockNamesys")
43
+}
44
+
45
func newNodeWithMockNamesys(ns mockNamesys) (*core.IpfsNode, error) {
46
c := config.Config{
47
Identity: config.Identity{
namesys/interface.go
+5
@@ -31,6 +31,7 @@ package namesys
31
32
import (
33
"errors"
34
+ "time"
35
36
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
37
ci "github.com/ipfs/go-ipfs/p2p/crypto"
@@ -105,4 +106,8 @@ type Publisher interface {
106
// Publish establishes a name-value mapping.
107
// TODO make this not PrivKey specific.
108
Publish(ctx context.Context, name ci.PrivKey, value path.Path) error
109
+
110
+ // TODO: to be replaced by a more generic 'PublishWithValidity' type
111
+ // call once the records spec is implemented
112
+ PublishWithEOL(ctx context.Context, name ci.PrivKey, value path.Path, eol time.Time) error
113
}
namesys/namesys.go
+5
@@ -2,6 +2,7 @@ package namesys
2
3
import (
4
"strings"
5
+ "time"
6
7
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
8
ci "github.com/ipfs/go-ipfs/p2p/crypto"
@@ -81,3 +82,7 @@ func (ns *mpns) resolveOnce(ctx context.Context, name string) (path.Path, error)
82
func (ns *mpns) Publish(ctx context.Context, name ci.PrivKey, value path.Path) error {
83
return ns.publishers["/ipns/"].Publish(ctx, name, value)
84
}
85
+
86
+func (ns *mpns) PublishWithEOL(ctx context.Context, name ci.PrivKey, val path.Path, eol time.Time) error {
87
+ return ns.publishers["/ipns/"].PublishWithEOL(ctx, name, val, eol)
88
+}
repo/config/ipns.go
+1
@@ -2,4 +2,5 @@ package config
2
3
type Ipns struct {
4
RepublishPeriod string
5
+ RecordLifetime string
6
}
test/sharness/t0100-name.sh
+3
-2
@@ -56,13 +56,14 @@ test_expect_success "resolve output looks good" '
56
57
# publish with an explicit node ID
58
59
-test_expect_success "'ipfs name publish <local-id> <hash>' succeeds" '
59
+test_expect_failure "'ipfs name publish <local-id> <hash>' succeeds" '
60
PEERID=`ipfs id --format="<id>"` &&
61
test_check_peerid "${PEERID}" &&
62
+ echo ipfs name publish "${PEERID}" "/ipfs/$HASH_WELCOME_DOCS" &&
63
ipfs name publish "${PEERID}" "/ipfs/$HASH_WELCOME_DOCS" >actual_node_id_publish
64
'
65
65
-test_expect_success "publish with our explicit node ID looks good" '
66
+test_expect_failure "publish with our explicit node ID looks good" '
67
echo "Published to ${PEERID}: /ipfs/$HASH_WELCOME_DOCS" >expected_node_id_publish &&
68
test_cmp expected_node_id_publish actual_node_id_publish
69
'
test/sharness/t0240-republisher.sh
new
+114
@@ -0,0 +1,114 @@
1
+#!/bin/sh
2
+#
3
+# Copyright (c) 2014 Jeromy Johnson
4
+# MIT Licensed; see the LICENSE file in this repository.
5
+#
6
+
7
+test_description="Test ipfs repo operations"
8
+
9
+. lib/test-lib.sh
10
+
11
+export IPTB_ROOT="`pwd`/.iptb"
12
+export DEBUG=true
13
+
14
+ipfsi() {
15
+ local dir=$1; shift; IPFS_PATH="$IPTB_ROOT/$dir" ipfs $@
16
+}
17
+
18
+setup_iptb() {
19
+ test_expect_success "iptb init" '
20
+ iptb init -n4 --bootstrap none --port 0
21
+ '
22
+
23
+ test_expect_success "set configs up" '
24
+ for i in `seq 0 3`
25
+ do
26
+ ipfsi $i config Ipns.RepublishPeriod 20s
27
+ done
28
+ '
29
+
30
+ test_expect_success "start up nodes" '
31
+ iptb start
32
+ '
33
+
34
+ test_expect_success "connect nodes" '
35
+ iptb connect 0 1 &&
36
+ iptb connect 0 2 &&
37
+ iptb connect 0 3
38
+ '
39
+
40
+ test_expect_success "nodes have connections" '
41
+ ipfsi 0 swarm peers | grep ipfs &&
42
+ ipfsi 1 swarm peers | grep ipfs &&
43
+ ipfsi 2 swarm peers | grep ipfs &&
44
+ ipfsi 3 swarm peers | grep ipfs
45
+ '
46
+}
47
+
48
+teardown_iptb() {
49
+ test_expect_success "shut down nodes" '
50
+ iptb kill
51
+ '
52
+}
53
+
54
+verify_can_resolve() {
55
+ node=$1
56
+ name=$2
57
+ expected=$3
58
+
59
+ test_expect_success "node can resolve entry" '
60
+ ipfsi $node name resolve $name > resolve
61
+ '
62
+
63
+ test_expect_success "output looks right" '
64
+ printf /ipfs/$expected > expected &&
65
+ test_cmp resolve expected
66
+ '
67
+}
68
+
69
+verify_cannot_resolve() {
70
+ node=$1
71
+ name=$2
72
+
73
+ echo "verifying resolution fails on node $node"
74
+ test_expect_success "node cannot resolve entry" '
75
+ # TODO: this should work without the timeout option
76
+ # but it currently hangs for some reason every so often
77
+ test_expect_code 1 ipfsi $node name resolve --timeout=300ms $name
78
+ '
79
+}
80
+
81
+setup_iptb
82
+
83
+test_expect_success "publish succeeds" '
84
+ HASH=$(echo "foobar" | ipfsi 1 add -q) &&
85
+ ipfsi 1 name publish -t 5s $HASH
86
+'
87
+
88
+test_expect_success "other nodes can resolve" '
89
+ id=$(ipfsi 1 id -f "<id>") &&
90
+ verify_can_resolve 0 $id $HASH &&
91
+ verify_can_resolve 1 $id $HASH &&
92
+ verify_can_resolve 2 $id $HASH &&
93
+ verify_can_resolve 3 $id $HASH
94
+'
95
+
96
+test_expect_success "after five seconds, records are invalid" '
97
+ go-sleep 5s &&
98
+ verify_cannot_resolve 0 $id &&
99
+ verify_cannot_resolve 1 $id &&
100
+ verify_cannot_resolve 2 $id &&
101
+ verify_cannot_resolve 3 $id
102
+'
103
+
104
+test_expect_success "republisher fires after twenty seconds" '
105
+ go-sleep 15s &&
106
+ verify_can_resolve 0 $id $HASH &&
107
+ verify_can_resolve 1 $id $HASH &&
108
+ verify_can_resolve 2 $id $HASH &&
109
+ verify_can_resolve 3 $id $HASH
110
+'
111
+
112
+teardown_iptb
113
+
114
+test_done