github.com/yacovm/fabric@v2.0.0-alpha.0.20191128145320-c5d4087dc723+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 Chaincode-Package-Metadata.json file 27 // which contains a 'Type', and optionally a 'Path'. In the future, it would 28 // be nice if we moved to a more buildpack type system, rather than the below 29 // presented JAR+manifest type system, but for expediency and incremental changes, 30 // moving to a tar format over the proto format for a user-inspectable artifact 31 // 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, io.ReadCloser, error) { 55 streamer := fpl.ChaincodePackageLocator.ChaincodePackageStreamer(packageID) 56 if streamer.Exists() { 57 metadata, err := streamer.Metadata() 58 if err != nil { 59 return nil, nil, errors.WithMessagef(err, "error retrieving chaincode package metadata '%s'", packageID) 60 } 61 62 tarStream, err := streamer.Code() 63 if err != nil { 64 return nil, nil, errors.WithMessagef(err, "error retrieving chaincode package code '%s'", packageID) 65 } 66 67 return metadata, tarStream, nil 68 } 69 70 cds, err := fpl.LegacyCCPackageLocator.GetChaincodeDepSpec(string(packageID)) 71 if err != nil { 72 return nil, nil, errors.WithMessagef(err, "could not get legacy chaincode package '%s'", packageID) 73 } 74 75 return &ChaincodePackageMetadata{ 76 Path: cds.ChaincodeSpec.ChaincodeId.Path, 77 Type: cds.ChaincodeSpec.Type.String(), 78 }, 79 ioutil.NopCloser(bytes.NewBuffer(cds.CodePackage)), 80 nil 81 } 82 83 type ChaincodePackageLocator struct { 84 ChaincodeDir string 85 } 86 87 func (cpl *ChaincodePackageLocator) ChaincodePackageStreamer(packageID string) *ChaincodePackageStreamer { 88 return &ChaincodePackageStreamer{ 89 PackagePath: filepath.Join(cpl.ChaincodeDir, CCFileName(packageID)), 90 } 91 } 92 93 type ChaincodePackageStreamer struct { 94 PackagePath string 95 } 96 97 func (cps *ChaincodePackageStreamer) Exists() bool { 98 _, err := os.Stat(cps.PackagePath) 99 return err == nil 100 } 101 102 func (cps *ChaincodePackageStreamer) Metadata() (*ChaincodePackageMetadata, error) { 103 tarFileStream, err := cps.File(MetadataFile) 104 if err != nil { 105 return nil, errors.WithMessage(err, "could not get metadata file") 106 } 107 108 defer tarFileStream.Close() 109 110 metadata := &ChaincodePackageMetadata{} 111 err = json.NewDecoder(tarFileStream).Decode(metadata) 112 if err != nil { 113 return nil, errors.WithMessage(err, "could not parse metadata file") 114 } 115 116 return metadata, nil 117 } 118 119 func (cps *ChaincodePackageStreamer) Code() (*TarFileStream, error) { 120 tarFileStream, err := cps.File(CodePackageFile) 121 if err != nil { 122 return nil, errors.WithMessage(err, "could not get code package") 123 } 124 125 return tarFileStream, nil 126 } 127 128 func (cps *ChaincodePackageStreamer) File(name string) (tarFileStream *TarFileStream, err error) { 129 file, err := os.Open(cps.PackagePath) 130 if err != nil { 131 return nil, errors.WithMessagef(err, "could not open chaincode package at '%s'", cps.PackagePath) 132 } 133 134 defer func() { 135 if err != nil { 136 file.Close() 137 } 138 }() 139 140 gzReader, err := gzip.NewReader(file) 141 if err != nil { 142 return nil, errors.Wrapf(err, "error reading as gzip stream") 143 } 144 145 tarReader := tar.NewReader(gzReader) 146 147 for { 148 header, err := tarReader.Next() 149 if err == io.EOF { 150 break 151 } 152 153 if err != nil { 154 return nil, errors.Wrapf(err, "error inspecting next tar header") 155 } 156 157 if header.Name != name { 158 continue 159 } 160 161 if header.Typeflag != tar.TypeReg { 162 return nil, errors.Errorf("tar entry %s is not a regular file, type %v", header.Name, header.Typeflag) 163 } 164 165 return &TarFileStream{ 166 TarFile: tarReader, 167 FileStream: file, 168 }, nil 169 } 170 171 return nil, errors.Errorf("did not find file '%s' in package", name) 172 } 173 174 type TarFileStream struct { 175 TarFile io.Reader 176 FileStream io.Closer 177 } 178 179 func (tfs *TarFileStream) Read(p []byte) (int, error) { 180 return tfs.TarFile.Read(p) 181 } 182 183 func (tfs *TarFileStream) Close() error { 184 return tfs.FileStream.Close() 185 } 186 187 // ChaincodePackage represents the un-tar-ed format of the chaincode package. 188 type ChaincodePackage struct { 189 Metadata *ChaincodePackageMetadata 190 CodePackage []byte 191 DBArtifacts []byte 192 } 193 194 // ChaincodePackageMetadata contains the information necessary to understand 195 // the embedded code package. 196 type ChaincodePackageMetadata struct { 197 Type string `json:"type"` 198 Path string `json:"path"` 199 Label string `json:"label"` 200 } 201 202 // MetadataProvider provides the means to retrieve metadata 203 // information (for instance the DB indexes) from a code package. 204 type MetadataProvider interface { 205 GetDBArtifacts(codePackage []byte) ([]byte, error) 206 } 207 208 // ChaincodePackageParser provides the ability to parse chaincode packages. 209 type ChaincodePackageParser struct { 210 MetadataProvider MetadataProvider 211 } 212 213 var ( 214 // LabelRegexp is the regular expression controlling 215 // the allowed characters for the package label 216 LabelRegexp = regexp.MustCompile("^[a-zA-Z0-9]+([.+-_][a-zA-Z0-9]+)*$") 217 ) 218 219 func validateLabel(label string) error { 220 if !LabelRegexp.MatchString(label) { 221 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) 222 } 223 224 return nil 225 } 226 227 // Parse parses a set of bytes as a chaincode package 228 // and returns the parsed package as a struct 229 func (ccpp ChaincodePackageParser) Parse(source []byte) (*ChaincodePackage, error) { 230 gzReader, err := gzip.NewReader(bytes.NewBuffer(source)) 231 if err != nil { 232 return nil, errors.Wrapf(err, "error reading as gzip stream") 233 } 234 235 tarReader := tar.NewReader(gzReader) 236 237 var codePackage []byte 238 var ccPackageMetadata *ChaincodePackageMetadata 239 for { 240 header, err := tarReader.Next() 241 if err == io.EOF { 242 break 243 } 244 245 if err != nil { 246 return nil, errors.Wrapf(err, "error inspecting next tar header") 247 } 248 249 if header.Typeflag != tar.TypeReg { 250 return nil, errors.Errorf("tar entry %s is not a regular file, type %v", header.Name, header.Typeflag) 251 } 252 253 fileBytes, err := ioutil.ReadAll(tarReader) 254 if err != nil { 255 return nil, errors.Wrapf(err, "could not read %s from tar", header.Name) 256 } 257 258 switch header.Name { 259 260 case MetadataFile: 261 ccPackageMetadata = &ChaincodePackageMetadata{} 262 err := json.Unmarshal(fileBytes, ccPackageMetadata) 263 if err != nil { 264 return nil, errors.Wrapf(err, "could not unmarshal %s as json", MetadataFile) 265 } 266 267 case CodePackageFile: 268 codePackage = fileBytes 269 default: 270 logger.Warningf("Encountered unexpected file '%s' in top level of chaincode package", header.Name) 271 } 272 } 273 274 if codePackage == nil { 275 return nil, errors.Errorf("did not find a code package inside the package") 276 } 277 278 if ccPackageMetadata == nil { 279 return nil, errors.Errorf("did not find any package metadata (missing %s)", MetadataFile) 280 } 281 282 if err := validateLabel(ccPackageMetadata.Label); err != nil { 283 return nil, err 284 } 285 286 dbArtifacts, err := ccpp.MetadataProvider.GetDBArtifacts(codePackage) 287 if err != nil { 288 return nil, errors.WithMessage(err, "error retrieving DB artifacts from code package") 289 } 290 291 return &ChaincodePackage{ 292 Metadata: ccPackageMetadata, 293 CodePackage: codePackage, 294 DBArtifacts: dbArtifacts, 295 }, nil 296 }