1717package client
1818
1919import (
20+ "bytes"
2021 "encoding/json"
2122 "fmt"
2223 "io"
@@ -27,6 +28,7 @@ import (
2728 "path/filepath"
2829 "strings"
2930
31+ "github.com/containerd/stargz-snapshotter/ipfs/ipnskey"
3032 "github.com/mitchellh/go-homedir"
3133 ma "github.com/multiformats/go-multiaddr"
3234 manet "github.com/multiformats/go-multiaddr/net"
@@ -226,3 +228,238 @@ func GetIPFSAPIAddress(ipfsPath string, scheme string) (string, error) {
226228 }
227229 return iurl , nil
228230}
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