github.com/yous1230/fabric@v2.0.0-beta.0.20191224111736-74345bee6ac2+incompatible/internal/peer/lifecycle/chaincode/package.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package chaincode 8 9 import ( 10 "archive/tar" 11 "bytes" 12 "compress/gzip" 13 "encoding/json" 14 "path/filepath" 15 "strings" 16 17 "github.com/hyperledger/fabric/core/chaincode/persistence" 18 "github.com/hyperledger/fabric/internal/peer/packaging" 19 "github.com/pkg/errors" 20 "github.com/spf13/cobra" 21 ) 22 23 // PlatformRegistry defines the interface to get the code bytes 24 // for a chaincode given the type and path 25 type PlatformRegistry interface { 26 GetDeploymentPayload(ccType, path string) ([]byte, error) 27 NormalizePath(ccType, path string) (string, error) 28 } 29 30 // Packager holds the dependencies needed to package 31 // a chaincode and write it 32 type Packager struct { 33 Command *cobra.Command 34 Input *PackageInput 35 PlatformRegistry PlatformRegistry 36 Writer Writer 37 } 38 39 // PackageInput holds the input parameters for packaging a 40 // ChaincodeInstallPackage 41 type PackageInput struct { 42 OutputFile string 43 Path string 44 Type string 45 Label string 46 } 47 48 // Validate checks for the required inputs 49 func (p *PackageInput) Validate() error { 50 if p.Path == "" { 51 return errors.New("chaincode path must be specified") 52 } 53 if p.Type == "" { 54 return errors.New("chaincode language must be specified") 55 } 56 if p.OutputFile == "" { 57 return errors.New("output file must be specified") 58 } 59 if p.Label == "" { 60 return errors.New("package label must be specified") 61 } 62 63 return nil 64 } 65 66 // PackageCmd returns the cobra command for packaging chaincode 67 func PackageCmd(p *Packager) *cobra.Command { 68 chaincodePackageCmd := &cobra.Command{ 69 Use: "package [outputfile]", 70 Short: "Package a chaincode", 71 Long: "Package a chaincode and write the package to a file.", 72 ValidArgs: []string{"1"}, 73 RunE: func(cmd *cobra.Command, args []string) error { 74 if p == nil { 75 pr := packaging.NewRegistry(packaging.SupportedPlatforms...) 76 77 p = &Packager{ 78 PlatformRegistry: pr, 79 Writer: &persistence.FilesystemIO{}, 80 } 81 } 82 p.Command = cmd 83 84 return p.PackageChaincode(args) 85 }, 86 } 87 flagList := []string{ 88 "label", 89 "lang", 90 "path", 91 "peerAddresses", 92 "tlsRootCertFiles", 93 "connectionProfile", 94 } 95 attachFlags(chaincodePackageCmd, flagList) 96 97 return chaincodePackageCmd 98 } 99 100 // PackageChaincode packages a chaincode. 101 func (p *Packager) PackageChaincode(args []string) error { 102 if p.Command != nil { 103 // Parsing of the command line is done so silence cmd usage 104 p.Command.SilenceUsage = true 105 } 106 107 if len(args) != 1 { 108 return errors.New("invalid number of args. expected only the output file") 109 } 110 p.setInput(args[0]) 111 112 return p.Package() 113 } 114 115 func (p *Packager) setInput(outputFile string) { 116 p.Input = &PackageInput{ 117 OutputFile: outputFile, 118 Path: chaincodePath, 119 Type: chaincodeLang, 120 Label: packageLabel, 121 } 122 } 123 124 // Package packages chaincodes into the package type, 125 // (.tar.gz) used by _lifecycle and writes it to disk 126 func (p *Packager) Package() error { 127 err := p.Input.Validate() 128 if err != nil { 129 return err 130 } 131 132 pkgTarGzBytes, err := p.getTarGzBytes() 133 if err != nil { 134 return err 135 } 136 137 dir, name := filepath.Split(p.Input.OutputFile) 138 // if p.Input.OutputFile is only file name, dir becomes an empty string that creates problem 139 // while invoking 'WriteFile' function below. So, irrespective, translate dir into absolute path 140 if dir, err = filepath.Abs(dir); err != nil { 141 return err 142 } 143 err = p.Writer.WriteFile(dir, name, pkgTarGzBytes) 144 if err != nil { 145 err = errors.Wrapf(err, "error writing chaincode package to %s", p.Input.OutputFile) 146 logger.Error(err.Error()) 147 return err 148 } 149 150 return nil 151 } 152 153 func (p *Packager) getTarGzBytes() ([]byte, error) { 154 payload := bytes.NewBuffer(nil) 155 gw := gzip.NewWriter(payload) 156 tw := tar.NewWriter(gw) 157 158 normalizedPath, err := p.PlatformRegistry.NormalizePath(strings.ToUpper(p.Input.Type), p.Input.Path) 159 if err != nil { 160 return nil, errors.WithMessage(err, "failed to normalize chaincode path") 161 } 162 metadataBytes, err := toJSON(normalizedPath, p.Input.Type, p.Input.Label) 163 if err != nil { 164 return nil, err 165 } 166 err = writeBytesToPackage(tw, "metadata.json", metadataBytes) 167 if err != nil { 168 return nil, errors.Wrap(err, "error writing package metadata to tar") 169 } 170 171 codeBytes, err := p.PlatformRegistry.GetDeploymentPayload(strings.ToUpper(p.Input.Type), p.Input.Path) 172 if err != nil { 173 return nil, errors.WithMessage(err, "error getting chaincode bytes") 174 } 175 176 codePackageName := "code.tar.gz" 177 178 err = writeBytesToPackage(tw, codePackageName, codeBytes) 179 if err != nil { 180 return nil, errors.Wrap(err, "error writing package code bytes to tar") 181 } 182 183 err = tw.Close() 184 if err == nil { 185 err = gw.Close() 186 } 187 if err != nil { 188 return nil, errors.Wrapf(err, "failed to create tar for chaincode") 189 } 190 191 return payload.Bytes(), nil 192 } 193 194 func writeBytesToPackage(tw *tar.Writer, name string, payload []byte) error { 195 err := tw.WriteHeader(&tar.Header{ 196 Name: name, 197 Size: int64(len(payload)), 198 Mode: 0100644, 199 }) 200 if err != nil { 201 return err 202 } 203 204 _, err = tw.Write(payload) 205 if err != nil { 206 return err 207 } 208 209 return nil 210 } 211 212 // PackageMetadata holds the path and type for a chaincode package 213 type PackageMetadata struct { 214 Path string `json:"path"` 215 Type string `json:"type"` 216 Label string `json:"label"` 217 } 218 219 func toJSON(path, ccType, label string) ([]byte, error) { 220 metadata := &PackageMetadata{ 221 Path: path, 222 Type: ccType, 223 Label: label, 224 } 225 226 metadataBytes, err := json.Marshal(metadata) 227 if err != nil { 228 return nil, errors.Wrap(err, "failed to marshal chaincode package metadata into JSON") 229 } 230 231 return metadataBytes, nil 232 }