17
17
package client
18
18
19
19
import (
20
+ "bytes"
20
21
"encoding/json"
21
22
"fmt"
22
23
"io"
@@ -27,6 +28,7 @@ import (
27
28
"path/filepath"
28
29
"strings"
29
30
31
+ "github.com/containerd/stargz-snapshotter/ipfs/ipnskey"
30
32
"github.com/mitchellh/go-homedir"
31
33
ma "github.com/multiformats/go-multiaddr"
32
34
manet "github.com/multiformats/go-multiaddr/net"
@@ -226,3 +228,238 @@ func GetIPFSAPIAddress(ipfsPath string, scheme string) (string, error) {
226
228
}
227
229
return iurl , nil
228
230
}
231
+
232
+ // Resolve resolves the IPNS name to its corresponding CID.
233
+ func (c * Client ) Resolve (ref string ) (string , error ) {
234
+ if c .Address == "" {
235
+ return "" , fmt .Errorf ("specify IPFS API address" )
236
+ }
237
+
238
+ peerID , err := c .importKey (ref )
239
+ if err != nil {
240
+ return "" , fmt .Errorf ("failed to import key: %w" , err )
241
+ }
242
+
243
+ client := c .Client
244
+ if client == nil {
245
+ client = http .DefaultClient
246
+ }
247
+
248
+ ipfsAPINameResolve := c .Address + "/api/v0/name/resolve"
249
+ req , err := http .NewRequest ("POST" , ipfsAPINameResolve , nil )
250
+ if err != nil {
251
+ return "" , err
252
+ }
253
+
254
+ q := req .URL .Query ()
255
+ q .Add ("arg" , "/ipns/" + peerID )
256
+ q .Add ("nocache" , "true" )
257
+ req .URL .RawQuery = q .Encode ()
258
+
259
+ resp , err := client .Do (req )
260
+ if err != nil {
261
+ return "" , err
262
+ }
263
+ defer func () {
264
+ io .Copy (io .Discard , resp .Body )
265
+ resp .Body .Close ()
266
+ }()
267
+
268
+ if resp .StatusCode / 100 != 2 {
269
+ return "" , fmt .Errorf ("failed to resolve name %v; status code: %v" , peerID , resp .StatusCode )
270
+ }
271
+
272
+ var rs struct {
273
+ Path string `json:"Path"`
274
+ }
275
+ if err := json .NewDecoder (resp .Body ).Decode (& rs ); err != nil {
276
+ return "" , err
277
+ }
278
+
279
+ parts := strings .Split (rs .Path , "/" )
280
+ if len (parts ) < 3 || parts [1 ] != "ipfs" {
281
+ return "" , fmt .Errorf ("invalid resolved path format: %s" , rs .Path )
282
+ }
283
+
284
+ return parts [2 ], nil
285
+ }
286
+
287
+ // Publish publishes the given CID to IPNS using the key associated with the given ref.
288
+ func (c * Client ) Publish (ref string , cid string ) error {
289
+ if c .Address == "" {
290
+ return fmt .Errorf ("specify IPFS API address" )
291
+ }
292
+
293
+ _ , err := c .importKey (ref )
294
+ if err != nil {
295
+ return fmt .Errorf ("failed to import key: %w" , err )
296
+ }
297
+
298
+ client := c .Client
299
+ if client == nil {
300
+ client = http .DefaultClient
301
+ }
302
+
303
+ ipfsAPINamePublish := c .Address + "/api/v0/name/publish"
304
+ req , err := http .NewRequest ("POST" , ipfsAPINamePublish , nil )
305
+ if err != nil {
306
+ return err
307
+ }
308
+
309
+ q := req .URL .Query ()
310
+ q .Add ("arg" , "/ipfs/" + cid )
311
+ q .Add ("key" , ref )
312
+ q .Add ("allow-offline" , "true" )
313
+ req .URL .RawQuery = q .Encode ()
314
+
315
+ resp , err := client .Do (req )
316
+ if err != nil {
317
+ return err
318
+ }
319
+ defer func () {
320
+ io .Copy (io .Discard , resp .Body )
321
+ resp .Body .Close ()
322
+ }()
323
+
324
+ respBody , err := io .ReadAll (resp .Body )
325
+ if err != nil {
326
+ return fmt .Errorf ("failed to read response body: %v" , err )
327
+ }
328
+
329
+ if resp .StatusCode / 100 != 2 {
330
+ return fmt .Errorf ("failed to publish; status code: %v, body: %s\n " +
331
+ "Request URL: %s" , resp .StatusCode , string (respBody ), ipfsAPINamePublish )
332
+ }
333
+
334
+ return nil
335
+ }
336
+
337
+ // importKey imports the key pair associated with the given ref into the local IPFS node.
338
+ // The ref will be used as the key name in IPFS. If the key already exists, it will return nil.
339
+ func (c * Client ) importKey (ref string ) (string , error ) {
340
+ if c .Address == "" {
341
+ return "" , fmt .Errorf ("specify IPFS API address" )
342
+ }
343
+
344
+ keyID , err := c .getKeyIDFromIPFS (ref )
345
+ if err == nil && keyID != "" {
346
+ return keyID , nil
347
+ }
348
+
349
+ keyData , err := ipnskey .GenerateKeyData (ref )
350
+ if err != nil {
351
+ return "" , fmt .Errorf ("failed to generate key data: %w" , err )
352
+ }
353
+
354
+ body := & bytes.Buffer {}
355
+ writer := multipart .NewWriter (body )
356
+
357
+ safeFilename := strings .ReplaceAll (ref , "/" , "_" )
358
+ safeFilename = strings .ReplaceAll (safeFilename , ":" , "_" )
359
+
360
+ part , err := writer .CreateFormFile ("file" , safeFilename + ".pem" )
361
+ if err != nil {
362
+ return "" , fmt .Errorf ("failed to create form file: %v" , err )
363
+ }
364
+
365
+ _ , err = part .Write (keyData )
366
+ if err != nil {
367
+ return "" , fmt .Errorf ("failed to write key data: %v" , err )
368
+ }
369
+
370
+ err = writer .Close ()
371
+ if err != nil {
372
+ return "" , fmt .Errorf ("failed to close multipart writer: %v" , err )
373
+ }
374
+
375
+ encodedKeyname := url .QueryEscape (ref )
376
+ ipfsAPIKeyImport := fmt .Sprintf ("%s/api/v0/key/import?arg=%s&format=pem-pkcs8-cleartext" , c .Address , encodedKeyname )
377
+
378
+ req , err := http .NewRequest ("POST" , ipfsAPIKeyImport , body )
379
+ if err != nil {
380
+ return "" , fmt .Errorf ("failed to create HTTP request: %v" , err )
381
+ }
382
+
383
+ req .Header .Set ("Content-Type" , writer .FormDataContentType ())
384
+
385
+ client := c .Client
386
+ if client == nil {
387
+ client = http .DefaultClient
388
+ }
389
+
390
+ resp , err := client .Do (req )
391
+ if err != nil {
392
+ return "" , fmt .Errorf ("failed to send request: %v" , err )
393
+ }
394
+ defer resp .Body .Close ()
395
+
396
+ respBody , err := io .ReadAll (resp .Body )
397
+ if err != nil {
398
+ return "" , fmt .Errorf ("failed to read response body: %v" , err )
399
+ }
400
+
401
+ if resp .StatusCode != http .StatusOK {
402
+ return "" , fmt .Errorf ("IPFS API returned error status: %d, body: %s\n Request URL: %s" , resp .StatusCode , string (respBody ), ipfsAPIKeyImport )
403
+ }
404
+
405
+ return c .getKeyIDFromIPFS (ref )
406
+ }
407
+
408
+ // getKeyIDFromIPFS checks if a key with the given name already exists in IPFS
409
+ func (c * Client ) getKeyIDFromIPFS (name string ) (string , error ) {
410
+ client := c .Client
411
+ if client == nil {
412
+ client = http .DefaultClient
413
+ }
414
+
415
+ ipfsAPIKeyList := c .Address + "/api/v0/key/list"
416
+ req , err := http .NewRequest ("POST" , ipfsAPIKeyList , nil )
417
+ if err != nil {
418
+ return "" , err
419
+ }
420
+
421
+ resp , err := client .Do (req )
422
+ if err != nil {
423
+ return "" , fmt .Errorf ("failed to get key list: %v" , err )
424
+ }
425
+ defer resp .Body .Close ()
426
+
427
+ respBody , err := io .ReadAll (resp .Body )
428
+ if err != nil {
429
+ return "" , fmt .Errorf ("failed to read response body: %v" , err )
430
+ }
431
+
432
+ if resp .StatusCode != http .StatusOK {
433
+ return "" , fmt .Errorf ("IPFS API returned error status: %d, body: %s\n Request URL: %s" , resp .StatusCode , string (respBody ), ipfsAPIKeyList )
434
+ }
435
+
436
+ var result struct {
437
+ Keys []struct {
438
+ Name string `json:"name"`
439
+ ID string `json:"id"`
440
+ } `json:"Keys"`
441
+ }
442
+
443
+ if err := json .Unmarshal (respBody , & result ); err != nil {
444
+ return "" , fmt .Errorf ("failed to decode response: %v" , err )
445
+ }
446
+
447
+ for _ , key := range result .Keys {
448
+ if key .Name == name {
449
+ return key .ID , nil
450
+ }
451
+ }
452
+
453
+ return "" , fmt .Errorf ("key not found: %s" , name )
454
+ }
455
+
456
+ func (c * Client ) IsRef (s string ) bool {
457
+ parts := strings .Split (s , "/" )
458
+ lastPart := parts [len (parts )- 1 ]
459
+
460
+ if strings .Contains (lastPart , ":" ) || strings .Contains (lastPart , "@" ) {
461
+ return true
462
+ }
463
+
464
+ return len (parts ) >= 2
465
+ }
0 commit comments