@cryptotaxi247 / kubo / commits / a8c798072

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: