github.com/lzy4123/fabric@v2.1.1+incompatible/core/chaincode/persistence/chaincode_package.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package persistence 8 9 import ( 10 "archive/tar" 11 "bytes" 12 "compress/gzip" 13 "encoding/json" 14 "io" 15 "io/ioutil" 16 "os" 17 "path/filepath" 18 "regexp" 19 20 pb "github.com/hyperledger/fabric-protos-go/peer" 21 22 "github.com/pkg/errors" 23 ) 24 25 // The chaincode package is simply a .tar.gz file. For the time being, we 26 // assume that the package contains a metadata.json file which contains a 27 // 'type', a 'path', and a 'label'. In the future, it would be nice if we 28 // move to a more buildpack type system, rather than the below presented 29 // JAR+manifest type system, but for expediency and incremental changes, 30 // moving to a tar format over the proto format for a user-inspectable 31 // artifact seems like a good step. 32 33 const ( 34 // MetadataFile is the expected location of the metadata json document 35 // in the top level of the chaincode package. 36 MetadataFile = "metadata.json" 37 38 // CodePackageFile is the expected location of the code package in the 39 // top level of the chaincode package 40 CodePackageFile = "code.tar.gz" 41 ) 42 43 //go:generate counterfeiter -o mock/legacy_cc_package_locator.go --fake-name LegacyCCPackageLocator . LegacyCCPackageLocator 44 45 type LegacyCCPackageLocator interface { 46 GetChaincodeDepSpec(nameVersion string) (*pb.ChaincodeDeploymentSpec, error) 47 } 48 49 type FallbackPackageLocator struct { 50 ChaincodePackageLocator *ChaincodePackageLocator 51 LegacyCCPackageLocator LegacyCCPackageLocator 52 } 53 54 func (fpl *FallbackPackageLocator) GetChaincodePackage(packageID string) (*ChaincodePackageMetadata, []byte, io.ReadCloser, error) { 55 // XXX, this path has too many return parameters. We could split it into two calls, 56 // or, we could deserialize the metadata where it's needed. But, as written was the 57 // fastest path to fixing a bug around the mutation of metadata. 58 streamer := fpl.ChaincodePackageLocator.ChaincodePackageStreamer(packageID) 59 if streamer.Exists() { 60 metadata, err := streamer.Metadata() 61 if err != nil { 62 return nil, nil, nil, errors.WithMessagef(err, "error retrieving chaincode package metadata '%s'", packageID) 63 } 64 65 mdBytes, err := streamer.MetadataBytes() 66 if err != nil { 67 return nil, nil, nil, errors.WithMessagef(err, "error retrieving chaincode package metadata bytes '%s'", packageID) 68 } 69 70 tarStream, err := streamer.Code() 71 if err != nil { 72 return nil, nil, nil, errors.WithMessagef(err, "error retrieving chaincode package code '%s'", packageID) 73 } 74 75 return metadata, mdBytes, tarStream, nil 76 } 77 78 cds, err := fpl.LegacyCCPackageLocator.GetChaincodeDepSpec(string(packageID)) 79 if err != nil { 80 return nil, nil, nil, errors.WithMessagef(err, "could not get legacy chaincode package '%s'", packageID) 81 } 82 83 md := &ChaincodePackageMetadata{ 84 Path: cds.ChaincodeSpec.ChaincodeId.Path, 85 Type: cds.ChaincodeSpec.Type.String(), 86 Label: cds.ChaincodeSpec.ChaincodeId.Name, 87 } 88 89 mdBytes, err := json.Marshal(md) 90 91 return md, 92 mdBytes, 93 ioutil.NopCloser(bytes.NewBuffer(cds.CodePackage)), 94 nil 95 } 96 97 type ChaincodePackageLocator struct { 98 ChaincodeDir string 99 } 100 101 func (cpl *ChaincodePackageLocator) ChaincodePackageStreamer(packageID string) *ChaincodePackageStreamer { 102 return &ChaincodePackageStreamer{ 103 PackagePath: filepath.Join(cpl.ChaincodeDir, CCFileName(packageID)), 104 } 105 } 106 107 type ChaincodePackageStreamer struct { 108 PackagePath string 109 } 110 111 func (cps *ChaincodePackageStreamer) Exists() bool { 112 _, err := os.Stat(cps.PackagePath) 113 return err == nil 114 } 115 116 func (cps *ChaincodePackageStreamer) Metadata() (*ChaincodePackageMetadata, error) { 117 tarFileStream, err := cps.File(MetadataFile) 118 if err != nil { 119 return nil, errors.WithMessage(err, "could not get metadata file") 120 } 121 122 defer tarFileStream.Close() 123 124 metadata := &ChaincodePackageMetadata{} 125 err = json.NewDecoder(tarFileStream).Decode(metadata) 126 if err != nil { 127 return nil, errors.WithMessage(err, "could not parse metadata file") 128 } 129 130 return metadata, nil 131 } 132 133 func (cps *ChaincodePackageStreamer) MetadataBytes() ([]byte, error) { 134 tarFileStream, err := cps.File(MetadataFile) 135 if err != nil { 136 return nil, errors.WithMessage(err, "could not get metadata file") 137 } 138 139 defer tarFileStream.Close() 140 141 md, err := ioutil.ReadAll(tarFileStream) 142 if err != nil { 143 return nil, errors.WithMessage(err, "could read metadata file") 144 } 145 146 return md, nil 147 } 148 149 func (cps *ChaincodePackageStreamer) Code() (*TarFileStream, error) { 150 tarFileStream, err := cps.File(CodePackageFile) 151 if err != nil { 152 return nil, errors.WithMessage(err, "could not get code package") 153 } 154 155 return tarFileStream, nil 156 } 157 158 func (cps *ChaincodePackageStreamer) File(name string) (tarFileStream *TarFileStream, err error) { 159 file, err := os.Open(cps.PackagePath) 160 if err != nil { 161 return nil, errors.WithMessagef(err, "could not open chaincode package at '%s'", cps.PackagePath) 162 } 163 164 defer func() { 165 if err != nil { 166 file.Close() 167 } 168 }() 169 170 gzReader, err := gzip.NewReader(file) 171 if err != nil { 172 return nil, errors.Wrapf(err, "error reading as gzip stream") 173 } 174 175 tarReader := tar.NewReader(gzReader) 176 177 for { 178 header, err := tarReader.Next() 179 if err == io.EOF { 180 break 181 } 182 183 if err != nil { 184 return nil, errors.Wrapf(err, "error inspecting next tar header") 185 } 186 187 if header.Name != name { 188 continue 189 } 190 191 if header.Typeflag != tar.TypeReg { 192 return nil, errors.Errorf("tar entry %s is not a regular file, type %v", header.Name, header.Typeflag) 193 } 194 195 return &TarFileStream{ 196 TarFile: tarReader, 197 FileStream: file, 198 }, nil 199 } 200 201 return nil, errors.Errorf("did not find file '%s' in package", name) 202 } 203 204 type TarFileStream struct { 205 TarFile io.Reader 206 FileStream io.Closer 207 } 208 209 func (tfs *TarFileStream) Read(p []byte) (int, error) { 210 return tfs.TarFile.Read(p) 211 } 212 213 func (tfs *TarFileStream) Close() error { 214 return tfs.FileStream.Close() 215 } 216 217 // ChaincodePackage represents the un-tar-ed format of the chaincode package. 218 type ChaincodePackage struct { 219 Metadata *ChaincodePackageMetadata 220 CodePackage []byte 221 DBArtifacts []byte 222 } 223 224 // ChaincodePackageMetadata contains the information necessary to understand 225 // the embedded code package. 226 type ChaincodePackageMetadata struct { 227 Type string `json:"type"` 228 Path string `json:"path"` 229 Label string `json:"label"` 230 } 231 232 // MetadataProvider provides the means to retrieve metadata 233 // information (for instance the DB indexes) from a code package. 234 type MetadataProvider interface { 235 GetDBArtifacts(codePackage []byte) ([]byte, error) 236 } 237 238 // ChaincodePackageParser provides the ability to parse chaincode packages. 239 type ChaincodePackageParser struct { 240 MetadataProvider MetadataProvider 241 } 242 243 // LabelRegexp is the regular expression controlling the allowed characters 244 // for the package label. 245 var LabelRegexp = regexp.MustCompile(`^[[:alnum:]][[:alnum:]_.+-]*$`) 246 247 // ValidateLabel return an error if the provided label contains any invalid 248 // characters, as determined by LabelRegexp. 249 func ValidateLabel(label string) error { 250 if !LabelRegexp.MatchString(label) { 251 return errors.Errorf("invalid label '%s'. Label must be non-empty, can only consist of alphanumerics, symbols from '.+-_', and can only begin with alphanumerics", label) 252 } 253 254 return nil 255 } 256 257 // Parse parses a set of bytes as a chaincode package 258 // and returns the parsed package as a struct 259 func (ccpp ChaincodePackageParser) Parse(source []byte) (*ChaincodePackage, error) { 260 gzReader, err := gzip.NewReader(bytes.NewBuffer(source)) 261 if err != nil { 262 return nil, errors.Wrapf(err, "error reading as gzip stream") 263 } 264 265 tarReader := tar.NewReader(gzReader) 266 267 var codePackage []byte 268 var ccPackageMetadata *ChaincodePackageMetadata 269 for { 270 header, err := tarReader.Next() 271 if err == io.EOF { 272 break 273 } 274 275 if err != nil { 276 return nil, errors.Wrapf(err, "error inspecting next tar header") 277 } 278 279 if header.Typeflag != tar.TypeReg { 280 return nil, errors.Errorf("tar entry %s is not a regular file, type %v", header.Name, header.Typeflag) 281 } 282 283 fileBytes, err := ioutil.ReadAll(tarReader) 284 if err != nil { 285 return nil, errors.Wrapf(err, "could not read %s from tar", header.Name) 286 } 287 288 switch header.Name { 289 290 case MetadataFile: 291 ccPackageMetadata = &ChaincodePackageMetadata{} 292 err := json.Unmarshal(fileBytes, ccPackageMetadata) 293 if err != nil { 294 return nil, errors.Wrapf(err, "could not unmarshal %s as json", MetadataFile) 295 } 296 297 case CodePackageFile: 298 codePackage = fileBytes 299 default: 300 logger.Warningf("Encountered unexpected file '%s' in top level of chaincode package", header.Name) 301 } 302 } 303 304 if codePackage == nil { 305 return nil, errors.Errorf("did not find a code package inside the package") 306 } 307 308 if ccPackageMetadata == nil { 309 return nil, errors.Errorf("did not find any package metadata (missing %s)", MetadataFile) 310 } 311 312 if err := ValidateLabel(ccPackageMetadata.Label); err != nil { 313 return nil, err 314 } 315 316 dbArtifacts, err := ccpp.MetadataProvider.GetDBArtifacts(codePackage) 317 if err != nil { 318 return nil, errors.WithMessage(err, "error retrieving DB artifacts from code package") 319 } 320 321 return &ChaincodePackage{ 322 Metadata: ccPackageMetadata, 323 CodePackage: codePackage, 324 DBArtifacts: dbArtifacts, 325 }, nil 326 }