github.com/defanghe/fabric@v2.1.1+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 if err := persistence.ValidateLabel(p.Label); err != nil { 63 return err 64 } 65 66 return nil 67 } 68 69 // PackageCmd returns the cobra command for packaging chaincode 70 func PackageCmd(p *Packager) *cobra.Command { 71 chaincodePackageCmd := &cobra.Command{ 72 Use: "package [outputfile]", 73 Short: "Package a chaincode", 74 Long: "Package a chaincode and write the package to a file.", 75 ValidArgs: []string{"1"}, 76 RunE: func(cmd *cobra.Command, args []string) error { 77 if p == nil { 78 pr := packaging.NewRegistry(packaging.SupportedPlatforms...) 79 80 p = &Packager{ 81 PlatformRegistry: pr, 82 Writer: &persistence.FilesystemIO{}, 83 } 84 } 85 p.Command = cmd 86 87 return p.PackageChaincode(args) 88 }, 89 } 90 flagList := []string{ 91 "label", 92 "lang", 93 "path", 94 "peerAddresses", 95 "tlsRootCertFiles", 96 "connectionProfile", 97 } 98 attachFlags(chaincodePackageCmd, flagList) 99 100 return chaincodePackageCmd 101 } 102 103 // PackageChaincode packages a chaincode. 104 func (p *Packager) PackageChaincode(args []string) error { 105 if p.Command != nil { 106 // Parsing of the command line is done so silence cmd usage 107 p.Command.SilenceUsage = true 108 } 109 110 if len(args) != 1 { 111 return errors.New("invalid number of args. expected only the output file") 112 } 113 p.setInput(args[0]) 114 115 return p.Package() 116 } 117 118 func (p *Packager) setInput(outputFile string) { 119 p.Input = &PackageInput{ 120 OutputFile: outputFile, 121 Path: chaincodePath, 122 Type: chaincodeLang, 123 Label: packageLabel, 124 } 125 } 126 127 // Package packages chaincodes into the package type, 128 // (.tar.gz) used by _lifecycle and writes it to disk 129 func (p *Packager) Package() error { 130 err := p.Input.Validate() 131 if err != nil { 132 return err 133 } 134 135 pkgTarGzBytes, err := p.getTarGzBytes() 136 if err != nil { 137 return err 138 } 139 140 dir, name := filepath.Split(p.Input.OutputFile) 141 // if p.Input.OutputFile is only file name, dir becomes an empty string that creates problem 142 // while invoking 'WriteFile' function below. So, irrespective, translate dir into absolute path 143 if dir, err = filepath.Abs(dir); err != nil { 144 return err 145 } 146 err = p.Writer.WriteFile(dir, name, pkgTarGzBytes) 147 if err != nil { 148 err = errors.Wrapf(err, "error writing chaincode package to %s", p.Input.OutputFile) 149 logger.Error(err.Error()) 150 return err 151 } 152 153 return nil 154 } 155 156 func (p *Packager) getTarGzBytes() ([]byte, error) { 157 payload := bytes.NewBuffer(nil) 158 gw := gzip.NewWriter(payload) 159 tw := tar.NewWriter(gw) 160 161 normalizedPath, err := p.PlatformRegistry.NormalizePath(strings.ToUpper(p.Input.Type), p.Input.Path) 162 if err != nil { 163 return nil, errors.WithMessage(err, "failed to normalize chaincode path") 164 } 165 metadataBytes, err := toJSON(normalizedPath, p.Input.Type, p.Input.Label) 166 if err != nil { 167 return nil, err 168 } 169 err = writeBytesToPackage(tw, "metadata.json", metadataBytes) 170 if err != nil { 171 return nil, errors.Wrap(err, "error writing package metadata to tar") 172 } 173 174 codeBytes, err := p.PlatformRegistry.GetDeploymentPayload(strings.ToUpper(p.Input.Type), p.Input.Path) 175 if err != nil { 176 return nil, errors.WithMessage(err, "error getting chaincode bytes") 177 } 178 179 codePackageName := "code.tar.gz" 180 181 err = writeBytesToPackage(tw, codePackageName, codeBytes) 182 if err != nil { 183 return nil, errors.Wrap(err, "error writing package code bytes to tar") 184 } 185 186 err = tw.Close() 187 if err == nil { 188 err = gw.Close() 189 } 190 if err != nil { 191 return nil, errors.Wrapf(err, "failed to create tar for chaincode") 192 } 193 194 return payload.Bytes(), nil 195 } 196 197 func writeBytesToPackage(tw *tar.Writer, name string, payload []byte) error { 198 err := tw.WriteHeader(&tar.Header{ 199 Name: name, 200 Size: int64(len(payload)), 201 Mode: 0100644, 202 }) 203 if err != nil { 204 return err 205 } 206 207 _, err = tw.Write(payload) 208 if err != nil { 209 return err 210 } 211 212 return nil 213 } 214 215 // PackageMetadata holds the path and type for a chaincode package 216 type PackageMetadata struct { 217 Path string `json:"path"` 218 Type string `json:"type"` 219 Label string `json:"label"` 220 } 221 222 func toJSON(path, ccType, label string) ([]byte, error) { 223 metadata := &PackageMetadata{ 224 Path: path, 225 Type: ccType, 226 Label: label, 227 } 228 229 metadataBytes, err := json.Marshal(metadata) 230 if err != nil { 231 return nil, errors.Wrap(err, "failed to marshal chaincode package metadata into JSON") 232 } 233 234 return metadataBytes, nil 235 }