diff --git a/core/commands/name/ipns.go b/core/commands/name/ipns.go index d187fcf61ae..e97c395c70b 100644 --- a/core/commands/name/ipns.go +++ b/core/commands/name/ipns.go @@ -25,6 +25,13 @@ type ResolvedPath struct { Path path.Path } +const ( + recursiveOptionName = "recursive" + nocacheOptionName = "nocache" + dhtRecordCountOptionName = "dht-record-count" + dhtTimeoutOptionName = "dht-timeout" +) + var IpnsCmd = &cmds.Command{ Helptext: cmdkit.HelpText{ Tagline: "Resolve IPNS names.", @@ -67,10 +74,10 @@ Resolve the value of a dnslink: cmdkit.StringArg("name", false, false, "The IPNS name to resolve. Defaults to your node's peerID."), }, Options: []cmdkit.Option{ - cmdkit.BoolOption("recursive", "r", "Resolve until the result is not an IPNS name."), - cmdkit.BoolOption("nocache", "n", "Do not use cached entries."), - cmdkit.UintOption("dht-record-count", "dhtrc", "Number of records to request for DHT resolution."), - cmdkit.StringOption("dht-timeout", "dhtt", "Max time to collect values during DHT resolution eg \"30s\". Pass 0 for no timeout."), + cmdkit.BoolOption(recursiveOptionName, "r", "Resolve until the result is not an IPNS name."), + cmdkit.BoolOption(nocacheOptionName, "n", "Do not use cached entries."), + cmdkit.UintOption(dhtRecordCountOptionName, "dhtrc", "Number of records to request for DHT resolution."), + cmdkit.StringOption(dhtTimeoutOptionName, "dhtt", "Max time to collect values during DHT resolution eg \"30s\". Pass 0 for no timeout."), }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) { n, err := cmdenv.GetNode(env) @@ -119,9 +126,10 @@ Resolve the value of a dnslink: name = req.Arguments[0] } - recursive, _ := req.Options["recursive"].(bool) - rc, rcok := req.Options["dht-record-count"].(int) - dhtt, dhttok := req.Options["dht-timeout"].(string) + recursive, _ := req.Options[recursiveOptionName].(bool) + rc, rcok := req.Options[dhtRecordCountOptionName].(int) + dhtt, dhttok := req.Options[dhtTimeoutOptionName].(string) + var ropts []nsopts.ResolveOpt if !recursive { ropts = append(ropts, nsopts.Depth(1)) diff --git a/core/commands/name/publish.go b/core/commands/name/publish.go index 92ddea6850b..c1489baebf5 100644 --- a/core/commands/name/publish.go +++ b/core/commands/name/publish.go @@ -19,6 +19,21 @@ import ( path "gx/ipfs/QmX7uSbkNz76yNwBhuwYwRbhihLnJqM73VTCjS3UMJud9A/go-path" ) +var ( + errAllowOffline = errors.New("can't publish while offline: pass `--allow-offline` to override") + errIpnsMount = errors.New("cannot manually publish while IPNS is mounted") + errIdentityLoad = errors.New("identity not loaded") +) + +const ( + ipfsPathOptionName = "ipfs-path" + resolveOptionName = "resolve" + allowOfflineOptionName = "allow-offline" + lifeTimeOptionName = "lifetime" + ttlOptionName = "ttl" + keyOptionName = "key" +) + var PublishCmd = &cmds.Command{ Helptext: cmdkit.HelpText{ Tagline: "Publish IPNS names.", @@ -60,16 +75,17 @@ Alternatively, publish an using a valid PeerID (as listed by }, Arguments: []cmdkit.Argument{ - cmdkit.StringArg("ipfs-path", true, false, "ipfs path of the object to be published.").EnableStdin(), + cmdkit.StringArg(ipfsPathOptionName, true, false, "ipfs path of the object to be published.").EnableStdin(), }, Options: []cmdkit.Option{ - cmdkit.BoolOption("resolve", "Resolve given path before publishing.").WithDefault(true), - cmdkit.StringOption("lifetime", "t", + cmdkit.BoolOption(resolveOptionName, "Resolve given path before publishing.").WithDefault(true), + cmdkit.StringOption(lifeTimeOptionName, "t", `Time duration that the record will be valid for. <> This accepts durations such as "300s", "1.5h" or "2h45m". Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".`).WithDefault("24h"), - cmdkit.StringOption("ttl", "Time duration this record should be cached for (caution: experimental)."), - cmdkit.StringOption("key", "k", "Name of the key to be used or a valid PeerID, as listed by 'ipfs key list -l'. Default: <>.").WithDefault("self"), + cmdkit.BoolOption(allowOfflineOptionName, "When offline, save the IPNS record to the the local datastore without broadcasting to the network instead of simply failing."), + cmdkit.StringOption(ttlOptionName, "Time duration this record should be cached for (caution: experimental)."), + cmdkit.StringOption(keyOptionName, "k", "Name of the key to be used or a valid PeerID, as listed by 'ipfs key list -l'. Default: <>.").WithDefault("self"), }, Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) { n, err := cmdenv.GetNode(env) @@ -78,7 +94,12 @@ Alternatively, publish an using a valid PeerID (as listed by return } + allowOffline, _ := req.Options[allowOfflineOptionName].(bool) if !n.OnlineMode() { + if !allowOffline { + res.SetError(errAllowOffline, cmdkit.ErrNormal) + return + } err := n.SetupOfflineRouting() if err != nil { res.SetError(err, cmdkit.ErrNormal) @@ -87,22 +108,22 @@ Alternatively, publish an using a valid PeerID (as listed by } if n.Mounts.Ipns != nil && n.Mounts.Ipns.IsActive() { - res.SetError(errors.New("cannot manually publish while IPNS is mounted"), cmdkit.ErrNormal) + res.SetError(errIpnsMount, cmdkit.ErrNormal) return } pstr := req.Arguments[0] if n.Identity == "" { - res.SetError(errors.New("identity not loaded"), cmdkit.ErrNormal) + res.SetError(errIdentityLoad, cmdkit.ErrNormal) return } popts := new(publishOpts) - popts.verifyExists, _ = req.Options["resolve"].(bool) + popts.verifyExists, _ = req.Options[resolveOptionName].(bool) - validtime, _ := req.Options["lifetime"].(string) + validtime, _ := req.Options[lifeTimeOptionName].(string) d, err := time.ParseDuration(validtime) if err != nil { res.SetError(fmt.Errorf("error parsing lifetime option: %s", err), cmdkit.ErrNormal) @@ -112,7 +133,7 @@ Alternatively, publish an using a valid PeerID (as listed by popts.pubValidTime = d ctx := req.Context - if ttl, found := req.Options["ttl"].(string); found { + if ttl, found := req.Options[ttlOptionName].(string); found { d, err := time.ParseDuration(ttl) if err != nil { res.SetError(err, cmdkit.ErrNormal) @@ -122,7 +143,7 @@ Alternatively, publish an using a valid PeerID (as listed by ctx = context.WithValue(ctx, "ipns-publish-ttl", d) } - kname, _ := req.Options["key"].(string) + kname, _ := req.Options[keyOptionName].(string) k, err := keylookup(n, kname) if err != nil { res.SetError(err, cmdkit.ErrNormal) diff --git a/test/sharness/t0100-name.sh b/test/sharness/t0100-name.sh index 7dbaa7f7988..f0ce9f090d0 100755 --- a/test/sharness/t0100-name.sh +++ b/test/sharness/t0100-name.sh @@ -12,10 +12,11 @@ test_init_ipfs # test publishing a hash -test_expect_success "'ipfs name publish' succeeds" ' + +test_expect_success "'ipfs name publish --allow-offline' succeeds" ' PEERID=`ipfs id --format=""` && test_check_peerid "${PEERID}" && - ipfs name publish "/ipfs/$HASH_WELCOME_DOCS" >publish_out + ipfs name publish --allow-offline "/ipfs/$HASH_WELCOME_DOCS" >publish_out ' test_expect_success "publish output looks good" ' @@ -34,10 +35,10 @@ test_expect_success "resolve output looks good" ' # now test with a path -test_expect_success "'ipfs name publish' succeeds" ' +test_expect_success "'ipfs name publish --allow-offline' succeeds" ' PEERID=`ipfs id --format=""` && test_check_peerid "${PEERID}" && - ipfs name publish "/ipfs/$HASH_WELCOME_DOCS/help" >publish_out + ipfs name publish --allow-offline "/ipfs/$HASH_WELCOME_DOCS/help" >publish_out ' test_expect_success "publish a path looks good" ' @@ -62,11 +63,11 @@ test_expect_success "ipfs cat on published content succeeds" ' # publish with an explicit node ID -test_expect_failure "'ipfs name publish ' succeeds" ' +test_expect_failure "'ipfs name publish --allow-offline ' succeeds" ' PEERID=`ipfs id --format=""` && test_check_peerid "${PEERID}" && - echo ipfs name publish "${PEERID}" "/ipfs/$HASH_WELCOME_DOCS" && - ipfs name publish "${PEERID}" "/ipfs/$HASH_WELCOME_DOCS" >actual_node_id_publish + echo ipfs name publish --allow-offline "${PEERID}" "/ipfs/$HASH_WELCOME_DOCS" && + ipfs name publish --allow-offline "${PEERID}" "/ipfs/$HASH_WELCOME_DOCS" >actual_node_id_publish ' test_expect_failure "publish with our explicit node ID looks good" ' @@ -81,8 +82,8 @@ test_expect_success "generate and verify a new key" ' test_check_peerid "${NEWID}" ' -test_expect_success "'ipfs name publish --key= ' succeeds" ' - ipfs name publish --key=${NEWID} "/ipfs/$HASH_WELCOME_DOCS" >actual_node_id_publish +test_expect_success "'ipfs name publis --allow-offline --key= ' succeeds" ' + ipfs name publish --allow-offline --key=${NEWID} "/ipfs/$HASH_WELCOME_DOCS" >actual_node_id_publish ' test_expect_success "publish an explicit node ID as key name looks good" ' @@ -94,7 +95,7 @@ test_expect_success "publish an explicit node ID as key name looks good" ' # test publishing nothing test_expect_success "'ipfs name publish' fails" ' - printf '' | test_expect_code 1 ipfs name publish >publish_out 2>&1 + printf '' | test_expect_code 1 ipfs name publish --allow-offline >publish_out 2>&1 ' test_expect_success "publish output has the correct error" ' @@ -115,4 +116,15 @@ test_expect_success "empty request to name publish doesn't panic and returns err test_kill_ipfs_daemon +# Test daemon in offline mode +test_launch_ipfs_daemon --offline + +test_expect_success "'ipfs name publish' fails offline mode" ' + PEERID=`ipfs id --format=""` && + test_check_peerid "${PEERID}" && + test_expect_code 1 ipfs name publish "/ipfs/$HASH_WELCOME_DOCS" +' + +test_kill_ipfs_daemon + test_done diff --git a/test/sharness/t0110-gateway.sh b/test/sharness/t0110-gateway.sh index 7d519911bc6..8c5d4982fef 100755 --- a/test/sharness/t0110-gateway.sh +++ b/test/sharness/t0110-gateway.sh @@ -62,7 +62,7 @@ test_expect_success "GET IPFS non existent file returns code expected (404)" ' ' test_expect_failure "GET IPNS path succeeds" ' - ipfs name publish "$HASH" && + ipfs name publish --allow-offline "$HASH" && PEERID=$(ipfs config Identity.PeerID) && test_check_peerid "$PEERID" && curl -sfo actual "http://127.0.0.1:$port/ipns/$PEERID" diff --git a/test/sharness/t0160-resolve.sh b/test/sharness/t0160-resolve.sh index d64c0eac5b7..dacd05d9f84 100755 --- a/test/sharness/t0160-resolve.sh +++ b/test/sharness/t0160-resolve.sh @@ -19,7 +19,7 @@ test_resolve_setup_name() { test_expect_success "resolve: prepare name" ' id_hash=$(ipfs id -f="") && - ipfs name publish "$ref" && + ipfs name publish --allow-offline "$ref" && printf "$ref\n" >expected_nameval && ipfs name resolve >actual_nameval && test_cmp expected_nameval actual_nameval @@ -31,7 +31,7 @@ test_resolve_setup_name_fail() { test_expect_failure "resolve: prepare name" ' id_hash=$(ipfs id -f="") && - ipfs name publish "$ref" && + ipfs name publish --allow-offline "$ref" && printf "$ref" >expected_nameval && ipfs name resolve >actual_nameval && test_cmp expected_nameval actual_nameval