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