github.com/kaleido-io/firefly@v0.0.0-20210622132723-8b4b6aacb971/internal/publicstorage/ipfs/ipfs.go (about) 1 // Copyright © 2021 Kaleido, Inc. 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"); 6 // you may not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, 13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 // See the License for the specific language governing permissions and 15 // limitations under the License. 16 17 package ipfs 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 24 "io" 25 26 "github.com/akamensky/base58" 27 "github.com/go-resty/resty/v2" 28 "github.com/kaleido-io/firefly/internal/config" 29 "github.com/kaleido-io/firefly/internal/i18n" 30 "github.com/kaleido-io/firefly/internal/log" 31 "github.com/kaleido-io/firefly/internal/restclient" 32 "github.com/kaleido-io/firefly/pkg/fftypes" 33 "github.com/kaleido-io/firefly/pkg/publicstorage" 34 ) 35 36 type IPFS struct { 37 ctx context.Context 38 capabilities *publicstorage.Capabilities 39 callbacks publicstorage.Callbacks 40 apiClient *resty.Client 41 gwClient *resty.Client 42 } 43 44 type ipfsUploadResponse struct { 45 Name string `json:"Name"` 46 Hash string `json:"Hash"` 47 Size json.Number `json:"Size"` 48 } 49 50 func (i *IPFS) Name() string { 51 return "ipfs" 52 } 53 54 func (i *IPFS) Init(ctx context.Context, prefix config.Prefix, callbacks publicstorage.Callbacks) error { 55 56 i.ctx = log.WithLogField(ctx, "publicstorage", "ipfs") 57 i.callbacks = callbacks 58 59 apiPrefix := prefix.SubPrefix(IPFSConfAPISubconf) 60 if apiPrefix.GetString(restclient.HTTPConfigURL) == "" { 61 return i18n.NewError(ctx, i18n.MsgMissingPluginConfig, apiPrefix.Resolve(restclient.HTTPConfigURL), "ipfs") 62 } 63 i.apiClient = restclient.New(i.ctx, apiPrefix) 64 gwPrefix := prefix.SubPrefix(IPFSConfGatewaySubconf) 65 if gwPrefix.GetString(restclient.HTTPConfigURL) == "" { 66 return i18n.NewError(ctx, i18n.MsgMissingPluginConfig, gwPrefix.Resolve(restclient.HTTPConfigURL), "ipfs") 67 } 68 i.gwClient = restclient.New(i.ctx, gwPrefix) 69 i.capabilities = &publicstorage.Capabilities{} 70 return nil 71 } 72 73 func (i *IPFS) Capabilities() *publicstorage.Capabilities { 74 return i.capabilities 75 } 76 77 func (i *IPFS) ipfsHashToBytes32(ipfshash string) (*fftypes.Bytes32, error) { 78 b, err := base58.Decode(ipfshash) 79 if err != nil { 80 return nil, i18n.WrapError(i.ctx, err, i18n.MsgIPFSHashDecodeFailed, ipfshash) 81 } 82 if len(b) != 34 { 83 return nil, i18n.NewError(i.ctx, i18n.MsgIPFSHashDecodeFailed, b) 84 } 85 var b32 fftypes.Bytes32 86 copy(b32[:], b[2:34]) 87 return &b32, nil 88 } 89 90 func (i *IPFS) bytes32ToIPFSHash(payloadRef *fftypes.Bytes32) string { 91 var hashBytes [34]byte 92 copy(hashBytes[0:2], []byte{0x12, 0x20}) 93 copy(hashBytes[2:34], payloadRef[0:32]) 94 return base58.Encode(hashBytes[:]) 95 } 96 97 func (i *IPFS) PublishData(ctx context.Context, data io.Reader) (payloadRef *fftypes.Bytes32, backendID string, err error) { 98 var ipfsResponse ipfsUploadResponse 99 res, err := i.apiClient.R(). 100 SetContext(ctx). 101 SetFileReader("document", "file.bin", data). 102 SetResult(&ipfsResponse). 103 Post("/api/v0/add") 104 if err != nil || !res.IsSuccess() { 105 return nil, "", restclient.WrapRestErr(i.ctx, res, err, i18n.MsgIPFSRESTErr) 106 } 107 log.L(ctx).Infof("IPFS published %s Size=%s", ipfsResponse.Hash, ipfsResponse.Size) 108 payloadRef, err = i.ipfsHashToBytes32(ipfsResponse.Hash) 109 return payloadRef, ipfsResponse.Hash, err 110 } 111 112 func (i *IPFS) RetrieveData(ctx context.Context, payloadRef *fftypes.Bytes32) (data io.ReadCloser, err error) { 113 ipfsHash := i.bytes32ToIPFSHash(payloadRef) 114 res, err := i.gwClient.R(). 115 SetContext(ctx). 116 SetDoNotParseResponse(true). 117 Get(fmt.Sprintf("/ipfs/%s", ipfsHash)) 118 restclient.OnAfterResponse(i.gwClient, res) // required using SetDoNotParseResponse 119 if err != nil || !res.IsSuccess() { 120 if res != nil && res.RawBody() != nil { 121 _ = res.RawBody().Close() 122 } 123 return nil, restclient.WrapRestErr(i.ctx, res, err, i18n.MsgIPFSRESTErr) 124 } 125 log.L(ctx).Infof("IPFS retrieved %s", ipfsHash) 126 return res.RawBody(), nil 127 }