add remote pinning to ipfs command (#7661)
Added support for remote pinning services A pinning service is a service that accepts CIDs from a user in order to host the data associated with them. The spec for these services is defined at https://github.com/ipfs/pinning-services-api-spec Support is available via the `ipfs pin remote` CLI and the corresponding HTTP API Co-authored-by: Petar Maymounkov <petarm@gmail.com> Co-authored-by: Marcin Rataj <lidel@lidel.org> Co-authored-by: Adin Schmahmann <adin.schmahmann@gmail.com>
Petar Maymounkov committed
Dec 8, 2020 at 17:32 UTC
a8c7980721909f9cd16c35d699bc481574512c38
10 files changed
+1127
-54
.circleci/config.yml
+30
-2
@@ -118,14 +118,42 @@ jobs:
118
- store_artifacts:
119
path: /tmp/circleci-test-results
120
sharness:
121
- executor: golang
121
+ machine:
122
+ image: ubuntu-2004:202010-01
123
+ working_directory: ~/ipfs/go-ipfs
124
+ environment:
125
+ <<: *default_environment
126
+ GO111MODULE: "on"
127
+ TEST_NO_DOCKER: 1
128
+ TEST_NO_FUSE: 1
129
+ GOPATH: /home/circleci/go
130
+ TEST_VERBOSE: 1
131
steps:
132
- run: sudo apt install socat
133
- checkout
134
+
135
+ - run:
136
+ mkdir rb-pinning-service-api &&
137
+ cd rb-pinning-service-api &&
138
+ git init &&
139
+ git remote add origin https://github.com/ipfs-shipyard/rb-pinning-service-api.git &&
140
+ git fetch --depth 1 origin 773c3adbb421c551d2d89288abac3e01e1f7c3a8 &&
141
+ git checkout FETCH_HEAD
142
+ - run:
143
+ cd rb-pinning-service-api &&
144
+ docker-compose pull &&
145
+ docker-compose up -d
146
+
147
- *make_out_dirs
148
- *restore_gomod
149
128
- - run: make -O -j 10 coverage/sharness_tests.coverprofile test/sharness/test-results/sharness.xml TEST_GENERATE_JUNIT=1 CONTINUE_ON_S_FAILURE=1
150
+ - run:
151
+ name: Setup Environment Variables
152
+ # we need the docker host IP; all ports exported by child containers can be accessed there.
153
+ command: echo "export DOCKER_HOST=$(ip -4 addr show docker0 | grep -Po 'inet \K[\d.]+')" >> $BASH_ENV
154
+ - run:
155
+ echo DOCKER_HOST=$DOCKER_HOST &&
156
+ make -O -j 3 coverage/sharness_tests.coverprofile test/sharness/test-results/sharness.xml TEST_GENERATE_JUNIT=1 CONTINUE_ON_S_FAILURE=1 DOCKER_HOST=$DOCKER_HOST
157
158
- run:
159
when: always
core/commands/commands_test.go
+9
-1
@@ -177,11 +177,19 @@ func TestCommands(t *testing.T) {
177
"/p2p/stream/ls",
178
"/pin",
179
"/pin/add",
180
- "/ping",
180
"/pin/ls",
181
+ "/pin/remote",
182
+ "/pin/remote/add",
183
+ "/pin/remote/ls",
184
+ "/pin/remote/rm",
185
+ "/pin/remote/service",
186
+ "/pin/remote/service/add",
187
+ "/pin/remote/service/ls",
188
+ "/pin/remote/service/rm",
189
"/pin/rm",
190
"/pin/update",
191
"/pin/verify",
192
+ "/ping",
193
"/pubsub",
194
"/pubsub/ls",
195
"/pubsub/peers",
core/commands/config.go
+75
-5
@@ -15,8 +15,8 @@ import (
15
"github.com/ipfs/go-ipfs/repo/fsrepo"
16
17
"github.com/elgris/jsondiff"
18
- "github.com/ipfs/go-ipfs-cmds"
19
- "github.com/ipfs/go-ipfs-config"
18
+ cmds "github.com/ipfs/go-ipfs-cmds"
19
+ config "github.com/ipfs/go-ipfs-config"
20
)
21
22
// ConfigUpdateOutput is config profile apply command's output
@@ -36,6 +36,8 @@ const (
36
configDryRunOptionName = "dry-run"
37
)
38
39
+var tryRemoteServiceApiErr = errors.New("cannot show or change pinning services through this API (try: ipfs pin remote service --help)")
40
+
41
var ConfigCmd = &cmds.Command{
42
Helptext: cmds.HelpText{
43
Tagline: "Get and set ipfs config values.",
@@ -86,6 +88,12 @@ Set the value of the 'Datastore.Path' key:
88
default:
89
}
90
91
+ // Temporary fix until we move ApiKey secrets out of the config file
92
+ // (remote services are a map, so more advanced blocking is required)
93
+ if blocked := inBlockedScope(key, config.RemoteServicesSelector); blocked {
94
+ return tryRemoteServiceApiErr
95
+ }
96
+
97
cfgRoot, err := cmdenv.GetConfigRoot(env)
98
if err != nil {
99
return err
@@ -140,11 +148,29 @@ Set the value of the 'Datastore.Path' key:
148
Type: ConfigField{},
149
}
150
151
+// Returns bool to indicate if tested key is in the blocked scope.
152
+// (scope includes parent, direct, and child match)
153
+func inBlockedScope(testKey string, blockedScope string) bool {
154
+ blockedScope = strings.ToLower(blockedScope)
155
+ roots := strings.Split(strings.ToLower(testKey), ".")
156
+ var scope []string
157
+ for _, name := range roots {
158
+ scope := append(scope, name)
159
+ impactedKey := strings.Join(scope, ".")
160
+ // blockedScope=foo.bar.BLOCKED should return true
161
+ // for parent and child impactedKeys: foo.bar and foo.bar.BLOCKED.subkey
162
+ if strings.HasPrefix(impactedKey, blockedScope) || strings.HasPrefix(blockedScope, impactedKey) {
163
+ return true
164
+ }
165
+ }
166
+ return false
167
+}
168
+
169
var configShowCmd = &cmds.Command{
170
Helptext: cmds.HelpText{
171
Tagline: "Output config file contents.",
172
ShortDescription: `
147
-NOTE: For security reasons, this command will omit your private key. If you would like to make a full backup of your config (private key included), you must copy the config file from your repo.
173
+NOTE: For security reasons, this command will omit your private key and remote services. If you would like to make a full backup of your config (private key included), you must copy the config file from your repo.
174
`,
175
},
176
Type: map[string]interface{}{},
@@ -175,6 +201,11 @@ NOTE: For security reasons, this command will omit your private key. If you woul
201
return err
202
}
203
204
+ err = scrubOptionalValue(cfg, []string{config.PinningTag, config.RemoteServicesTag})
205
+ if err != nil {
206
+ return err
207
+ }
208
+
209
return cmds.EmitOnce(res, &cfg)
210
},
211
Encoders: cmds.EncoderMap{
@@ -190,7 +221,17 @@ NOTE: For security reasons, this command will omit your private key. If you woul
221
},
222
}
223
224
+// Scrubs value and returns error if missing
225
func scrubValue(m map[string]interface{}, key []string) error {
226
+ return scrub(m, key, false)
227
+}
228
+
229
+// Scrubs value and returns no error if missing
230
+func scrubOptionalValue(m map[string]interface{}, key []string) error {
231
+ return scrub(m, key, true)
232
+}
233
+
234
+func scrub(m map[string]interface{}, key []string, okIfMissing bool) error {
235
find := func(m map[string]interface{}, k string) (string, interface{}, bool) {
236
lckey := strings.ToLower(k)
237
for mkey, val := range m {
@@ -205,7 +246,7 @@ func scrubValue(m map[string]interface{}, key []string) error {
246
cur := m
247
for _, k := range key[:len(key)-1] {
248
foundk, val, ok := find(cur, k)
208
- if !ok {
249
+ if !ok && !okIfMissing {
250
return errors.New("failed to find specified key")
251
}
252
@@ -223,7 +264,7 @@ func scrubValue(m map[string]interface{}, key []string) error {
264
}
265
266
todel, _, ok := find(cur, key[len(key)-1])
226
- if !ok {
267
+ if !ok && !okIfMissing {
268
return fmt.Errorf("%s, not found", strings.Join(key, "."))
269
}
270
@@ -466,6 +507,9 @@ func replaceConfig(r repo.Repo, file io.Reader) error {
507
if err := json.NewDecoder(file).Decode(&cfg); err != nil {
508
return errors.New("failed to decode file as config")
509
}
510
+
511
+ // Handle Identity.PrivKey (secret)
512
+
513
if len(cfg.Identity.PrivKey) != 0 {
514
return errors.New("setting private key with API is not supported")
515
}
@@ -482,5 +526,31 @@ func replaceConfig(r repo.Repo, file io.Reader) error {
526
527
cfg.Identity.PrivKey = pkstr
528
529
+ // Handle Pinning.RemoteServices (ApiKey of each service is secret)
530
+ // Note: these settings are opt-in and may be missing
531
+
532
+ if len(cfg.Pinning.RemoteServices) != 0 {
533
+ return tryRemoteServiceApiErr
534
+ }
535
+
536
+ // detect if existing config has any remote services defined..
537
+ if remoteServicesTag, err := getConfig(r, config.RemoteServicesSelector); err == nil {
538
+ // seems that golang cannot type assert map[string]interface{} to map[string]config.RemotePinningService
539
+ // so we have to manually copy the data :-|
540
+ if val, ok := remoteServicesTag.Value.(map[string]interface{}); ok {
541
+ var services map[string]config.RemotePinningService
542
+ jsonString, err := json.Marshal(val)
543
+ if err != nil {
544
+ return fmt.Errorf("failed to replace config while preserving %s: %s", config.RemoteServicesSelector, err)
545
+ }
546
+ err = json.Unmarshal(jsonString, &services)
547
+ if err != nil {
548
+ return fmt.Errorf("failed to replace config while preserving %s: %s", config.RemoteServicesSelector, err)
549
+ }
550
+ // .. if so, apply them on top of the new config
551
+ cfg.Pinning.RemoteServices = services
552
+ }
553
+ }
554
+
555
return r.SetConfig(&cfg)
556
}
core/commands/pin/pin.go
renamed
+2
-1
@@ -1,4 +1,4 @@
1
-package commands
1
+package pin
2
3
import (
4
"context"
@@ -35,6 +35,7 @@ var PinCmd = &cmds.Command{
35
"ls": listPinCmd,
36
"verify": verifyPinCmd,
37
"update": updatePinCmd,
38
+ "remote": remotePinCmd,
39
},
40
}
41
core/commands/pin/remotepin.go
new
+671
@@ -0,0 +1,671 @@
1
+package pin
2
+
3
+import (
4
+ "context"
5
+ "fmt"
6
+ "io"
7
+ "sort"
8
+ "strings"
9
+ "text/tabwriter"
10
+ "time"
11
+
12
+ neturl "net/url"
13
+
14
+ "golang.org/x/sync/errgroup"
15
+
16
+ cid "github.com/ipfs/go-cid"
17
+ cmds "github.com/ipfs/go-ipfs-cmds"
18
+ config "github.com/ipfs/go-ipfs-config"
19
+ "github.com/ipfs/go-ipfs/core/commands/cmdenv"
20
+ fsrepo "github.com/ipfs/go-ipfs/repo/fsrepo"
21
+ logging "github.com/ipfs/go-log"
22
+ pinclient "github.com/ipfs/go-pinning-service-http-client"
23
+ path "github.com/ipfs/interface-go-ipfs-core/path"
24
+ "github.com/libp2p/go-libp2p-core/host"
25
+ peer "github.com/libp2p/go-libp2p-core/peer"
26
+)
27
+
28
+var log = logging.Logger("core/commands/cmdenv")
29
+
30
+var remotePinCmd = &cmds.Command{
31
+ Helptext: cmds.HelpText{
32
+ Tagline: "Pin (and unpin) objects to remote pinning service.",
33
+ },
34
+
35
+ Subcommands: map[string]*cmds.Command{
36
+ "add": addRemotePinCmd,
37
+ "ls": listRemotePinCmd,
38
+ "rm": rmRemotePinCmd,
39
+ "service": remotePinServiceCmd,
40
+ },
41
+}
42
+
43
+var remotePinServiceCmd = &cmds.Command{
44
+ Helptext: cmds.HelpText{
45
+ Tagline: "Configure remote pinning services.",
46
+ },
47
+
48
+ Subcommands: map[string]*cmds.Command{
49
+ "add": addRemotePinServiceCmd,
50
+ "ls": lsRemotePinServiceCmd,
51
+ "rm": rmRemotePinServiceCmd,
52
+ },
53
+}
54
+
55
+const pinNameOptionName = "name"
56
+const pinCIDsOptionName = "cid"
57
+const pinStatusOptionName = "status"
58
+const pinServiceNameOptionName = "service"
59
+const pinServiceURLOptionName = "url"
60
+const pinServiceKeyOptionName = "key"
61
+const pinServiceStatOptionName = "stat"
62
+const pinBackgroundOptionName = "background"
63
+const pinForceOptionName = "force"
64
+
65
+type RemotePinOutput struct {
66
+ Status string
67
+ Cid string
68
+ Name string
69
+}
70
+
71
+func toRemotePinOutput(ps pinclient.PinStatusGetter) RemotePinOutput {
72
+ return RemotePinOutput{
73
+ Name: ps.GetPin().GetName(),
74
+ Status: ps.GetStatus().String(),
75
+ Cid: ps.GetPin().GetCid().String(),
76
+ }
77
+}
78
+
79
+func printRemotePinDetails(w io.Writer, out *RemotePinOutput) {
80
+ tw := tabwriter.NewWriter(w, 0, 0, 1, ' ', 0)
81
+ defer tw.Flush()
82
+ fw := func(k string, v string) {
83
+ fmt.Fprintf(tw, "%s:\t%s\n", k, v)
84
+ }
85
+ fw("CID", out.Cid)
86
+ fw("Name", out.Name)
87
+ fw("Status", out.Status)
88
+}
89
+
90
+// remote pin commands
91
+
92
+var pinServiceNameOption = cmds.StringOption(pinServiceNameOptionName, "Name of the remote pinning service to use.")
93
+
94
+var addRemotePinCmd = &cmds.Command{
95
+ Helptext: cmds.HelpText{
96
+ Tagline: "Pin object to remote pinning service.",
97
+ ShortDescription: "Stores an IPFS object from a given path to a remote pinning service.",
98
+ },
99
+
100
+ Arguments: []cmds.Argument{
101
+ cmds.StringArg("ipfs-path", true, false, "Path to object(s) to be pinned."),
102
+ },
103
+ Options: []cmds.Option{
104
+ cmds.StringOption(pinNameOptionName, "An optional name for the pin."),
105
+ pinServiceNameOption,
106
+ cmds.BoolOption(pinBackgroundOptionName, "Add to the queue on the remote service and return immediately (does not wait for pinned status).").WithDefault(false),
107
+ },
108
+ Type: RemotePinOutput{},
109
+ Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
110
+ ctx, cancel := context.WithCancel(req.Context)
111
+ defer cancel()
112
+
113
+ // Get remote service
114
+ c, err := getRemotePinServiceFromRequest(req, env)
115
+ if err != nil {
116
+ return err
117
+ }
118
+
119
+ // Prepare value for Pin.cid
120
+ if len(req.Arguments) != 1 {
121
+ return fmt.Errorf("expecting one CID argument")
122
+ }
123
+ api, err := cmdenv.GetApi(env, req)
124
+ if err != nil {
125
+ return err
126
+ }
127
+ rp, err := api.ResolvePath(ctx, path.New(req.Arguments[0]))
128
+ if err != nil {
129
+ return err
130
+ }
131
+
132
+ // Prepare Pin.name
133
+ opts := []pinclient.AddOption{}
134
+ if name, nameFound := req.Options[pinNameOptionName]; nameFound {
135
+ nameStr := name.(string)
136
+ opts = append(opts, pinclient.PinOpts.WithName(nameStr))
137
+ }
138
+
139
+ // Prepare Pin.origins
140
+ // Add own multiaddrs to the 'origins' array, so Pinning Service can
141
+ // use that as a hint and connect back to us (if possible)
142
+ node, err := cmdenv.GetNode(env)
143
+ if err != nil {
144
+ return err
145
+ }
146
+ if node.PeerHost != nil {
147
+ addrs, err := peer.AddrInfoToP2pAddrs(host.InfoFromHost(node.PeerHost))
148
+ if err != nil {
149
+ return err
150
+ }
151
+ opts = append(opts, pinclient.PinOpts.WithOrigins(addrs...))
152
+ }
153
+
154
+ // Execute remote pin request
155
+ // TODO: fix panic when pinning service is down
156
+ ps, err := c.Add(ctx, rp.Cid(), opts...)
157
+ if err != nil {
158
+ return err
159
+ }
160
+
161
+ // Act on PinStatus.delegates
162
+ // If Pinning Service returned any delegates, proactively try to
163
+ // connect to them to facilitate data exchange without waiting for DHT
164
+ // lookup
165
+ for _, d := range ps.GetDelegates() {
166
+ // TODO: confirm this works as expected
167
+ p, err := peer.AddrInfoFromP2pAddr(d)
168
+ if err != nil {
169
+ return err
170
+ }
171
+ if err := api.Swarm().Connect(ctx, *p); err != nil {
172
+ log.Infof("error connecting to remote pin delegate %v : %w", d, err)
173
+ }
174
+ }
175
+
176
+ // Block unless --background=true is passed
177
+ if !req.Options[pinBackgroundOptionName].(bool) {
178
+ requestId := ps.GetRequestId()
179
+ for {
180
+ ps, err = c.GetStatusByID(ctx, requestId)
181
+ if err != nil {
182
+ return fmt.Errorf("failed to check pin status for requestid=%q due to error: %v", requestId, err)
183
+ }
184
+ if ps.GetRequestId() != requestId {
185
+ return fmt.Errorf("failed to check pin status for requestid=%q, remote service sent unexpected requestid=%q", requestId, ps.GetRequestId())
186
+ }
187
+ s := ps.GetStatus()
188
+ if s == pinclient.StatusPinned {
189
+ break
190
+ }
191
+ if s == pinclient.StatusFailed {
192
+ return fmt.Errorf("remote service failed to pin requestid=%q", requestId)
193
+ }
194
+ tmr := time.NewTimer(time.Second / 2)
195
+ select {
196
+ case <-tmr.C:
197
+ case <-ctx.Done():
198
+ return fmt.Errorf("waiting for pin interrupted, requestid=%q remains on remote service", requestId)
199
+ }
200
+ }
201
+ }
202
+
203
+ return res.Emit(toRemotePinOutput(ps))
204
+ },
205
+ Encoders: cmds.EncoderMap{
206
+ cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *RemotePinOutput) error {
207
+ printRemotePinDetails(w, out)
208
+ return nil
209
+ }),
210
+ },
211
+}
212
+
213
+var listRemotePinCmd = &cmds.Command{
214
+ Helptext: cmds.HelpText{
215
+ Tagline: "List objects pinned to remote pinning service.",
216
+ ShortDescription: `
217
+Returns a list of objects that are pinned to a remote pinning service.
218
+`,
219
+ LongDescription: `
220
+Returns a list of objects that are pinned to a remote pinning service.
221
+`,
222
+ },
223
+
224
+ Arguments: []cmds.Argument{},
225
+ Options: []cmds.Option{
226
+ cmds.StringOption(pinNameOptionName, "Return pins objects with names that contain provided value (case-sensitive, exact match)."),
227
+ cmds.StringsOption(pinCIDsOptionName, "Return only pin objects for the specified CID(s); optional, comma separated."),
228
+ cmds.StringsOption(pinStatusOptionName, "Return only pin objects with the specified statuses (queued,pinning,pinned,failed)").WithDefault([]string{"pinned"}),
229
+ pinServiceNameOption,
230
+ },
231
+ Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
232
+ ctx, cancel := context.WithCancel(req.Context)
233
+ defer cancel()
234
+
235
+ c, err := getRemotePinServiceFromRequest(req, env)
236
+ if err != nil {
237
+ return err
238
+ }
239
+
240
+ psCh, errCh, err := lsRemote(ctx, req, c)
241
+ if err != nil {
242
+ return err
243
+ }
244
+
245
+ for ps := range psCh {
246
+ if err := res.Emit(toRemotePinOutput(ps)); err != nil {
247
+ return err
248
+ }
249
+ }
250
+
251
+ return <-errCh
252
+ },
253
+ Type: RemotePinOutput{},
254
+ Encoders: cmds.EncoderMap{
255
+ cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, out *RemotePinOutput) error {
256
+ // pin remote ls produces a flat output similar to legacy pin ls
257
+ fmt.Fprintf(w, "%s\t%s\t%s\n", out.Cid, out.Status, out.Name)
258
+ return nil
259
+ }),
260
+ },
261
+}
262
+
263
+// Executes GET /pins/?query-with-filters
264
+func lsRemote(ctx context.Context, req *cmds.Request, c *pinclient.Client) (chan pinclient.PinStatusGetter, chan error, error) {
265
+ opts := []pinclient.LsOption{}
266
+ if name, nameFound := req.Options[pinNameOptionName]; nameFound {
267
+ nameStr := name.(string)
268
+ opts = append(opts, pinclient.PinOpts.FilterName(nameStr))
269
+ }
270
+
271
+ if cidsRaw, cidsFound := req.Options[pinCIDsOptionName]; cidsFound {
272
+ cidsRawArr := cidsRaw.([]string)
273
+ parsedCIDs := []cid.Cid{}
274
+ for _, rawCID := range flattenCommaList(cidsRawArr) {
275
+ parsedCID, err := cid.Decode(rawCID)
276
+ if err != nil {
277
+ return nil, nil, fmt.Errorf("CID %q cannot be parsed: %v", rawCID, err)
278
+ }
279
+ parsedCIDs = append(parsedCIDs, parsedCID)
280
+ }
281
+ opts = append(opts, pinclient.PinOpts.FilterCIDs(parsedCIDs...))
282
+ }
283
+ if statusRaw, statusFound := req.Options[pinStatusOptionName]; statusFound {
284
+ statusRawArr := statusRaw.([]string)
285
+ parsedStatuses := []pinclient.Status{}
286
+ for _, rawStatus := range flattenCommaList(statusRawArr) {
287
+ s := pinclient.Status(rawStatus)
288
+ if s.String() == string(pinclient.StatusUnknown) {
289
+ return nil, nil, fmt.Errorf("status %q is not valid", rawStatus)
290
+ }
291
+ parsedStatuses = append(parsedStatuses, s)
292
+ }
293
+ opts = append(opts, pinclient.PinOpts.FilterStatus(parsedStatuses...))
294
+ }
295
+
296
+ psCh, errCh := c.Ls(ctx, opts...)
297
+
298
+ return psCh, errCh, nil
299
+}
300
+
301
+func flattenCommaList(list []string) []string {
302
+ flatList := list[:0]
303
+ for _, s := range list {
304
+ flatList = append(flatList, strings.Split(s, ",")...)
305
+ }
306
+ return flatList
307
+}
308
+
309
+var rmRemotePinCmd = &cmds.Command{
310
+ Helptext: cmds.HelpText{
311
+ Tagline: "Remove pinned objects from remote pinning service.",
312
+ ShortDescription: `
313
+Removes the pin from the given object allowing it to be garbage
314
+collected if needed.
315
+`,
316
+ },
317
+
318
+ Arguments: []cmds.Argument{},
319
+ Options: []cmds.Option{
320
+ pinServiceNameOption,
321
+ cmds.StringOption(pinNameOptionName, "Remove pin objects with names that contain provided value (case-sensitive, exact match)."),
322
+ cmds.StringsOption(pinCIDsOptionName, "Remove only pin objects for the specified CID(s)."),
323
+ cmds.StringsOption(pinStatusOptionName, "Remove only pin objects with the specified statuses (queued,pinning,pinned,failed).").WithDefault([]string{"pinned"}),
324
+ cmds.BoolOption(pinForceOptionName, "Remove multiple pins without confirmation.").WithDefault(false),
325
+ },
326
+ Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
327
+ ctx, cancel := context.WithCancel(req.Context)
328
+ defer cancel()
329
+
330
+ c, err := getRemotePinServiceFromRequest(req, env)
331
+ if err != nil {
332
+ return err
333
+ }
334
+
335
+ rmIDs := []string{}
336
+ if len(req.Arguments) == 0 {
337
+ psCh, errCh, err := lsRemote(ctx, req, c)
338
+ if err != nil {
339
+ return err
340
+ }
341
+ for ps := range psCh {
342
+ rmIDs = append(rmIDs, ps.GetRequestId())
343
+ }
344
+ if err = <-errCh; err != nil {
345
+ return fmt.Errorf("error while listing remote pins: %v", err)
346
+ }
347
+
348
+ if len(rmIDs) > 1 && !req.Options[pinForceOptionName].(bool) {
349
+ return fmt.Errorf("multiple remote pins are matching this query, add --force to confirm the bulk removal")
350
+ }
351
+ } else {
352
+ return fmt.Errorf("unexpected argument %q", req.Arguments[0])
353
+ }
354
+
355
+ for _, rmID := range rmIDs {
356
+ if err := c.DeleteByID(ctx, rmID); err != nil {
357
+ return fmt.Errorf("removing pin identified by requestid=%q failed: %v", rmID, err)
358
+ }
359
+ }
360
+ return nil
361
+ },
362
+}
363
+
364
+// remote service commands
365
+
366
+var addRemotePinServiceCmd = &cmds.Command{
367
+ Helptext: cmds.HelpText{
368
+ Tagline: "Add remote pinning service.",
369
+ ShortDescription: "Add a credentials for access to a remote pinning service.",
370
+ },
371
+ Arguments: []cmds.Argument{
372
+ cmds.StringArg(pinServiceNameOptionName, true, false, "Service name."),
373
+ cmds.StringArg(pinServiceURLOptionName, true, false, "Service URL."),
374
+ cmds.StringArg(pinServiceKeyOptionName, true, false, "Service key."),
375
+ },
376
+ Type: nil,
377
+ Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
378
+ cfgRoot, err := cmdenv.GetConfigRoot(env)
379
+ if err != nil {
380
+ return err
381
+ }
382
+ repo, err := fsrepo.Open(cfgRoot)
383
+ if err != nil {
384
+ return err
385
+ }
386
+ defer repo.Close()
387
+
388
+ if len(req.Arguments) < 3 {
389
+ return fmt.Errorf("expecting three arguments: service name, url and key")
390
+ }
391
+
392
+ name := req.Arguments[0]
393
+ url := strings.TrimSuffix(req.Arguments[1], "/pins") // fix /pins/pins :-)
394
+ key := req.Arguments[2]
395
+
396
+ u, err := neturl.ParseRequestURI(url)
397
+ if err != nil || !strings.HasPrefix(u.Scheme, "http") {
398
+ return fmt.Errorf("service url must be a valid HTTP URL")
399
+ }
400
+
401
+ cfg, err := repo.Config()
402
+ if err != nil {
403
+ return err
404
+ }
405
+ if cfg.Pinning.RemoteServices != nil {
406
+ if _, present := cfg.Pinning.RemoteServices[name]; present {
407
+ return fmt.Errorf("service already present")
408
+ }
409
+ } else {
410
+ cfg.Pinning.RemoteServices = map[string]config.RemotePinningService{}
411
+ }
412
+
413
+ cfg.Pinning.RemoteServices[name] = config.RemotePinningService{
414
+ Api: config.RemotePinningServiceApi{
415
+ Endpoint: url,
416
+ Key: key,
417
+ },
418
+ }
419
+
420
+ return repo.SetConfig(cfg)
421
+ },
422
+}
423
+
424
+var rmRemotePinServiceCmd = &cmds.Command{
425
+ Helptext: cmds.HelpText{
426
+ Tagline: "Remove remote pinning service.",
427
+ ShortDescription: "Remove credentials for access to a remote pinning service.",
428
+ },
429
+ Arguments: []cmds.Argument{
430
+ cmds.StringArg("remote-pin-service", true, false, "Name of remote pinning service to remove."),
431
+ },
432
+ Options: []cmds.Option{},
433
+ Type: nil,
434
+ Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
435
+ cfgRoot, err := cmdenv.GetConfigRoot(env)
436
+ if err != nil {
437
+ return err
438
+ }
439
+ repo, err := fsrepo.Open(cfgRoot)
440
+ if err != nil {
441
+ return err
442
+ }
443
+ defer repo.Close()
444
+
445
+ if len(req.Arguments) != 1 {
446
+ return fmt.Errorf("expecting one argument: name")
447
+ }
448
+ name := req.Arguments[0]
449
+
450
+ cfg, err := repo.Config()
451
+ if err != nil {
452
+ return err
453
+ }
454
+ if cfg.Pinning.RemoteServices != nil {
455
+ delete(cfg.Pinning.RemoteServices, name)
456
+ }
457
+ return repo.SetConfig(cfg)
458
+ },
459
+}
460
+
461
+var lsRemotePinServiceCmd = &cmds.Command{
462
+ Helptext: cmds.HelpText{
463
+ Tagline: "List remote pinning services.",
464
+ ShortDescription: "List remote pinning services.",
465
+ },
466
+ Arguments: []cmds.Argument{},
467
+ Options: []cmds.Option{
468
+ cmds.BoolOption(pinServiceStatOptionName, "Try to fetch and display current pin count on remote service (queued/pinning/pinned/failed).").WithDefault(false),
469
+ },
470
+ Run: func(req *cmds.Request, res cmds.ResponseEmitter, env cmds.Environment) error {
471
+ ctx, cancel := context.WithCancel(req.Context)
472
+ defer cancel()
473
+
474
+ cfgRoot, err := cmdenv.GetConfigRoot(env)
475
+ if err != nil {
476
+ return err
477
+ }
478
+ repo, err := fsrepo.Open(cfgRoot)
479
+ if err != nil {
480
+ return err
481
+ }
482
+ defer repo.Close()
483
+
484
+ cfg, err := repo.Config()
485
+ if err != nil {
486
+ return err
487
+ }
488
+ if cfg.Pinning.RemoteServices == nil {
489
+ return nil // no pinning services added yet
490
+ }
491
+ services := cfg.Pinning.RemoteServices
492
+ result := PinServicesList{make([]ServiceDetails, 0, len(services))}
493
+ for svcName, svcConfig := range services {
494
+ svcDetails := ServiceDetails{svcName, svcConfig.Api.Endpoint, nil}
495
+
496
+ // if --pin-count is passed, we try to fetch pin numbers from remote service
497
+ if req.Options[pinServiceStatOptionName].(bool) {
498
+ lsRemotePinCount := func(ctx context.Context, env cmds.Environment, svcName string) (*PinCount, error) {
499
+ c, err := getRemotePinService(env, svcName)
500
+ if err != nil {
501
+ return nil, err
502
+ }
503
+ // we only care about total count, so requesting smallest batch
504
+ batch := pinclient.PinOpts.Limit(1)
505
+ fs := pinclient.PinOpts.FilterStatus
506
+
507
+ statuses := []pinclient.Status{
508
+ pinclient.StatusQueued,
509
+ pinclient.StatusPinning,
510
+ pinclient.StatusPinned,
511
+ pinclient.StatusFailed,
512
+ }
513
+
514
+ g, ctx := errgroup.WithContext(ctx)
515
+ pc := &PinCount{}
516
+
517
+ for _, s := range statuses {
518
+ status := s // lol https://golang.org/doc/faq#closures_and_goroutines
519
+ g.Go(func() error {
520
+ _, n, err := c.LsBatchSync(ctx, batch, fs(status))
521
+ if err != nil {
522
+ return err
523
+ }
524
+ switch status {
525
+ case pinclient.StatusQueued:
526
+ pc.Queued = n
527
+ case pinclient.StatusPinning:
528
+ pc.Pinning = n
529
+ case pinclient.StatusPinned:
530
+ pc.Pinned = n
531
+ case pinclient.StatusFailed:
532
+ pc.Failed = n
533
+ }
534
+ return nil
535
+ })
536
+ }
537
+ if err := g.Wait(); err != nil {
538
+ return nil, err
539
+ }
540
+
541
+ return pc, nil
542
+ }
543
+
544
+ pinCount, err := lsRemotePinCount(ctx, env, svcName)
545
+
546
+ // PinCount is present only if we were able to fetch counts.
547
+ // We don't want to break listing of services so this is best-effort.
548
+ // (verbose err is returned by 'pin remote ls', if needed)
549
+ svcDetails.Stat = &Stat{}
550
+ if err == nil {
551
+ svcDetails.Stat.Status = "valid"
552
+ svcDetails.Stat.PinCount = pinCount
553
+ } else {
554
+ svcDetails.Stat.Status = "invalid"
555
+ }
556
+ }
557
+ result.RemoteServices = append(result.RemoteServices, svcDetails)
558
+ }
559
+ sort.Sort(result)
560
+ return cmds.EmitOnce(res, &result)
561
+ },
562
+ Type: PinServicesList{},
563
+ Encoders: cmds.EncoderMap{
564
+ cmds.Text: cmds.MakeTypedEncoder(func(req *cmds.Request, w io.Writer, list *PinServicesList) error {
565
+ tw := tabwriter.NewWriter(w, 1, 2, 1, ' ', 0)
566
+ withStat := req.Options[pinServiceStatOptionName].(bool)
567
+ for _, s := range list.RemoteServices {
568
+ if withStat {
569
+ stat := s.Stat.Status
570
+ pc := s.Stat.PinCount
571
+ if s.Stat.PinCount != nil {
572
+ stat = fmt.Sprintf("%d/%d/%d/%d", pc.Queued, pc.Pinning, pc.Pinned, pc.Failed)
573
+ }
574
+ fmt.Fprintf(tw, "%s\t%s\t%s\n", s.Service, s.ApiEndpoint, stat)
575
+ } else {
576
+ fmt.Fprintf(tw, "%s\t%s\n", s.Service, s.ApiEndpoint)
577
+ }
578
+ }
579
+ tw.Flush()
580
+ return nil
581
+ }),
582
+ },
583
+}
584
+
585
+type ServiceDetails struct {
586
+ Service string
587
+ ApiEndpoint string
588
+ Stat *Stat `json:",omitempty"` // present only when --stat not passed
589
+}
590
+
591
+type Stat struct {
592
+ Status string
593
+ PinCount *PinCount `json:",omitempty"` // missing when --stat is passed but the service is offline
594
+}
595
+
596
+type PinCount struct {
597
+ Queued int
598
+ Pinning int
599
+ Pinned int
600
+ Failed int
601
+}
602
+
603
+// Struct returned by ipfs pin remote service ls --enc=json | jq
604
+type PinServicesList struct {
605
+ RemoteServices []ServiceDetails
606
+}
607
+
608
+func (l PinServicesList) Len() int {
609
+ return len(l.RemoteServices)
610
+}
611
+
612
+func (l PinServicesList) Swap(i, j int) {
613
+ s := l.RemoteServices
614
+ s[i], s[j] = s[j], s[i]
615
+}
616
+
617
+func (l PinServicesList) Less(i, j int) bool {
618
+ s := l.RemoteServices
619
+ return s[i].Service < s[j].Service
620
+}
621
+
622
+func getRemotePinServiceFromRequest(req *cmds.Request, env cmds.Environment) (*pinclient.Client, error) {
623
+ service, serviceFound := req.Options[pinServiceNameOptionName]
624
+ if !serviceFound {
625
+ return nil, fmt.Errorf("a service name must be passed")
626
+ }
627
+
628
+ serviceStr := service.(string)
629
+ var err error
630
+ c, err := getRemotePinService(env, serviceStr)
631
+ if err != nil {
632
+ return nil, err
633
+ }
634
+
635
+ return c, nil
636
+}
637
+
638
+func getRemotePinService(env cmds.Environment, name string) (*pinclient.Client, error) {
639
+ if name == "" {
640
+ return nil, fmt.Errorf("remote pinning service name not specified")
641
+ }
642
+ url, key, err := getRemotePinServiceInfo(env, name)
643
+ if err != nil {
644
+ return nil, err
645
+ }
646
+ return pinclient.NewClient(url, key), nil
647
+}
648
+
649
+func getRemotePinServiceInfo(env cmds.Environment, name string) (url, key string, err error) {
650
+ cfgRoot, err := cmdenv.GetConfigRoot(env)
651
+ if err != nil {
652
+ return "", "", err
653
+ }
654
+ repo, err := fsrepo.Open(cfgRoot)
655
+ if err != nil {
656
+ return "", "", err
657
+ }
658
+ defer repo.Close()
659
+ cfg, err := repo.Config()
660
+ if err != nil {
661
+ return "", "", err
662
+ }
663
+ if cfg.Pinning.RemoteServices == nil {
664
+ return "", "", fmt.Errorf("service not known")
665
+ }
666
+ service, present := cfg.Pinning.RemoteServices[name]
667
+ if !present {
668
+ return "", "", fmt.Errorf("service not known")
669
+ }
670
+ return service.Api.Endpoint, service.Api.Key, nil
671
+}
core/commands/root.go
+2
-1
@@ -7,6 +7,7 @@ import (
7
dag "github.com/ipfs/go-ipfs/core/commands/dag"
8
name "github.com/ipfs/go-ipfs/core/commands/name"
9
ocmd "github.com/ipfs/go-ipfs/core/commands/object"
10
+ "github.com/ipfs/go-ipfs/core/commands/pin"
11
unixfs "github.com/ipfs/go-ipfs/core/commands/unixfs"
12
13
cmds "github.com/ipfs/go-ipfs-cmds"
@@ -136,7 +137,7 @@ var rootSubcommands = map[string]*cmds.Command{
137
"mount": MountCmd,
138
"name": name.NameCmd,
139
"object": ocmd.ObjectCmd,
139
- "pin": PinCmd,
140
+ "pin": pin.PinCmd,
141
"ping": PingCmd,
142
"p2p": P2PCmd,
143
"refs": RefsCmd,
docs/config.md
+54
@@ -176,6 +176,11 @@ does (e.g, `"1d2h4m40.01s"`).
176
- [`Mounts.IPFS`](#mountsipfs)
177
- [`Mounts.IPNS`](#mountsipns)
178
- [`Mounts.FuseAllowOther`](#mountsfuseallowother)
179
+- [`Pinning`](#pinning)
180
+ - [`Pinning.RemoteServices`](#pinningremoteservices)
181
+ - [`Pinning.RemoteServices.API`](#pinningremoteservices-api)
182
+ - [`Pinning.RemoteServices.API.Endpoint`](#pinningremoteservices-apiendpoint)
183
+ - [`Pinning.RemoteServices.API.Key`](#pinningremoteservices-apikey)
184
- [`Pubsub`](#pubsub)
185
- [`Pubsub.Router`](#pubsubrouter)
186
- [`Pubsub.DisableSigning`](#pubsubdisablesigning)
@@ -813,6 +818,55 @@ Type: `string` (filesystem path)
818
819
Sets the FUSE allow other option on the mountpoint.
820
821
+## `Pinning`
822
+
823
+Pinning configures the options available for pinning content
824
+(i.e. keeping content longer term instead of as temporarily cached storage).
825
+
826
+### `Pinning.RemoteServices`
827
+
828
+`RemoteServices` maps a name for a remote pinning service to its configuration.
829
+
830
+A remote pinning service is a remote service that exposes an API for managing
831
+that service's interest in longer term data storage.
832
+
833
+The exposed API conforms to the specification defined at
834
+https://ipfs.github.io/pinning-services-api-spec/
835
+
836
+#### `Pinning.RemoteServices: API`
837
+
838
+Contains information relevant to utilizing the remote pinning service
839
+
840
+Example:
841
+```json
842
+{
843
+ "Pinning": {
844
+ "RemoteServices": {
845
+ "myPinningService": {
846
+ "API" : {
847
+ "Endpoint" : "https://pinningservice.tld:1234/my/api/path",
848
+ "Key" : "someOpaqueKey"
849
+ }
850
+ }
851
+ }
852
+ }
853
+}
854
+```
855
+
856
+##### `Pinning.RemoteServices: API.Endpoint`
857
+
858
+The HTTP(S) endpoint through which to access the pinning service
859
+
860
+Example: "https://pinningservice.tld:1234/my/api/path"
861
+
862
+Type: `string`
863
+
864
+##### `Pinning.RemoteServices: API.Key`
865
+
866
+The key through which access to the pinning service is granted
867
+
868
+Type: `string`
869
+
870
## `Pubsub`
871
872
Pubsub configures the `ipfs pubsub` subsystem. To use, it must be enabled by
go.mod
+4
-2
@@ -30,8 +30,8 @@ require (
30
github.com/ipfs/go-graphsync v0.5.1
31
github.com/ipfs/go-ipfs-blockstore v0.1.4
32
github.com/ipfs/go-ipfs-chunker v0.0.5
33
- github.com/ipfs/go-ipfs-cmds v0.4.0
34
- github.com/ipfs/go-ipfs-config v0.9.0
33
+ github.com/ipfs/go-ipfs-cmds v0.5.0
34
+ github.com/ipfs/go-ipfs-config v0.11.0
35
github.com/ipfs/go-ipfs-ds-help v0.1.1
36
github.com/ipfs/go-ipfs-exchange-interface v0.0.1
37
github.com/ipfs/go-ipfs-exchange-offline v0.0.1
@@ -51,6 +51,7 @@ require (
51
github.com/ipfs/go-metrics-prometheus v0.0.2
52
github.com/ipfs/go-mfs v0.1.2
53
github.com/ipfs/go-path v0.0.8
54
+ github.com/ipfs/go-pinning-service-http-client v0.1.0
55
github.com/ipfs/go-unixfs v0.2.4
56
github.com/ipfs/go-verifcid v0.0.1
57
github.com/ipfs/interface-go-ipfs-core v0.4.0
@@ -104,6 +105,7 @@ require (
105
go.uber.org/zap v1.16.0
106
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a
107
golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
108
+ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208
109
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1
110
)
111
go.sum
+9
-42
@@ -35,7 +35,6 @@ github.com/Kubuxu/go-os-helper v0.0.1 h1:EJiD2VUQyh5A9hWJLmc6iWg6yIcJ7jpBcwC8GMG
35
github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y=
36
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
37
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
38
-github.com/Stebalien/go-bitfield v0.0.0-20180330043415-076a62f9ce6e/go.mod h1:3oM7gXIttpYDAJXpVNnSCiUMYBLIZ6cb1t+Ip982MRo=
38
github.com/Stebalien/go-bitfield v0.0.1 h1:X3kbSSPUaJK60wV2hjOPZwmpljr6VGCqdq4cBLhbQBo=
39
github.com/Stebalien/go-bitfield v0.0.1/go.mod h1:GNjFpasyUVkHMsfEOk8EFLJ9syQ6SI+XWrX9Wf2XH0s=
40
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
@@ -142,7 +141,6 @@ github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5 h1:BBso6MBKW
141
github.com/facebookgo/atomicfile v0.0.0-20151019160806-2de1f203e7d5/go.mod h1:JpoxHjuQauoxiFMl1ie8Xc/7TfLuMZ5eOCONd1sUBHg=
142
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
143
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
145
-github.com/fd/go-nat v1.0.0/go.mod h1:BTBu/CKvMmOMUPkKVef1pngt2WFH/lg7E6yQnulfp6E=
144
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
145
github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6 h1:u/UEqS66A5ckRmS4yNpjmVH56sVtS/RfclBAYocb4as=
146
github.com/flynn/noise v0.0.0-20180327030543-2492fe189ae6/go.mod h1:1i71OnUq3iUe1ma7Lr6yG6/rjvM3emb6yoL7xLFzcVQ=
@@ -253,7 +251,6 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:Fecb
251
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
252
github.com/gxed/hashland/keccakpg v0.0.1/go.mod h1:kRzw3HkwxFU1mpmPP8v1WyQzwdGfmKFJ6tItnhQ67kU=
253
github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmvhST0bie/0lS48=
256
-github.com/gxed/pubsub v0.0.0-20180201040156-26ebdf44f824/go.mod h1:OiEWyHgK+CWrmOlVquHaIK1vhpUJydC9m0Je6mhaiNE=
254
github.com/hannahhoward/cbor-gen-for v0.0.0-20200817222906-ea96cece81f1/go.mod h1:jvfsLIxk0fY/2BKSQ1xf2406AKA5dwMmKKv0ADcOfN8=
255
github.com/hannahhoward/go-pubsub v0.0.0-20200423002714-8d62886cc36e h1:3YKHER4nmd7b5qy5t0GWDTwSn4OyRgfAXSmo6VnryBY=
256
github.com/hannahhoward/go-pubsub v0.0.0-20200423002714-8d62886cc36e/go.mod h1:I8h3MITA53gN9OnWGCgaMa0JWVRdXthWw4M3CPM54OY=
@@ -268,7 +265,6 @@ github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG
265
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
266
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
267
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
271
-github.com/huin/goupnp v0.0.0-20180415215157-1395d1447324/go.mod h1:MZ2ZmwcBpvOoJ22IJsc7va19ZwoheaBk43rKg12SKag=
268
github.com/huin/goupnp v1.0.0 h1:wg75sLpL6DZqwHQN6E1Cfk6mtfzS45z8OV+ic+DtHRo=
269
github.com/huin/goupnp v1.0.0/go.mod h1:n9v9KO1tAxYH82qOn+UTIFQDmx5n1Zxd/ClZDMX7Bnc=
270
github.com/huin/goutil v0.0.0-20170803182201-1ca381bf3150/go.mod h1:PpLOETDnJ0o3iZrZfqZzyLl6l7F3c6L1oWn7OICBi6o=
@@ -277,7 +273,6 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
273
github.com/ipfs/bbloom v0.0.1/go.mod h1:oqo8CVWsJFMOZqTglBG4wydCE4IQA/G2/SEofB0rjUI=
274
github.com/ipfs/bbloom v0.0.4 h1:Gi+8EGJ2y5qiD5FbsbpX/TMNcJw8gSqr7eyjHa4Fhvs=
275
github.com/ipfs/bbloom v0.0.4/go.mod h1:cS9YprKXpoZ9lT0n/Mw/a6/aFV6DTjTLYHeA+gyqMG0=
280
-github.com/ipfs/go-bitswap v0.0.3/go.mod h1:jadAZYsP/tcRMl47ZhFxhaNuDQoXawT8iHMg+iFoQbg=
276
github.com/ipfs/go-bitswap v0.0.9/go.mod h1:kAPf5qgn2W2DrgAcscZ3HrM9qh4pH+X8Fkk3UPrwvis=
277
github.com/ipfs/go-bitswap v0.1.0/go.mod h1:FFJEf18E9izuCqUtHxbWEvq+reg7o4CW5wSAE1wsxj0=
278
github.com/ipfs/go-bitswap v0.1.2/go.mod h1:qxSWS4NXGs7jQ6zQvoPY3+NmOfHHG47mhkiLzBpJQIs=
@@ -288,7 +283,6 @@ github.com/ipfs/go-bitswap v0.3.3/go.mod h1:AyWWfN3moBzQX0banEtfKOfbXb3ZeoOeXnZG
283
github.com/ipfs/go-block-format v0.0.1/go.mod h1:DK/YYcsSUIVAFNwo/KZCdIIbpN0ROH/baNLgayt4pFc=
284
github.com/ipfs/go-block-format v0.0.2 h1:qPDvcP19izTjU8rgo6p7gTXZlkMkF5bz5G3fqIsSCPE=
285
github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY=
291
-github.com/ipfs/go-blockservice v0.0.3/go.mod h1:/NNihwTi6V2Yr6g8wBI+BSwPuURpBRMtYNGrlxZ8KuI=
286
github.com/ipfs/go-blockservice v0.0.7/go.mod h1:EOfb9k/Y878ZTRY/CH0x5+ATtaipfbRhbvNSdgc/7So=
287
github.com/ipfs/go-blockservice v0.1.0/go.mod h1:hzmMScl1kXHg3M2BjTymbVPjv627N7sYcvYaKbop39M=
288
github.com/ipfs/go-blockservice v0.1.1/go.mod h1:t+411r7psEUhLueM8C7aPA7cxCclv4O3VsUVxt9kz2I=
@@ -368,10 +362,10 @@ github.com/ipfs/go-ipfs-chunker v0.0.1 h1:cHUUxKFQ99pozdahi+uSC/3Y6HeRpi9oTeUHbE
362
github.com/ipfs/go-ipfs-chunker v0.0.1/go.mod h1:tWewYK0we3+rMbOh7pPFGDyypCtvGcBFymgY4rSDLAw=
363
github.com/ipfs/go-ipfs-chunker v0.0.5 h1:ojCf7HV/m+uS2vhUGWcogIIxiO5ubl5O57Q7NapWLY8=
364
github.com/ipfs/go-ipfs-chunker v0.0.5/go.mod h1:jhgdF8vxRHycr00k13FM8Y0E+6BoalYeobXmUyTreP8=
371
-github.com/ipfs/go-ipfs-cmds v0.4.0 h1:xUavIxA9Ts8U6PAHmQBvDGMlGfUrQ13Rymd+5t8LIF4=
372
-github.com/ipfs/go-ipfs-cmds v0.4.0/go.mod h1:ZgYiWVnCk43ChwoH8hAmI1IRbuVtq3GSTHwtRB/Kqhk=
373
-github.com/ipfs/go-ipfs-config v0.9.0 h1:qTXJ9CyOyQv1LFJUMysxz8fi6RxxnP9QqcmiobuANvw=
374
-github.com/ipfs/go-ipfs-config v0.9.0/go.mod h1:GQUxqb0NfkZmEU92PxqqqLVVFTLpoGGUlBaTyDaAqrE=
365
+github.com/ipfs/go-ipfs-cmds v0.5.0 h1:T1ZT6Qu3IUCp6FgU2IzVtvGLaexEWo9q13+S5ic+Q5Y=
366
+github.com/ipfs/go-ipfs-cmds v0.5.0/go.mod h1:ZgYiWVnCk43ChwoH8hAmI1IRbuVtq3GSTHwtRB/Kqhk=
367
+github.com/ipfs/go-ipfs-config v0.11.0 h1:w4t2pz415Gtg6MTUKAq06C7ezC59/Us+k3+n1Tje+wg=
368
+github.com/ipfs/go-ipfs-config v0.11.0/go.mod h1:Ei/FLgHGTdPyqCPK0oPCwGTe8VSnsjJjx7HZqUb6Ry0=
369
github.com/ipfs/go-ipfs-delay v0.0.0-20181109222059-70721b86a9a8/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw=
370
github.com/ipfs/go-ipfs-delay v0.0.1 h1:r/UXYyRcddO6thwOnhiznIAiSvxMECGgtv35Xs1IeRQ=
371
github.com/ipfs/go-ipfs-delay v0.0.1/go.mod h1:8SP1YXK1M1kXuc4KJZINY3TQQ03J2rwBG9QfXmbRPrw=
@@ -383,11 +377,9 @@ github.com/ipfs/go-ipfs-exchange-interface v0.0.1 h1:LJXIo9W7CAmugqI+uofioIpRb6r
377
github.com/ipfs/go-ipfs-exchange-interface v0.0.1/go.mod h1:c8MwfHjtQjPoDyiy9cFquVtVHkO9b9Ob3FG91qJnWCM=
378
github.com/ipfs/go-ipfs-exchange-offline v0.0.1 h1:P56jYKZF7lDDOLx5SotVh5KFxoY6C81I1NSHW1FxGew=
379
github.com/ipfs/go-ipfs-exchange-offline v0.0.1/go.mod h1:WhHSFCVYX36H/anEKQboAzpUws3x7UeEGkzQc3iNkM0=
386
-github.com/ipfs/go-ipfs-files v0.0.2/go.mod h1:INEFm0LL2LWXBhNJ2PMIIb2w45hpXgPjNoE7yA8Y1d4=
380
github.com/ipfs/go-ipfs-files v0.0.3/go.mod h1:INEFm0LL2LWXBhNJ2PMIIb2w45hpXgPjNoE7yA8Y1d4=
381
github.com/ipfs/go-ipfs-files v0.0.8 h1:8o0oFJkJ8UkO/ABl8T6ac6tKF3+NIpj67aAB6ZpusRg=
382
github.com/ipfs/go-ipfs-files v0.0.8/go.mod h1:wiN/jSG8FKyk7N0WyctKSvq3ljIa2NNTiZB55kpTdOs=
390
-github.com/ipfs/go-ipfs-flags v0.0.1/go.mod h1:RnXBb9WV53GSfTrSDVK61NLTFKvWc60n+K9EgCDh+rA=
383
github.com/ipfs/go-ipfs-pinner v0.1.0 h1:rjSrbUDYd1YYHZ5dOgu+QEOuLcU0m/2a/brcxC/ReeU=
384
github.com/ipfs/go-ipfs-pinner v0.1.0/go.mod h1:EzyyaWCWeZJ/he9cDBH6QrEkSuRqTRWMmCoyNkylTTg=
385
github.com/ipfs/go-ipfs-posinfo v0.0.1 h1:Esoxj+1JgSjX0+ylc0hUmJCOv6V2vFoZiETLR6OtpRs=
@@ -404,7 +396,6 @@ github.com/ipfs/go-ipfs-util v0.0.1 h1:Wz9bL2wB2YBJqggkA4dD7oSmqB4cAnpNbGrlHJulv
396
github.com/ipfs/go-ipfs-util v0.0.1/go.mod h1:spsl5z8KUnrve+73pOhSVZND1SIxPW5RyBCNzQxlJBc=
397
github.com/ipfs/go-ipfs-util v0.0.2 h1:59Sswnk1MFaiq+VcaknX7aYEyGyGDAA73ilhEK2POp8=
398
github.com/ipfs/go-ipfs-util v0.0.2/go.mod h1:CbPtkWJzjLdEcezDns2XYaehFVNXG9zrdrtMecczcsQ=
407
-github.com/ipfs/go-ipld-cbor v0.0.1/go.mod h1:RXHr8s4k0NE0TKhnrxqZC9M888QfsBN9rhS5NjfKzY8=
399
github.com/ipfs/go-ipld-cbor v0.0.2/go.mod h1:wTBtrQZA3SoFKMVkp6cn6HMRteIB1VsmHA0AQFOn7Nc=
400
github.com/ipfs/go-ipld-cbor v0.0.3 h1:ENsxvybwkmke7Z/QJOmeJfoguj6GH3Y0YOaGrfy9Q0I=
401
github.com/ipfs/go-ipld-cbor v0.0.3/go.mod h1:wTBtrQZA3SoFKMVkp6cn6HMRteIB1VsmHA0AQFOn7Nc=
@@ -441,7 +432,6 @@ github.com/ipfs/go-log/v2 v2.0.5 h1:fL4YI+1g5V/b1Yxr1qAiXTMg1H8z9vx/VmJxBuQMHvU=
432
github.com/ipfs/go-log/v2 v2.0.5/go.mod h1:eZs4Xt4ZUJQFM3DlanGhy7TkwwawCZcSByscwkWG+dw=
433
github.com/ipfs/go-log/v2 v2.1.1 h1:G4TtqN+V9y9HY9TA6BwbCVyyBZ2B9MbCjR2MtGx8FR0=
434
github.com/ipfs/go-log/v2 v2.1.1/go.mod h1:2v2nsGfZsvvAJz13SyFzf9ObaqwHiHxsPLEHntrv9KM=
444
-github.com/ipfs/go-merkledag v0.0.3/go.mod h1:Oc5kIXLHokkE1hWGMBHw+oxehkAaTOqtEb7Zbh6BhLA=
435
github.com/ipfs/go-merkledag v0.0.6/go.mod h1:QYPdnlvkOg7GnQRofu9XZimC5ZW5Wi3bKys/4GQQfto=
436
github.com/ipfs/go-merkledag v0.1.0/go.mod h1:SQiXrtSts3KGNmgOzMICy5c0POOpUNQLvB3ClKnBAlk=
437
github.com/ipfs/go-merkledag v0.2.3 h1:aMdkK9G1hEeNvn3VXfiEMLY0iJnbiQQUHnM0HFJREsE=
@@ -458,7 +448,6 @@ github.com/ipfs/go-metrics-prometheus v0.0.2 h1:9i2iljLg12S78OhC6UAiXi176xvQGiZa
448
github.com/ipfs/go-metrics-prometheus v0.0.2/go.mod h1:ELLU99AQQNi+zX6GCGm2lAgnzdSH3u5UVlCdqSXnEks=
449
github.com/ipfs/go-mfs v0.1.2 h1:DlelNSmH+yz/Riy0RjPKlooPg0KML4lXGdLw7uZkfAg=
450
github.com/ipfs/go-mfs v0.1.2/go.mod h1:T1QBiZPEpkPLzDqEJLNnbK55BVKVlNi2a+gVm4diFo0=
461
-github.com/ipfs/go-path v0.0.3/go.mod h1:zIRQUez3LuQIU25zFjC2hpBTHimWx7VK5bjZgRLbbdo=
451
github.com/ipfs/go-path v0.0.7 h1:H06hKMquQ0aYtHiHryOMLpQC1qC3QwXwkahcEVD51Ho=
452
github.com/ipfs/go-path v0.0.7/go.mod h1:6KTKmeRnBXgqrTvzFrPV3CamxcgvXX/4z79tfAd2Sno=
453
github.com/ipfs/go-path v0.0.8 h1:R0k6t9x/pa+g8qzl5apQIPurJFozXhopks3iw3MX+jU=
@@ -468,14 +457,13 @@ github.com/ipfs/go-peertaskqueue v0.1.0/go.mod h1:Jmk3IyCcfl1W3jTW3YpghSwSEC6IJ3
457
github.com/ipfs/go-peertaskqueue v0.1.1/go.mod h1:Jmk3IyCcfl1W3jTW3YpghSwSEC6IJ3Vzz/jUmWw8Z0U=
458
github.com/ipfs/go-peertaskqueue v0.2.0 h1:2cSr7exUGKYyDeUyQ7P/nHPs9P7Ht/B+ROrpN1EJOjc=
459
github.com/ipfs/go-peertaskqueue v0.2.0/go.mod h1:5/eNrBEbtSKWCG+kQK8K8fGNixoYUnr+P7jivavs9lY=
471
-github.com/ipfs/go-unixfs v0.0.4/go.mod h1:eIo/p9ADu/MFOuyxzwU+Th8D6xoxU//r590vUpWyfz8=
460
+github.com/ipfs/go-pinning-service-http-client v0.1.0 h1:Au0P4NglL5JfzhNSZHlZ1qra+IcJyO3RWMd9EYCwqSY=
461
+github.com/ipfs/go-pinning-service-http-client v0.1.0/go.mod h1:tcCKmlkWWH9JUUkKs8CrOZBanacNc1dmKLfjlyXAMu4=
462
github.com/ipfs/go-unixfs v0.1.0/go.mod h1:lysk5ELhOso8+Fed9U1QTGey2ocsfaZ18h0NCO2Fj9s=
463
github.com/ipfs/go-unixfs v0.2.4 h1:6NwppOXefWIyysZ4LR/qUBPvXd5//8J3jiMdvpbw6Lo=
464
github.com/ipfs/go-unixfs v0.2.4/go.mod h1:SUdisfUjNoSDzzhGVxvCL9QO/nKdwXdr+gbMUdqcbYw=
465
github.com/ipfs/go-verifcid v0.0.1 h1:m2HI7zIuR5TFyQ1b79Da5N9dnnCP1vcu2QqawmWlK2E=
466
github.com/ipfs/go-verifcid v0.0.1/go.mod h1:5Hrva5KBeIog4A+UpqlaIU+DEstipcJYQQZc0g37pY0=
477
-github.com/ipfs/interface-go-ipfs-core v0.3.0 h1:oZdLLfh256gPGcYPURjivj/lv296GIcr8mUqZUnXOEI=
478
-github.com/ipfs/interface-go-ipfs-core v0.3.0/go.mod h1:Tihp8zxGpUeE3Tokr94L6zWZZdkRQvG5TL6i9MuNE+s=
467
github.com/ipfs/interface-go-ipfs-core v0.4.0 h1:+mUiamyHIwedqP8ZgbCIwpy40oX7QcXUbo4CZOeJVJg=
468
github.com/ipfs/interface-go-ipfs-core v0.4.0/go.mod h1:UJBcU6iNennuI05amq3FQ7g0JHUkibHFAfhfUIy927o=
469
github.com/ipld/go-car v0.1.1-0.20201015032735-ff6ccdc46acc h1:BdI33Q56hLWG9Ef0WbQ7z+dwmbRYhTb45SMjw0RudbQ=
@@ -486,7 +474,6 @@ github.com/ipld/go-ipld-prime v0.5.1-0.20201021195245-109253e8a018/go.mod h1:0xE
474
github.com/ipld/go-ipld-prime-proto v0.0.0-20200922192210-9a2bfd4440a6/go.mod h1:3pHYooM9Ea65jewRwrb2u5uHZCNkNTe9ABsVB+SrkH0=
475
github.com/ipld/go-ipld-prime-proto v0.1.0 h1:j7gjqrfwbT4+gXpHwEx5iMssma3mnctC7YaCimsFP70=
476
github.com/ipld/go-ipld-prime-proto v0.1.0/go.mod h1:11zp8f3sHVgIqtb/c9Kr5ZGqpnCLF1IVTNOez9TopzE=
489
-github.com/jackpal/gateway v1.0.4/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA=
477
github.com/jackpal/gateway v1.0.5 h1:qzXWUJfuMdlLMtt0a3Dgt+xkWQiA5itDEITVJtuSwMc=
478
github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQetPfnjA=
479
github.com/jackpal/go-nat-pmp v1.0.1 h1:i0LektDkO1QlrTm/cSuP+PyBCDnYvjPLGl4LdWEMiaA=
@@ -551,7 +538,6 @@ github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoR
538
github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c=
539
github.com/libp2p/go-cidranger v1.1.0/go.mod h1:KWZTfSr+r9qEo9OkI9/SIEeAtw+NNoU0dXIXt15Okic=
540
github.com/libp2p/go-conn-security v0.0.1/go.mod h1:bGmu51N0KU9IEjX7kl2PQjgZa40JQWnayTvNMgD/vyk=
554
-github.com/libp2p/go-conn-security-multistream v0.0.1/go.mod h1:nc9vud7inQ+d6SO0I/6dSWrdMnHnzZNHeyUQqrAJulE=
541
github.com/libp2p/go-conn-security-multistream v0.0.2/go.mod h1:nc9vud7inQ+d6SO0I/6dSWrdMnHnzZNHeyUQqrAJulE=
542
github.com/libp2p/go-conn-security-multistream v0.1.0 h1:aqGmto+ttL/uJgX0JtQI0tD21CIEy5eYd1Hlp0juHY0=
543
github.com/libp2p/go-conn-security-multistream v0.1.0/go.mod h1:aw6eD7LOsHEX7+2hJkDxw1MteijaVcI+/eP2/x3J1xc=
@@ -567,7 +553,6 @@ github.com/libp2p/go-flow-metrics v0.0.2 h1:U5TvqfoyR6GVRM+bC15Ux1ltar1kbj6Zw6xO
553
github.com/libp2p/go-flow-metrics v0.0.2/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs=
554
github.com/libp2p/go-flow-metrics v0.0.3 h1:8tAs/hSdNvUiLgtlSy3mxwxWP4I9y/jlkPFT7epKdeM=
555
github.com/libp2p/go-flow-metrics v0.0.3/go.mod h1:HeoSNUrOJVK1jEpDqVEiUOIXqhbnS27omG0uWU5slZs=
570
-github.com/libp2p/go-libp2p v0.0.2/go.mod h1:Qu8bWqFXiocPloabFGUcVG4kk94fLvfC8mWTDdFC9wE=
556
github.com/libp2p/go-libp2p v0.0.30/go.mod h1:XWT8FGHlhptAv1+3V/+J5mEpzyui/5bvFsNuWYs611A=
557
github.com/libp2p/go-libp2p v0.1.0/go.mod h1:6D/2OBauqLUoqcADOJpn9WbKqvaM07tDw68qHM0BxUM=
558
github.com/libp2p/go-libp2p v0.1.1/go.mod h1:I00BRo1UuUSdpuc8Q2mN7yDF/oTUTRAX6JWpTiK9Rp8=
@@ -582,7 +567,6 @@ github.com/libp2p/go-libp2p v0.12.0 h1:+xai9RQnQ9l5elFOKvp5wRyjyWisSwEx+6nU2+onp
567
github.com/libp2p/go-libp2p v0.12.0/go.mod h1:FpHZrfC1q7nA8jitvdjKBDF31hguaC676g/nT9PgQM0=
568
github.com/libp2p/go-libp2p-asn-util v0.0.0-20200825225859-85005c6cf052 h1:BM7aaOF7RpmNn9+9g6uTjGJ0cTzWr5j9i9IKeun2M8U=
569
github.com/libp2p/go-libp2p-asn-util v0.0.0-20200825225859-85005c6cf052/go.mod h1:nRMRTab+kZuk0LnKZpxhOVH/ndsdr2Nr//Zltc/vwgo=
585
-github.com/libp2p/go-libp2p-autonat v0.0.2/go.mod h1:fs71q5Xk+pdnKU014o2iq1RhMs9/PMaG5zXRFNnIIT4=
570
github.com/libp2p/go-libp2p-autonat v0.0.6/go.mod h1:uZneLdOkZHro35xIhpbtTzLlgYturpu4J5+0cZK3MqE=
571
github.com/libp2p/go-libp2p-autonat v0.1.0 h1:aCWAu43Ri4nU0ZPO7NyLzUvvfqd0nE3dX0R/ZGYVgOU=
572
github.com/libp2p/go-libp2p-autonat v0.1.0/go.mod h1:1tLf2yXxiE/oKGtDwPYWTSYG3PtvYlJmg7NeVtPRqH8=
@@ -602,7 +586,6 @@ github.com/libp2p/go-libp2p-blankhost v0.1.4 h1:I96SWjR4rK9irDHcHq3XHN6hawCRTPUA
586
github.com/libp2p/go-libp2p-blankhost v0.1.4/go.mod h1:oJF0saYsAXQCSfDq254GMNmLNz6ZTHTOvtF4ZydUvwU=
587
github.com/libp2p/go-libp2p-blankhost v0.2.0 h1:3EsGAi0CBGcZ33GwRuXEYJLLPoVWyXJ1bcJzAJjINkk=
588
github.com/libp2p/go-libp2p-blankhost v0.2.0/go.mod h1:eduNKXGTioTuQAUcZ5epXi9vMl+t4d8ugUBRQ4SqaNQ=
605
-github.com/libp2p/go-libp2p-circuit v0.0.1/go.mod h1:Dqm0s/BiV63j8EEAs8hr1H5HudqvCAeXxDyic59lCwE=
589
github.com/libp2p/go-libp2p-circuit v0.0.9/go.mod h1:uU+IBvEQzCu953/ps7bYzC/D/R0Ho2A9LfKVVCatlqU=
590
github.com/libp2p/go-libp2p-circuit v0.1.0/go.mod h1:Ahq4cY3V9VJcHcn1SBXjr78AbFkZeIRmfunbA7pmFh8=
591
github.com/libp2p/go-libp2p-circuit v0.1.4 h1:Phzbmrg3BkVzbqd4ZZ149JxCuUWu2wZcXf/Kr6hZJj8=
@@ -650,7 +633,6 @@ github.com/libp2p/go-libp2p-crypto v0.0.1/go.mod h1:yJkNyDmO341d5wwXxDUGO0LykUVT
633
github.com/libp2p/go-libp2p-crypto v0.0.2/go.mod h1:eETI5OUfBnvARGOHrJz2eWNyTUxEGZnBxMcbUjfIj4I=
634
github.com/libp2p/go-libp2p-crypto v0.1.0 h1:k9MFy+o2zGDNGsaoZl0MA3iZ75qXxr9OOoAZF+sD5OQ=
635
github.com/libp2p/go-libp2p-crypto v0.1.0/go.mod h1:sPUokVISZiy+nNuTTH/TY+leRSxnFj/2GLjtOTW90hI=
653
-github.com/libp2p/go-libp2p-discovery v0.0.1/go.mod h1:ZkkF9xIFRLA1xCc7bstYFkd80gBGK8Fc1JqGoU2i+zI=
636
github.com/libp2p/go-libp2p-discovery v0.0.5/go.mod h1:YtF20GUxjgoKZ4zmXj8j3Nb2TUSBHFlOCetzYdbZL5I=
637
github.com/libp2p/go-libp2p-discovery v0.1.0 h1:j+R6cokKcGbnZLf4kcNwpx6mDEUPF3N6SrqMymQhmvs=
638
github.com/libp2p/go-libp2p-discovery v0.1.0/go.mod h1:4F/x+aldVHjHDHuX85x1zWoFTGElt8HnoDzwkFZm29g=
@@ -688,7 +670,6 @@ github.com/libp2p/go-libp2p-mplex v0.2.3 h1:2zijwaJvpdesST2MXpI5w9wWFRgYtMcpRX7r
670
github.com/libp2p/go-libp2p-mplex v0.2.3/go.mod h1:CK3p2+9qH9x+7ER/gWWDYJ3QW5ZxWDkm+dVvjfuG3ek=
671
github.com/libp2p/go-libp2p-mplex v0.3.0 h1:CZyqqKP0BSGQyPLvpRQougbfXaaaJZdGgzhCpJNuNSk=
672
github.com/libp2p/go-libp2p-mplex v0.3.0/go.mod h1:l9QWxRbbb5/hQMECEb908GbS9Sm2UAR2KFZKUJEynEs=
691
-github.com/libp2p/go-libp2p-nat v0.0.2/go.mod h1:QrjXQSD5Dj4IJOdEcjHRkWTSomyxRo6HnUkf/TfQpLQ=
673
github.com/libp2p/go-libp2p-nat v0.0.4 h1:+KXK324yaY701On8a0aGjTnw8467kW3ExKcqW2wwmyw=
674
github.com/libp2p/go-libp2p-nat v0.0.4/go.mod h1:N9Js/zVtAXqaeT99cXgTV9e75KpnWCvVOiGzlcHmBbY=
675
github.com/libp2p/go-libp2p-nat v0.0.5 h1:/mH8pXFVKleflDL1YwqMg27W9GD8kjEx7NY0P6eGc98=
@@ -743,7 +724,6 @@ github.com/libp2p/go-libp2p-record v0.1.3/go.mod h1:yNUff/adKIfPnYQXgp6FQmNu3gLJ
724
github.com/libp2p/go-libp2p-routing v0.0.1/go.mod h1:N51q3yTr4Zdr7V8Jt2JIktVU+3xBBylx1MZeVA6t1Ys=
725
github.com/libp2p/go-libp2p-routing-helpers v0.2.3 h1:xY61alxJ6PurSi+MXbywZpelvuU4U4p/gPTxjqCqTzY=
726
github.com/libp2p/go-libp2p-routing-helpers v0.2.3/go.mod h1:795bh+9YeoFl99rMASoiVgHdi5bjack0N1+AFAdbvBw=
746
-github.com/libp2p/go-libp2p-secio v0.0.1/go.mod h1:IdG6iQybdcYmbTzxp4J5dwtUEDTOvZrT0opIDVNPrJs=
727
github.com/libp2p/go-libp2p-secio v0.0.3/go.mod h1:hS7HQ00MgLhRO/Wyu1bTX6ctJKhVpm+j2/S2A5UqYb0=
728
github.com/libp2p/go-libp2p-secio v0.1.0/go.mod h1:tMJo2w7h3+wN4pgU2LSYeiKPrfqBgkOsdiKK77hE7c8=
729
github.com/libp2p/go-libp2p-secio v0.2.0 h1:ywzZBsWEEz2KNTn5RtzauEDq5RFEefPsttXYwAWqHng=
@@ -752,7 +732,6 @@ github.com/libp2p/go-libp2p-secio v0.2.1 h1:eNWbJTdyPA7NxhP7J3c5lT97DC5d+u+Ildkg
732
github.com/libp2p/go-libp2p-secio v0.2.1/go.mod h1:cWtZpILJqkqrSkiYcDBh5lA3wbT2Q+hz3rJQq3iftD8=
733
github.com/libp2p/go-libp2p-secio v0.2.2 h1:rLLPvShPQAcY6eNurKNZq3eZjPWfU9kXF2eI9jIYdrg=
734
github.com/libp2p/go-libp2p-secio v0.2.2/go.mod h1:wP3bS+m5AUnFA+OFO7Er03uO1mncHG0uVwGrwvjYlNY=
755
-github.com/libp2p/go-libp2p-swarm v0.0.1/go.mod h1:mh+KZxkbd3lQnveQ3j2q60BM1Cw2mX36XXQqwfPOShs=
735
github.com/libp2p/go-libp2p-swarm v0.0.6/go.mod h1:s5GZvzg9xXe8sbeESuFpjt8CJPTCa8mhEusweJqyFy8=
736
github.com/libp2p/go-libp2p-swarm v0.1.0/go.mod h1:wQVsCdjsuZoc730CgOvh5ox6K8evllckjebkdiY5ta4=
737
github.com/libp2p/go-libp2p-swarm v0.2.2 h1:T4hUpgEs2r371PweU3DuH7EOmBIdTBCwWs+FLcgx3bQ=
@@ -778,9 +757,7 @@ github.com/libp2p/go-libp2p-testing v0.3.0/go.mod h1:efZkql4UZ7OVsEfaxNHZPzIehts
757
github.com/libp2p/go-libp2p-tls v0.1.3 h1:twKMhMu44jQO+HgQK9X8NHO5HkeJu2QbhLzLJpa8oNM=
758
github.com/libp2p/go-libp2p-tls v0.1.3/go.mod h1:wZfuewxOndz5RTnCAxFliGjvYSDA40sKitV4c50uI1M=
759
github.com/libp2p/go-libp2p-transport v0.0.1/go.mod h1:UzbUs9X+PHOSw7S3ZmeOxfnwaQY5vGDzZmKPod3N3tk=
781
-github.com/libp2p/go-libp2p-transport v0.0.4/go.mod h1:StoY3sx6IqsP6XKoabsPnHCwqKXWUMWU7Rfcsubee/A=
760
github.com/libp2p/go-libp2p-transport v0.0.5/go.mod h1:StoY3sx6IqsP6XKoabsPnHCwqKXWUMWU7Rfcsubee/A=
783
-github.com/libp2p/go-libp2p-transport-upgrader v0.0.1/go.mod h1:NJpUAgQab/8K6K0m+JmZCe5RUXG10UMEx4kWe9Ipj5c=
761
github.com/libp2p/go-libp2p-transport-upgrader v0.0.4/go.mod h1:RGq+tupk+oj7PzL2kn/m1w6YXxcIAYJYeI90h6BGgUc=
762
github.com/libp2p/go-libp2p-transport-upgrader v0.1.1 h1:PZMS9lhjK9VytzMCW3tWHAXtKXmlURSc3ZdvwEcKCzw=
763
github.com/libp2p/go-libp2p-transport-upgrader v0.1.1/go.mod h1:IEtA6or8JUbsV07qPW4r01GnTenLW4oi3lOPbUMGJJA=
@@ -808,7 +785,6 @@ github.com/libp2p/go-maddr-filter v0.0.4/go.mod h1:6eT12kSQMA9x2pvFQa+xesMKUBlj9
785
github.com/libp2p/go-maddr-filter v0.0.5 h1:CW3AgbMO6vUvT4kf87y4N+0P8KUl2aqLYhrGyDUbLSg=
786
github.com/libp2p/go-maddr-filter v0.0.5/go.mod h1:Jk+36PMfIqCJhAnaASRH83bdAvfDRp/w6ENFaC9bG+M=
787
github.com/libp2p/go-maddr-filter v0.1.0/go.mod h1:VzZhTXkMucEGGEOSKddrwGiOv0tUhgnKqNEmIAz/bPU=
811
-github.com/libp2p/go-mplex v0.0.1/go.mod h1:pK5yMLmOoBR1pNCqDlA2GQrdAVTMkqFalaTWe7l4Yd0=
788
github.com/libp2p/go-mplex v0.0.3/go.mod h1:pK5yMLmOoBR1pNCqDlA2GQrdAVTMkqFalaTWe7l4Yd0=
789
github.com/libp2p/go-mplex v0.0.4/go.mod h1:pK5yMLmOoBR1pNCqDlA2GQrdAVTMkqFalaTWe7l4Yd0=
790
github.com/libp2p/go-mplex v0.1.0 h1:/nBTy5+1yRyY82YaO6HXQRnO5IAGsXTjEJaR3LdTPc0=
@@ -819,7 +795,6 @@ github.com/libp2p/go-mplex v0.1.2 h1:qOg1s+WdGLlpkrczDqmhYzyk3vCfsQ8+RxRTQjOZWwI
795
github.com/libp2p/go-mplex v0.1.2/go.mod h1:Xgz2RDCi3co0LeZfgjm4OgUF15+sVR8SRcu3SFXI1lk=
796
github.com/libp2p/go-mplex v0.2.0 h1:Ov/D+8oBlbRkjBs1R1Iua8hJ8cUfbdiW8EOdZuxcgaI=
797
github.com/libp2p/go-mplex v0.2.0/go.mod h1:0Oy/A9PQlwBytDRp4wSkFnzHYDKcpLot35JQ6msjvYQ=
822
-github.com/libp2p/go-msgio v0.0.1/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ=
798
github.com/libp2p/go-msgio v0.0.2/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ=
799
github.com/libp2p/go-msgio v0.0.3/go.mod h1:63lBBgOTDKQL6EWazRMCwXsEeEeK9O2Cd+0+6OOuipQ=
800
github.com/libp2p/go-msgio v0.0.4 h1:agEFehY3zWJFUHK6SEMR7UYmk2z6kC3oeCM7ybLhguA=
@@ -850,7 +825,6 @@ github.com/libp2p/go-reuseport v0.0.1 h1:7PhkfH73VXfPJYKQ6JwS5I/eVcoyYi9IMNGc6FW
825
github.com/libp2p/go-reuseport v0.0.1/go.mod h1:jn6RmB1ufnQwl0Q1f+YxAj8isJgDCQzaaxIFYDhcYEA=
826
github.com/libp2p/go-reuseport v0.0.2 h1:XSG94b1FJfGA01BUrT82imejHQyTxO4jEWqheyCXYvU=
827
github.com/libp2p/go-reuseport v0.0.2/go.mod h1:SPD+5RwGC7rcnzngoYC86GjPzjSywuQyMVAheVBD9nQ=
853
-github.com/libp2p/go-reuseport-transport v0.0.1/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs=
828
github.com/libp2p/go-reuseport-transport v0.0.2 h1:WglMwyXyBu61CMkjCCtnmqNqnjib0GIEjMiHTwR/KN4=
829
github.com/libp2p/go-reuseport-transport v0.0.2/go.mod h1:YkbSDrvjUVDL6b8XqriyA20obEtsW9BLkuOUyQAOCbs=
830
github.com/libp2p/go-reuseport-transport v0.0.3 h1:zzOeXnTooCkRvoH+bSXEfXhn76+LAiwoneM0gnXjF2M=
@@ -868,7 +842,6 @@ github.com/libp2p/go-stream-muxer-multistream v0.2.0 h1:714bRJ4Zy9mdhyTLJ+ZKiROm
842
github.com/libp2p/go-stream-muxer-multistream v0.2.0/go.mod h1:j9eyPol/LLRqT+GPLSxvimPhNph4sfYfMoDPd7HkzIc=
843
github.com/libp2p/go-stream-muxer-multistream v0.3.0 h1:TqnSHPJEIqDEO7h1wZZ0p3DXdvDSiLHQidKKUGZtiOY=
844
github.com/libp2p/go-stream-muxer-multistream v0.3.0/go.mod h1:yDh8abSIzmZtqtOt64gFJUXEryejzNb0lisTt+fAMJA=
871
-github.com/libp2p/go-tcp-transport v0.0.1/go.mod h1:mnjg0o0O5TmXUaUIanYPUqkW4+u6mK0en8rlpA6BBTs=
845
github.com/libp2p/go-tcp-transport v0.0.4/go.mod h1:+E8HvC8ezEVOxIo3V5vCK9l1y/19K427vCzQ+xHKH/o=
846
github.com/libp2p/go-tcp-transport v0.1.0/go.mod h1:oJ8I5VXryj493DEJ7OsBieu8fcg2nHGctwtInJVpipc=
847
github.com/libp2p/go-tcp-transport v0.1.1 h1:yGlqURmqgNA2fvzjSgZNlHcsd/IulAnKM8Ncu+vlqnw=
@@ -879,7 +852,6 @@ github.com/libp2p/go-tcp-transport v0.2.1 h1:ExZiVQV+h+qL16fzCWtd1HSzPsqWottJ8KX
852
github.com/libp2p/go-tcp-transport v0.2.1/go.mod h1:zskiJ70MEfWz2MKxvFB/Pv+tPIB1PpPUrHIWQ8aFw7M=
853
github.com/libp2p/go-testutil v0.0.1/go.mod h1:iAcJc/DKJQanJ5ws2V+u5ywdL2n12X1WbbEG+Jjy69I=
854
github.com/libp2p/go-testutil v0.1.0/go.mod h1:81b2n5HypcVyrCg/MJx4Wgfp/VHojytjVe/gLzZ2Ehc=
882
-github.com/libp2p/go-ws-transport v0.0.1/go.mod h1:p3bKjDWHEgtuKKj+2OdPYs5dAPIjtpQGHF2tJfGz7Ww=
855
github.com/libp2p/go-ws-transport v0.0.5/go.mod h1:Qbl4BxPfXXhhd/o0wcrgoaItHqA9tnZjoFZnxykuaXU=
856
github.com/libp2p/go-ws-transport v0.1.0/go.mod h1:rjw1MG1LU9YDC6gzmwObkPd/Sqwhw7yT74kj3raBFuo=
857
github.com/libp2p/go-ws-transport v0.2.0 h1:MJCw2OrPA9+76YNRvdo1wMnSOxb9Bivj6sVFY1Xrj6w=
@@ -933,7 +905,6 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
905
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
906
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
907
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
936
-github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
908
github.com/miekg/dns v1.1.12 h1:WMhc1ik4LNkTg8U9l3hI1LvxKmIL+f1+WV/SZtCbDDA=
909
github.com/miekg/dns v1.1.12/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
910
github.com/miekg/dns v1.1.28 h1:gQhy5bsJa8zTlVI8lywCTZp1lguor+xevFoYlzeCTQY=
@@ -950,6 +921,7 @@ github.com/minio/sha256-simd v0.1.1 h1:5QHSlgo3nt5yKOJrC7W8w7X+NFl8cMPZm96iu8kKU
921
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
922
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
923
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
924
+github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
925
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
926
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
927
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -1011,7 +983,6 @@ github.com/multiformats/go-multibase v0.0.3 h1:l/B6bJDQjvQ5G52jw4QGSYeOTZoAwIO77
983
github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc=
984
github.com/multiformats/go-multihash v0.0.1/go.mod h1:w/5tugSrLEbWqlcgJabL3oHFKTwfvkofsjW2Qa1ct4U=
985
github.com/multiformats/go-multihash v0.0.5/go.mod h1:lt/HCbqlQwlPBz7lv0sQCdtfcMtlJvakRUn/0Ual8po=
1014
-github.com/multiformats/go-multihash v0.0.7/go.mod h1:XuKXPp8VHcTygube3OWZC+aZrA+H1IhmjoCDtJc7PXM=
986
github.com/multiformats/go-multihash v0.0.8 h1:wrYcW5yxSi3dU07n5jnuS5PrNwyHy0zRHGVoUugWvXg=
987
github.com/multiformats/go-multihash v0.0.8/go.mod h1:YSLudS+Pi8NHE7o6tb3D8vrpKa63epEDmG8nTduyAew=
988
github.com/multiformats/go-multihash v0.0.9 h1:aoijQXYYl7Xtb2pUUP68R+ys1TlnlR3eX6wmozr0Hp4=
@@ -1205,10 +1176,6 @@ github.com/whyrusleeping/go-logging v0.0.1 h1:fwpzlmT0kRC/Fmd0MdmGgJG/CXIZ6gFq46
1176
github.com/whyrusleeping/go-logging v0.0.1/go.mod h1:lDPYj54zutzG1XYfHAhcc7oNXEburHQBn+Iqd4yS4vE=
1177
github.com/whyrusleeping/go-notifier v0.0.0-20170827234753-097c5d47330f h1:M/lL30eFZTKnomXY6huvM6G0+gVquFNf6mxghaWlFUg=
1178
github.com/whyrusleeping/go-notifier v0.0.0-20170827234753-097c5d47330f/go.mod h1:cZNvX9cFybI01GriPRMXDtczuvUhgbcYr9iCGaNlRv8=
1208
-github.com/whyrusleeping/go-smux-multiplex v3.0.16+incompatible/go.mod h1:34LEDbeKFZInPUrAG+bjuJmUXONGdEFW7XL0SpTY1y4=
1209
-github.com/whyrusleeping/go-smux-multistream v2.0.2+incompatible/go.mod h1:dRWHHvc4HDQSHh9gbKEBbUZ+f2Q8iZTPG3UOGYODxSQ=
1210
-github.com/whyrusleeping/go-smux-yamux v2.0.8+incompatible/go.mod h1:6qHUzBXUbB9MXmw3AUdB52L8sEb/hScCqOdW2kj/wuI=
1211
-github.com/whyrusleeping/go-smux-yamux v2.0.9+incompatible/go.mod h1:6qHUzBXUbB9MXmw3AUdB52L8sEb/hScCqOdW2kj/wuI=
1179
github.com/whyrusleeping/go-sysinfo v0.0.0-20190219211824-4a357d4b90b1 h1:ctS9Anw/KozviCCtK6VWMz5kPL9nbQzbQY4yfqlIV4M=
1180
github.com/whyrusleeping/go-sysinfo v0.0.0-20190219211824-4a357d4b90b1/go.mod h1:tKH72zYNt/exx6/5IQO6L9LoQ0rEjd5SbbWaDTs9Zso=
1181
github.com/whyrusleeping/mafmt v1.2.8 h1:TCghSl5kkwEE0j+sU/gudyhVMRlpBin8fMBBHg59EbA=
@@ -1222,7 +1189,6 @@ github.com/whyrusleeping/tar-utils v0.0.0-20201201191210-20a61371de5b h1:wA3QeTs
1189
github.com/whyrusleeping/tar-utils v0.0.0-20201201191210-20a61371de5b/go.mod h1:xT1Y5p2JR2PfSZihE0s4mjdJaRGp1waCTf5JzhQLBck=
1190
github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee h1:lYbXeSvJi5zk5GLKVuid9TVjS9a0OmLIDKTfoZBL6Ow=
1191
github.com/whyrusleeping/timecache v0.0.0-20160911033111-cfcb2f1abfee/go.mod h1:m2aV4LZI4Aez7dP5PMyVKEHhUyEJ/RjmPEDOpDvudHg=
1225
-github.com/whyrusleeping/yamux v1.1.5/go.mod h1:E8LnQQ8HKx5KD29HZFUwM1PxCOdPRzGwur1mcYhXcD8=
1192
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
1193
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
1194
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -1332,7 +1298,6 @@ golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
1298
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
1299
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
1300
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
1335
-golang.org/x/net v0.0.0-20180524181706-dfa909b99c79/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
1301
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
1302
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
1303
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -1372,6 +1337,7 @@ golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAG
1337
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
1338
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
1339
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
1340
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
1341
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
1342
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
1343
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -1503,6 +1469,7 @@ google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
1469
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
1470
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
1471
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
1472
+google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
1473
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
1474
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
1475
google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
test/sharness/t0700-remotepin.sh
new
+271
@@ -0,0 +1,271 @@
1
+#!/usr/bin/env bash
2
+
3
+test_description="Test ipfs remote pinning operations"
4
+
5
+. lib/test-lib.sh
6
+
7
+if [ -z ${DOCKER_HOST+x} ]; then
8
+ # TODO: set up instead of skipping?
9
+ skip_all='Skipping pinning service integration tests: missing DOCKER_HOST, remote pinning service not available'
10
+ test_done
11
+fi
12
+
13
+# daemon running in online mode to ensure Pin.origins/PinStatus.delegates work
14
+test_init_ipfs
15
+test_launch_ipfs_daemon
16
+
17
+# create user on pinning service
18
+TEST_PIN_SVC="http://${DOCKER_HOST}:5000/api/v1"
19
+TEST_PIN_SVC_KEY=$(curl -s -X POST "$TEST_PIN_SVC/users" -d email="go-ipfs-sharness@ipfs.example.com" | jq --raw-output .access_token)
20
+
21
+# pin remote service add|ls|rm
22
+
23
+# add valid and invalid services
24
+test_expect_success "creating test user on remote pinning service" '
25
+ echo CI host IP address ${TEST_PIN_SVC} &&
26
+ ipfs pin remote service add test_pin_svc ${TEST_PIN_SVC} ${TEST_PIN_SVC_KEY} &&
27
+ ipfs pin remote service add test_invalid_key_svc ${TEST_PIN_SVC} fake_api_key &&
28
+ ipfs pin remote service add test_invalid_url_path_svc ${TEST_PIN_SVC}/invalid-path fake_api_key &&
29
+ ipfs pin remote service add test_invalid_url_dns_svc https://invalid-service.example.com fake_api_key
30
+'
31
+
32
+test_expect_success "test 'ipfs pin remote service ls'" '
33
+ ipfs pin remote service ls | tee ls_out &&
34
+ grep -q test_pin_svc ls_out &&
35
+ grep -q test_invalid_key_svc ls_out &&
36
+ grep -q test_invalid_url_path_svc ls_out &&
37
+ grep -q test_invalid_url_dns_svc ls_out
38
+'
39
+
40
+# SECURITY of access tokens in Api.Key fields:
41
+# Pinning.RemoteServices includes Api.Key, and we give it the same treatment
42
+# as Identity.PrivKey to prevent exposing it on the network
43
+
44
+test_expect_success "'ipfs config Pinning' fails" '
45
+ test_expect_code 1 ipfs config Pinning 2>&1 > config_out
46
+'
47
+test_expect_success "output does not include Api.Key" '
48
+ test_expect_code 1 grep -q Key config_out
49
+'
50
+
51
+test_expect_success "'ipfs config Pinning.RemoteServices.test_pin_svc.Api.Key' fails" '
52
+ test_expect_code 1 ipfs config Pinning.RemoteServices.test_pin_svc.Api.Key 2> config_out
53
+'
54
+
55
+test_expect_success "output includes meaningful error" '
56
+ echo "Error: cannot show or change pinning services through this API (try: ipfs pin remote service --help)" > config_exp &&
57
+ test_cmp config_exp config_out
58
+'
59
+
60
+test_expect_success "'ipfs config Pinning.RemoteServices.test_pin_svc' fails" '
61
+ test_expect_code 1 ipfs config Pinning.RemoteServices.test_pin_svc 2> config_out
62
+'
63
+test_expect_success "output includes meaningful error" '
64
+ test_cmp config_exp config_out
65
+'
66
+
67
+test_expect_success "'ipfs config show' doesn't include RemoteServices" '
68
+ ipfs config show > show_config &&
69
+ test_expect_code 1 grep RemoteServices show_config
70
+'
71
+
72
+test_expect_success "'ipfs config replace' injects remote services back" '
73
+ test_expect_code 1 grep -q -E "test_.+_svc" show_config &&
74
+ ipfs config replace show_config &&
75
+ test_expect_code 0 grep -q test_pin_svc "$IPFS_PATH/config" &&
76
+ test_expect_code 0 grep -q test_invalid_key_svc "$IPFS_PATH/config" &&
77
+ test_expect_code 0 grep -q test_invalid_url_path_svc "$IPFS_PATH/config" &&
78
+ test_expect_code 0 grep -q test_invalid_url_dns_svc "$IPFS_PATH/config"
79
+'
80
+
81
+# note: we remove Identity.PrivKey to ensure error is triggered by Pinning.RemoteServices
82
+test_expect_success "'ipfs config replace' with remote services errors out" '
83
+ jq -M "del(.Identity.PrivKey)" "$IPFS_PATH/config" | jq ".Pinning += { RemoteServices: {\"foo\": {} }}" > new_config &&
84
+ test_expect_code 1 ipfs config replace - < new_config 2> replace_out
85
+'
86
+test_expect_success "output includes meaningful error" '
87
+ echo "Error: cannot show or change pinning services through this API (try: ipfs pin remote service --help)" > replace_expected &&
88
+ test_cmp replace_out replace_expected
89
+'
90
+
91
+# /SECURITY
92
+
93
+test_expect_success "pin remote service ls --stat' returns numbers for a valid service" '
94
+ ipfs pin remote service ls --stat | grep -E "^test_pin_svc.+[0-9]+/[0-9]+/[0-9]+/[0-9]+$"
95
+'
96
+
97
+test_expect_success "pin remote service ls --enc=json --stat' returns valid status" "
98
+ ipfs pin remote service ls --stat --enc=json | jq --raw-output '.RemoteServices[] | select(.Service == \"test_pin_svc\") | .Stat.Status' | tee stat_out &&
99
+ echo valid > stat_expected &&
100
+ test_cmp stat_out stat_expected
101
+"
102
+
103
+test_expect_success "pin remote service ls --stat' returns invalid status for invalid service" '
104
+ ipfs pin remote service ls --stat | grep -E "^test_invalid_url_path_svc.+invalid$"
105
+'
106
+
107
+test_expect_success "pin remote service ls --enc=json --stat' returns invalid status" "
108
+ ipfs pin remote service ls --stat --enc=json | jq --raw-output '.RemoteServices[] | select(.Service == \"test_invalid_url_path_svc\") | .Stat.Status' | tee stat_out &&
109
+ echo invalid > stat_expected &&
110
+ test_cmp stat_out stat_expected
111
+"
112
+
113
+test_expect_success "pin remote service ls --enc=json' (without --stat) returns no Stat object" "
114
+ ipfs pin remote service ls --enc=json | jq --raw-output '.RemoteServices[] | select(.Service == \"test_invalid_url_path_svc\") | .Stat' | tee stat_out &&
115
+ echo null > stat_expected &&
116
+ test_cmp stat_out stat_expected
117
+"
118
+
119
+test_expect_success "check connection to the test pinning service" '
120
+ ipfs pin remote ls --service=test_pin_svc --enc=json
121
+'
122
+
123
+test_expect_success "unauthorized pinning service calls fail" '
124
+ test_expect_code 1 ipfs pin remote ls --service=test_invalid_key_svc
125
+'
126
+
127
+test_expect_success "misconfigured pinning service calls fail (wrong path)" '
128
+ test_expect_code 1 ipfs pin remote ls --service=test_invalid_url_path_svc
129
+'
130
+
131
+test_expect_success "misconfigured pinning service calls fail (dns error)" '
132
+ test_expect_code 1 ipfs pin remote ls --service=test_invalid_url_dns_svc
133
+'
134
+
135
+# pin remote service rm
136
+
137
+test_expect_success "remove pinning service" '
138
+ ipfs pin remote service rm test_invalid_key_svc &&
139
+ ipfs pin remote service rm test_invalid_url_path_svc &&
140
+ ipfs pin remote service rm test_invalid_url_dns_svc
141
+'
142
+
143
+test_expect_success "verify pinning service removal works" '
144
+ ipfs pin remote service ls | tee ls_out &&
145
+ test_expect_code 1 grep test_invalid_key_svc ls_out &&
146
+ test_expect_code 1 grep test_invalid_url_path_svc ls_out &&
147
+ test_expect_code 1 grep test_invalid_url_dns_svc ls_out
148
+'
149
+
150
+# pin remote add
151
+
152
+# we leverage the fact that inlined CID can be pinned instantly on the remote service
153
+# (https://github.com/ipfs-shipyard/rb-pinning-service-api/issues/8)
154
+# below test ensures that assumption is correct (before we proceed to actual tests)
155
+test_expect_success "verify that default add (implicit --background=false) works with data inlined in CID" '
156
+ ipfs pin remote add --service=test_pin_svc --name=inlined_null bafkqaaa &&
157
+ ipfs pin remote ls --service=test_pin_svc --enc=json --name=inlined_null --status=pinned | jq --raw-output .Status | tee ls_out &&
158
+ grep -q "pinned" ls_out
159
+'
160
+
161
+test_remote_pins() {
162
+ BASE=$1
163
+ if [ -n "$BASE" ]; then
164
+ BASE_ARGS="--cid-base=$BASE"
165
+ fi
166
+
167
+ # note: HAS_MISSING is not inlined nor imported to IPFS on purpose, to reliably test 'queued' state
168
+ test_expect_success "create some hashes using base $BASE" '
169
+ export HASH_A=$(echo -n "A @ $(date +%s.%N)" | ipfs add $BASE_ARGS -q --inline --inline-limit 1000 --pin=false) &&
170
+ export HASH_B=$(echo -n "B @ $(date +%s.%N)" | ipfs add $BASE_ARGS -q --inline --inline-limit 1000 --pin=false) &&
171
+ export HASH_C=$(echo -n "C @ $(date +%s.%N)" | ipfs add $BASE_ARGS -q --inline --inline-limit 1000 --pin=false) &&
172
+ export HASH_MISSING=$(echo "MISSING FROM IPFS @ $(date +%s.%N)" | ipfs add $BASE_ARGS -q --only-hash) &&
173
+ echo "A: $HASH_A" &&
174
+ echo "B: $HASH_B" &&
175
+ echo "C: $HASH_C" &&
176
+ echo "M: $HASH_MISSING"
177
+ '
178
+
179
+ test_expect_success "'ipfs pin remote add --background=true'" '
180
+ ipfs pin remote add --background=true --service=test_pin_svc --enc=json $BASE_ARGS --name=name_a $HASH_A
181
+ '
182
+
183
+ test_expect_success "verify background add worked (instantly pinned variant)" '
184
+ ipfs pin remote ls --service=test_pin_svc --enc=json --name=name_a | tee ls_out &&
185
+ test_expect_code 0 grep -q name_a ls_out &&
186
+ test_expect_code 0 grep -q $HASH_A ls_out
187
+ '
188
+
189
+ test_expect_success "'ipfs pin remote add --background=true' with CID that is not available" '
190
+ test_expect_code 0 ipfs pin remote add --background=true --service=test_pin_svc --enc=json $BASE_ARGS --name=name_m $HASH_MISSING
191
+ '
192
+
193
+ test_expect_success "verify background add worked (queued variant)" '
194
+ ipfs pin remote ls --service=test_pin_svc --enc=json --name=name_m --status=queued,pinning | tee ls_out &&
195
+ test_expect_code 0 grep -q name_m ls_out &&
196
+ test_expect_code 0 grep -q $HASH_MISSING ls_out
197
+ '
198
+
199
+ test_expect_success "'ipfs pin remote add --background=false'" '
200
+ test_expect_code 0 ipfs pin remote add --background=false --service=test_pin_svc --enc=json $BASE_ARGS --name=name_b $HASH_B
201
+ '
202
+
203
+ test_expect_success "verify foreground add worked" '
204
+ ipfs pin remote ls --service=test_pin_svc --enc=json $ID_B | tee ls_out &&
205
+ test_expect_code 0 grep -q name_b ls_out &&
206
+ test_expect_code 0 grep -q pinned ls_out &&
207
+ test_expect_code 0 grep -q $HASH_B ls_out
208
+ '
209
+
210
+ test_expect_success "'ipfs pin remote ls' for existing pins by multiple statuses" '
211
+ ipfs pin remote ls --service=test_pin_svc --enc=json --status=queued,pinning,pinned,failed | tee ls_out &&
212
+ test_expect_code 0 grep -q $HASH_A ls_out &&
213
+ test_expect_code 0 grep -q $HASH_B ls_out &&
214
+ test_expect_code 0 grep -q $HASH_MISSING ls_out
215
+ '
216
+
217
+ test_expect_success "'ipfs pin remote ls' for existing pins by CID" '
218
+ ipfs pin remote ls --service=test_pin_svc --enc=json --cid=$HASH_B | tee ls_out &&
219
+ test_expect_code 0 grep -q $HASH_B ls_out
220
+ '
221
+
222
+ test_expect_success "'ipfs pin remote ls' for existing pins by name" '
223
+ ipfs pin remote ls --service=test_pin_svc --enc=json --name=name_a | tee ls_out &&
224
+ test_expect_code 0 grep -q $HASH_A ls_out
225
+ '
226
+
227
+ test_expect_success "'ipfs pin remote ls' for ongoing pins by status" '
228
+ ipfs pin remote ls --service=test_pin_svc --status=queued,pinning | tee ls_out &&
229
+ test_expect_code 0 grep -q $HASH_MISSING ls_out
230
+ '
231
+
232
+ # --force is required only when more than a single match is found,
233
+ # so we add second pin with the same name (but different CID) to simulate that scenario
234
+ test_expect_success "'ipfs pin remote rm --name' fails without --force when matching multiple pins" '
235
+ test_expect_code 0 ipfs pin remote add --service=test_pin_svc --enc=json $BASE_ARGS --name=name_b $HASH_C &&
236
+ test_expect_code 1 ipfs pin remote rm --service=test_pin_svc --name=name_b 2> rm_out &&
237
+ echo "Error: multiple remote pins are matching this query, add --force to confirm the bulk removal" > rm_expected &&
238
+ test_cmp rm_out rm_expected
239
+ '
240
+
241
+ test_expect_success "'ipfs pin remote rm --name' without --force did not remove matching pins" '
242
+ ipfs pin remote ls --service=test_pin_svc --enc=json --name=name_b | jq --raw-output .Cid | tee ls_out &&
243
+ test_expect_code 0 grep -q $HASH_B ls_out &&
244
+ test_expect_code 0 grep -q $HASH_C ls_out
245
+ '
246
+
247
+ test_expect_success "'ipfs pin remote rm --name' with --force removes all matching pins" '
248
+ test_expect_code 0 ipfs pin remote rm --service=test_pin_svc --name=name_b --force &&
249
+ ipfs pin remote ls --service=test_pin_svc --enc=json --name=name_b | jq --raw-output .Cid | tee ls_out &&
250
+ test_expect_code 1 grep -q $HASH_B ls_out &&
251
+ test_expect_code 1 grep -q $HASH_C ls_out
252
+ '
253
+
254
+ test_expect_success "'ipfs pin remote rm --force' removes all pinned items" '
255
+ ipfs pin remote ls --service=test_pin_svc --enc=json --status=queued,pinning,pinned,failed | jq --raw-output .Cid | tee ls_out &&
256
+ test_expect_code 0 grep -q $HASH_A ls_out &&
257
+ test_expect_code 0 grep -q $HASH_MISSING ls_out &&
258
+ ipfs pin remote rm --service=test_pin_svc --status=queued,pinning,pinned,failed --force &&
259
+ ipfs pin remote ls --service=test_pin_svc --enc=json --status=queued,pinning,pinned,failed | jq --raw-output .Cid | tee ls_out &&
260
+ test_expect_code 1 grep -q $HASH_A ls_out &&
261
+ test_expect_code 1 grep -q $HASH_MISSING ls_out
262
+ '
263
+
264
+}
265
+
266
+test_remote_pins ""
267
+
268
+test_kill_ipfs_daemon
269
+test_done
270
+
271
+# vim: ts=2 sw=2 sts=2 et: