Skip to content

Commit b7a87c0

Browse files
committed
ipfs: add support to pull images by ref
Signed-off-by: abushwang <[email protected]>
1 parent 6378393 commit b7a87c0

File tree

10 files changed

+446
-37
lines changed

10 files changed

+446
-37
lines changed

cmd/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ require (
7575
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
7676
github.com/imdario/mergo v0.3.13 // indirect
7777
github.com/intel/goresctrl v0.8.0 // indirect
78-
github.com/ipfs/go-cid v0.1.0 // indirect
78+
github.com/ipfs/go-cid v0.4.1 // indirect
7979
github.com/josharian/intern v1.0.0 // indirect
8080
github.com/json-iterator/go v1.1.12 // indirect
8181
github.com/klauspost/cpuid/v2 v2.2.6 // indirect

cmd/go.sum

+2-18
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,8 @@ github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
172172
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
173173
github.com/intel/goresctrl v0.8.0 h1:N3shVbS3kA1Hk2AmcbHv8805Hjbv+zqsCIZCGktxx50=
174174
github.com/intel/goresctrl v0.8.0/go.mod h1:T3ZZnuHSNouwELB5wvOoUJaB7l/4Rm23rJy/wuWJlr0=
175-
github.com/ipfs/go-cid v0.1.0 h1:YN33LQulcRHjfom/i25yoOZR4Telp1Hr/2RU3d0PnC0=
176-
github.com/ipfs/go-cid v0.1.0/go.mod h1:rH5/Xv83Rfy8Rw6xG+id3DYAMUVmem1MowoKwdXmN2o=
175+
github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
176+
github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=
177177
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
178178
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
179179
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
@@ -185,7 +185,6 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
185185
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
186186
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
187187
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
188-
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
189188
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
190189
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
191190
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -210,8 +209,6 @@ github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U
210209
github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA=
211210
github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ=
212211
github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE=
213-
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
214-
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
215212
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
216213
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
217214
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@@ -238,25 +235,19 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
238235
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
239236
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
240237
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
241-
github.com/mr-tron/base58 v1.1.0/go.mod h1:xcD2VGqlgYjBdcBLw+TuYLr8afG+Hj8g2eTVqeSzSU8=
242238
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
243239
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
244240
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
245-
github.com/multiformats/go-base32 v0.0.3/go.mod h1:pLiuGC8y0QR3Ue4Zug5UzK9LjgbkL8NSQj0zQ5Nz/AA=
246241
github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=
247242
github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
248-
github.com/multiformats/go-base36 v0.1.0/go.mod h1:kFGE83c6s80PklsHO9sRn2NCoffoRdUUOENyW/Vv6sM=
249243
github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
250244
github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
251245
github.com/multiformats/go-multiaddr v0.14.0 h1:bfrHrJhrRuh/NXH5mCnemjpbGjzRw/b+tJFOD41g2tU=
252246
github.com/multiformats/go-multiaddr v0.14.0/go.mod h1:6EkVAxtznq2yC3QT5CM1UTAwG0GTP3EWAIcjHuzQ+r4=
253-
github.com/multiformats/go-multibase v0.0.3/go.mod h1:5+1R4eQrT3PkYZ24C3W2Ue2tPwIdYQD509ZjSb5y9Oc=
254247
github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
255248
github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
256-
github.com/multiformats/go-multihash v0.0.15/go.mod h1:D6aZrWNLFTV/ynMpKsNtB40mJzmCl4jb1alC0OvHiHg=
257249
github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
258250
github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
259-
github.com/multiformats/go-varint v0.0.6/go.mod h1:3Ls8CIEsrijN6+B7PbrXRPxHRPuXSrVKRY101jdMZYE=
260251
github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
261252
github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
262253
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
@@ -382,8 +373,6 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
382373
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
383374
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
384375
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
385-
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
386-
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
387376
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
388377
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
389378
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@@ -407,7 +396,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
407396
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
408397
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
409398
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
410-
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
411399
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8=
412400
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk=
413401
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -430,15 +418,11 @@ golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7w
430418
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
431419
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
432420
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
433-
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
434-
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
435421
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
436422
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
437423
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
438424
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
439425
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
440-
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
441-
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
442426
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
443427
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
444428
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

docs/ipfs.md

+31
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,37 @@ user 0m0.556s
140140
sys 0m0.280s
141141
```
142142

143+
### Pulling Container Images via Image Reference
144+
145+
Stargz Snapshotter currently supports pulling container images using an image reference.
146+
147+
For example, the following command uses lazy pulling to download the specified image reference:
148+
```
149+
ctr-remote i rpull --snapshotter=stargz --ipfs ghcr.io/stargz-containers/python:3.9-org
150+
fetching sha256:3b3f42e1... application/vnd.oci.image.index.v1+json
151+
fetching sha256:4afd7ffe... application/vnd.oci.image.manifest.v1+json
152+
fetching sha256:099a9289... application/vnd.oci.image.config.v1+json
153+
```
154+
155+
Alternatively, you can use the following command to download the specified image reference without lazy pulling:
156+
```
157+
ctr-remote i rpull --snapshotter=overlayfs --ipfs ghcr.io/stargz-containers/python:3.9-org
158+
fetching sha256:33ad01f9... application/vnd.oci.image.index.v1+json
159+
fetching sha256:49d6d96d... application/vnd.oci.image.manifest.v1+json
160+
fetching sha256:6f1289b1... application/vnd.oci.image.config.v1+json
161+
fetching sha256:4c25b309... application/vnd.oci.image.layer.v1.tar+gzip
162+
fetching sha256:9476e460... application/vnd.oci.image.layer.v1.tar+gzip
163+
fetching sha256:64c0f10e... application/vnd.oci.image.layer.v1.tar+gzip
164+
fetching sha256:1acf5650... application/vnd.oci.image.layer.v1.tar+gzip
165+
fetching sha256:3fff52a3... application/vnd.oci.image.layer.v1.tar+gzip
166+
fetching sha256:b95c0dd0... application/vnd.oci.image.layer.v1.tar+gzip
167+
fetching sha256:5cf06daf... application/vnd.oci.image.layer.v1.tar+gzip
168+
fetching sha256:419e258e... application/vnd.oci.image.layer.v1.tar+gzip
169+
fetching sha256:942374d5... application/vnd.oci.image.layer.v1.tar+gzip
170+
```
171+
172+
This functionality is also compatible with downloading container images using CID, allowing you to choose your preferred method.
173+
143174
## Appendix 1: Creating IPFS private network
144175

145176
You can create a private IPFS network as described in the official docs.

ipfs/client/client.go

+237
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package client
1818

1919
import (
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\nRequest 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\nRequest 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

Comments
 (0)