Skip to content

Commit 373dc6d

Browse files
denyeartChris Elder
authored andcommitted
[FAB-8446] Add couchdb index validation to LSCC
The existing validation for packaged couchdb indexes in chaincode was only getting called if peer CLI packaged the index during chaincode install (client side). Need the same validation logic called on server side of chaincocde install, so that the same validation logic will be in effect regardless of whether a peer CLI client or an SDK client performed the chaincode install. This implies making the same validation logic available in the LSCC chaincode install function. The benefit is that each client does not need to perform their own validation, they can rely on the server side LSCC validation, and any validation errors will get returned to them on the chaincode install failure response. This change ensures that the same validation is called during peer CLI packaging and during LSCC install chaincode. Change-Id: I44692141f6efe430fd5e298c8df7d59e519ce028 Signed-off-by: David Enyeart <enyeart@us.ibm.com>
1 parent 3c38415 commit 373dc6d

File tree

9 files changed

+192
-144
lines changed

9 files changed

+192
-144
lines changed

core/chaincode/platforms/golang/platform.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"compress/gzip"
2323
"errors"
2424
"fmt"
25+
"io/ioutil"
2526
"net/url"
2627
"os"
2728
"path/filepath"
@@ -428,22 +429,21 @@ func (goPlatform *Platform) GetDeploymentPayload(spec *pb.ChaincodeSpec) ([]byte
428429

429430
for _, file := range files {
430431

432+
// file.Path represents os localpath
433+
// file.Name represents tar packagepath
434+
431435
// If the file is metadata rather than golang code, remove the leading go code path, for example:
432436
// original file.Name: src/github.com/hyperledger/fabric/examples/chaincode/go/marbles02/META-INF/statedb/couchdb/indexes/indexOwner.json
433437
// updated file.Name: META-INF/statedb/couchdb/indexes/indexOwner.json
434438
if file.IsMetadata {
435439

436-
// Ensure META-INF directory can be found, then grab the META-INF relative path to use for packaging
437-
if !strings.HasPrefix(file.Name, filepath.Join("src", code.Pkg, "META-INF")) {
438-
return nil, fmt.Errorf("Could not find META-INF directory in metadata file %s.", file.Name)
439-
}
440440
file.Name, err = filepath.Rel(filepath.Join("src", code.Pkg), file.Name)
441441
if err != nil {
442-
return nil, fmt.Errorf("Could not get relative path for META-INF directory %s. Error:%s", file.Name, err)
442+
return nil, fmt.Errorf("This error was caused by bad packaging of the metadata. The file [%s] is marked as MetaFile, however not located under META-INF Error:[%s]", file.Name, err)
443443
}
444444

445-
// Split the filename itself from its path
446-
_, filename := filepath.Split(file.Name)
445+
// Split the tar location (file.Name) into a tar package directory and filename
446+
packageDir, filename := filepath.Split(file.Name)
447447

448448
// Hidden files are not supported as metadata, therefore ignore them.
449449
// User often doesn't know that hidden files are there, and may not be able to delete them, therefore warn user rather than error out.
@@ -452,9 +452,15 @@ func (goPlatform *Platform) GetDeploymentPayload(spec *pb.ChaincodeSpec) ([]byte
452452
continue
453453
}
454454

455+
fileBytes, err := ioutil.ReadFile(file.Path)
456+
if err != nil {
457+
return nil, err
458+
}
459+
455460
// Validate metadata file for inclusion in tar
456461
// Validation is based on the passed metadata directory, e.g. META-INF/statedb/couchdb/indexes
457-
err = ccmetadata.ValidateMetadataFile(file.Path, filepath.Dir(file.Name))
462+
// Clean metadata directory to remove trailing slash
463+
err = ccmetadata.ValidateMetadataFile(filename, fileBytes, filepath.Clean(packageDir))
458464
if err != nil {
459465
return nil, err
460466
}

core/common/ccprovider/cc_statedb_artifacts_provider.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,21 @@ import (
1111
"bytes"
1212
"compress/gzip"
1313
"io"
14+
"io/ioutil"
15+
"path/filepath"
1416
"strings"
1517
)
1618

1719
const (
1820
ccPackageStatedbDir = "META-INF/statedb/"
1921
)
2022

23+
// tarFileEntry encapsulates a file entry and it's contents inside a tar
24+
type TarFileEntry struct {
25+
FileHeader *tar.Header
26+
FileContent []byte
27+
}
28+
2129
// ExtractStatedbArtifactsAsTarbytes extracts the statedb artifacts from the code package tar and create a statedb artifact tar.
2230
// The state db artifacts are expected to contain state db specific artifacts such as index specification in the case of couchdb.
2331
// This function is intented to be used during chaincode instantiate/upgrade so that statedb artifacts can be created.
@@ -83,3 +91,35 @@ func ExtractStatedbArtifactsFromCCPackage(ccpackage CCPackage) (statedbArtifacts
8391
ccproviderLogger.Debug("Created statedb artifact tar")
8492
return statedbTarBuffer.Bytes(), nil
8593
}
94+
95+
// ExtractFileEntries extract file entries from the given `tarBytes`. A file entry is included in the
96+
// returned results only if it is located in the dir specified in the `filterDirs` parameter
97+
func ExtractFileEntries(tarBytes []byte, filterDirs map[string]bool) ([]*TarFileEntry, error) {
98+
var fileEntries []*TarFileEntry
99+
//initialize a tar reader
100+
tarReader := tar.NewReader(bytes.NewReader(tarBytes))
101+
for {
102+
//read the next header from the tar
103+
tarHeader, err := tarReader.Next()
104+
//if the EOF is detected, then exit
105+
if err == io.EOF {
106+
// end of tar archive
107+
break
108+
}
109+
if err != nil {
110+
return nil, err
111+
}
112+
ccproviderLogger.Debugf("Processing entry from tar: %s", tarHeader.Name)
113+
//Ensure that this is a file located in the dir present in the 'filterDirs'
114+
if !tarHeader.FileInfo().IsDir() && filterDirs[filepath.Dir(tarHeader.Name)] {
115+
ccproviderLogger.Debugf("Selecting file entry from tar: %s", tarHeader.Name)
116+
//read the tar entry into a byte array
117+
fileContent, err := ioutil.ReadAll(tarReader)
118+
if err != nil {
119+
return nil, err
120+
}
121+
fileEntries = append(fileEntries, &TarFileEntry{tarHeader, fileContent})
122+
}
123+
}
124+
return fileEntries, nil
125+
}

core/common/ccprovider/metadata/validators.go

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ package metadata
99
import (
1010
"encoding/json"
1111
"fmt"
12-
"io/ioutil"
1312
"path/filepath"
1413
"reflect"
1514
"strings"
@@ -20,7 +19,7 @@ import (
2019
var logger = flogging.MustGetLogger("metadata")
2120

2221
// fileValidators are used as handlers to validate specific metadata directories
23-
type fileValidator func(srcPath string) error
22+
type fileValidator func(fileName string, fileBytes []byte) error
2423

2524
// Currently, the only metadata expected and allowed is for META-INF/statedb/couchdb/indexes.
2625
var fileValidators = map[string]fileValidator{
@@ -56,8 +55,7 @@ func (e *InvalidIndexContentError) Error() string {
5655

5756
// ValidateMetadataFile checks that metadata files are valid
5857
// according to the validation rules of the metadata directory (metadataType)
59-
func ValidateMetadataFile(srcPath, metadataType string) error {
60-
58+
func ValidateMetadataFile(fileName string, fileBytes []byte, metadataType string) error {
6159
// Get the validator handler for the metadata directory
6260
fileValidator, ok := fileValidators[metadataType]
6361

@@ -66,8 +64,8 @@ func ValidateMetadataFile(srcPath, metadataType string) error {
6664
return &UnhandledDirectoryError{fmt.Sprintf("Metadata not supported in directory: %s", metadataType)}
6765
}
6866

69-
// If the file is not valid for the given metadata directory, return an error
70-
err := fileValidator(srcPath)
67+
// If the file is not valid for the given metadata directory, return the corresponding error
68+
err := fileValidator(fileName, fileBytes)
7169
if err != nil {
7270
return err
7371
}
@@ -77,30 +75,25 @@ func ValidateMetadataFile(srcPath, metadataType string) error {
7775
}
7876

7977
// couchdbIndexFileValidator implements fileValidator
80-
func couchdbIndexFileValidator(srcPath string) error {
78+
func couchdbIndexFileValidator(fileName string, fileBytes []byte) error {
8179

82-
ext := filepath.Ext(srcPath)
80+
ext := filepath.Ext(fileName)
8381

8482
// if the file does not have a .json extension, then return as error
8583
if ext != ".json" {
86-
return &BadExtensionError{fmt.Sprintf("Index metadata file [%s] does not have a .json extension", srcPath)}
87-
}
88-
89-
fileBytes, err := ioutil.ReadFile(srcPath)
90-
if err != nil {
91-
return err
84+
return &BadExtensionError{fmt.Sprintf("Index metadata file [%s] does not have a .json extension", fileName)}
9285
}
9386

9487
// if the content does not validate as JSON, return err to invalidate the file
9588
boolIsJSON, indexDefinition := isJSON(fileBytes)
9689
if !boolIsJSON {
97-
return &InvalidIndexContentError{fmt.Sprintf("Index metadata file [%s] is not a valid JSON", srcPath)}
90+
return &InvalidIndexContentError{fmt.Sprintf("Index metadata file [%s] is not a valid JSON", fileName)}
9891
}
9992

10093
// validate the index definition
101-
err = validateIndexJSON(indexDefinition)
94+
err := validateIndexJSON(indexDefinition)
10295
if err != nil {
103-
return &InvalidIndexContentError{fmt.Sprintf("Index metadata file [%s] is not a valid index definition: %s", srcPath, err)}
96+
return &InvalidIndexContentError{fmt.Sprintf("Index metadata file [%s] is not a valid index definition: %s", fileName, err)}
10497
}
10598

10699
return nil

core/common/ccprovider/metadata/validators_test.go

Lines changed: 20 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,11 @@ func TestGoodIndexJSON(t *testing.T) {
2222
cleanupDir(testDir)
2323
defer cleanupDir(testDir)
2424

25-
filename := filepath.Join(testDir, "META-INF/statedb/couchdb/indexes", "myIndex.json")
26-
filebytes := []byte(`{"index":{"fields":["data.docType","data.owner"]},"name":"indexOwner","type":"json"}`)
25+
fileName := "myIndex.json"
26+
fileBytes := []byte(`{"index":{"fields":["data.docType","data.owner"]},"name":"indexOwner","type":"json"}`)
27+
metadataType := "META-INF/statedb/couchdb/indexes"
2728

28-
err := writeToFile(filename, filebytes)
29-
assert.NoError(t, err, "Error writing to file")
30-
31-
err = ValidateMetadataFile(filename, "META-INF/statedb/couchdb/indexes")
29+
err := ValidateMetadataFile(fileName, fileBytes, metadataType)
3230
assert.NoError(t, err, "Error validating a good index")
3331
}
3432

@@ -37,13 +35,11 @@ func TestBadIndexJSON(t *testing.T) {
3735
cleanupDir(testDir)
3836
defer cleanupDir(testDir)
3937

40-
filename := filepath.Join(testDir, "META-INF/statedb/couchdb/indexes", "myIndex.json")
41-
filebytes := []byte("invalid json")
42-
43-
err := writeToFile(filename, filebytes)
44-
assert.NoError(t, err, "Error writing to file")
38+
fileName := "myIndex.json"
39+
fileBytes := []byte("invalid json")
40+
metadataType := "META-INF/statedb/couchdb/indexes"
4541

46-
err = ValidateMetadataFile(filename, "META-INF/statedb/couchdb/indexes")
42+
err := ValidateMetadataFile(fileName, fileBytes, metadataType)
4743

4844
assert.Error(t, err, "Should have received an InvalidIndexContentError")
4945

@@ -59,14 +55,12 @@ func TestIndexWrongLocation(t *testing.T) {
5955
cleanupDir(testDir)
6056
defer cleanupDir(testDir)
6157

58+
fileName := "myIndex.json"
59+
fileBytes := []byte(`{"index":{"fields":["data.docType","data.owner"]},"name":"indexOwner","type":"json"}`)
6260
// place the index one directory too high
63-
filename := filepath.Join(testDir, "META-INF/statedb/couchdb", "myIndex.json")
64-
filebytes := []byte("invalid json")
61+
metadataType := "META-INF/statedb/couchdb"
6562

66-
err := writeToFile(filename, filebytes)
67-
assert.NoError(t, err, "Error writing to file")
68-
69-
err = ValidateMetadataFile(filename, "META-INF/statedb/couchdb")
63+
err := ValidateMetadataFile(fileName, fileBytes, metadataType)
7064
assert.Error(t, err, "Should have received an UnhandledDirectoryError")
7165

7266
// Type assertion on UnhandledDirectoryError
@@ -81,48 +75,28 @@ func TestInvalidMetadataType(t *testing.T) {
8175
cleanupDir(testDir)
8276
defer cleanupDir(testDir)
8377

84-
filename := filepath.Join(testDir, "META-INF/statedb/couchdb/indexes", "myIndex.json")
85-
filebytes := []byte("invalid json")
86-
87-
err := writeToFile(filename, filebytes)
88-
assert.NoError(t, err, "Error writing to file")
78+
fileName := "myIndex.json"
79+
fileBytes := []byte(`{"index":{"fields":["data.docType","data.owner"]},"name":"indexOwner","type":"json"}`)
80+
metadataType := "Invalid metadata type"
8981

90-
err = ValidateMetadataFile(filename, "Invalid metadata type")
82+
err := ValidateMetadataFile(fileName, fileBytes, metadataType)
9183
assert.Error(t, err, "Should have received an UnhandledDirectoryError")
9284

9385
// Type assertion on UnhandledDirectoryError
9486
_, ok := err.(*UnhandledDirectoryError)
9587
assert.True(t, ok, "Should have received an UnhandledDirectoryError")
9688
}
9789

98-
func TestCantReadFile(t *testing.T) {
99-
testDir := filepath.Join(packageTestDir, "CantReadFile")
100-
cleanupDir(testDir)
101-
defer cleanupDir(testDir)
102-
103-
filename := filepath.Join(testDir, "META-INF/statedb/couchdb/indexes", "myIndex.json")
104-
105-
// Don't write the file - test for can't read file
106-
// err := writeToFile(filename, filebytes)
107-
// assert.NoError(t, err, "Error writing to file")
108-
109-
err := ValidateMetadataFile(filename, "META-INF/statedb/couchdb/indexes")
110-
assert.Error(t, err, "Should have received error reading file")
111-
112-
}
113-
11490
func TestBadMetadataExtension(t *testing.T) {
11591
testDir := filepath.Join(packageTestDir, "BadMetadataExtension")
11692
cleanupDir(testDir)
11793
defer cleanupDir(testDir)
11894

119-
filename := filepath.Join(testDir, "META-INF/statedb/couchdb/indexes", "myIndex.go")
120-
filebytes := []byte(`{"index":{"fields":["data.docType","data.owner"]},"name":"indexOwner","type":"json"}`)
121-
122-
err := writeToFile(filename, filebytes)
123-
assert.NoError(t, err, "Error writing to file")
95+
fileName := "myIndex.go"
96+
fileBytes := []byte(`{"index":{"fields":["data.docType","data.owner"]},"name":"indexOwner","type":"json"}`)
97+
metadataType := "META-INF/statedb/couchdb/indexes"
12498

125-
err = ValidateMetadataFile(filename, "META-INF/statedb/couchdb/indexes")
99+
err := ValidateMetadataFile(fileName, fileBytes, metadataType)
126100
assert.Error(t, err, "Should have received an BadExtensionError")
127101

128102
// Type assertion on BadExtensionError

core/container/util/writer.go

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ import (
1111
"bufio"
1212
"fmt"
1313
"io"
14+
"io/ioutil"
1415
"os"
1516
"path/filepath"
1617
"strings"
1718
"time"
1819

1920
"github.com/hyperledger/fabric/common/flogging"
20-
ccmetadata "github.com/hyperledger/fabric/core/common/ccprovider/metadata"
21+
"github.com/hyperledger/fabric/core/common/ccprovider/metadata"
2122
"github.com/pkg/errors"
2223
)
2324

@@ -49,10 +50,10 @@ func WriteFolderToTarPackage(tw *tar.Writer, srcPath string, excludeDir string,
4950
}
5051

5152
rootDirLen := len(rootDirectory)
52-
walkFn := func(path string, info os.FileInfo, err error) error {
53+
walkFn := func(localpath string, info os.FileInfo, err error) error {
5354

54-
// If path includes .git, ignore
55-
if strings.Contains(path, ".git") {
55+
// If localpath includes .git, ignore
56+
if strings.Contains(localpath, ".git") {
5657
return nil
5758
}
5859

@@ -61,15 +62,15 @@ func WriteFolderToTarPackage(tw *tar.Writer, srcPath string, excludeDir string,
6162
}
6263

6364
//exclude any files with excludeDir prefix. They should already be in the tar
64-
if excludeDir != "" && strings.Index(path, excludeDir) == rootDirLen+1 {
65+
if excludeDir != "" && strings.Index(localpath, excludeDir) == rootDirLen+1 {
6566
//1 for "/"
6667
return nil
6768
}
6869
// Because of scoping we can reference the external rootDirectory variable
69-
if len(path[rootDirLen:]) == 0 {
70+
if len(localpath[rootDirLen:]) == 0 {
7071
return nil
7172
}
72-
ext := filepath.Ext(path)
73+
ext := filepath.Ext(localpath)
7374

7475
if includeFileTypeMap != nil {
7576
// we only want 'fileTypes' source files at this point
@@ -85,35 +86,41 @@ func WriteFolderToTarPackage(tw *tar.Writer, srcPath string, excludeDir string,
8586
}
8687
}
8788

88-
var newPath string
89+
var packagepath string
8990

9091
// if file is metadata, keep the /META-INF directory, e.g: META-INF/statedb/couchdb/indexes/indexOwner.json
9192
// otherwise file is source code, put it in /src dir, e.g: src/marbles_chaincode.js
92-
if strings.HasPrefix(path, filepath.Join(rootDirectory, "META-INF")) {
93-
newPath = path[rootDirLen+1:]
93+
if strings.HasPrefix(localpath, filepath.Join(rootDirectory, "META-INF")) {
94+
packagepath = localpath[rootDirLen+1:]
9495

95-
// Split the filename itself from its path
96-
_, filename := filepath.Split(newPath)
96+
// Split the tar packagepath into a tar package directory and filename
97+
packageDir, filename := filepath.Split(packagepath)
9798

9899
// Hidden files are not supported as metadata, therefore ignore them.
99100
// User often doesn't know that hidden files are there, and may not be able to delete them, therefore warn user rather than error out.
100101
if strings.HasPrefix(filename, ".") {
101-
vmLogger.Warningf("Ignoring hidden file in metadata directory: %s", newPath)
102+
vmLogger.Warningf("Ignoring hidden file in metadata directory: %s", packagepath)
102103
return nil
103104
}
104105

106+
fileBytes, err := ioutil.ReadFile(localpath)
107+
if err != nil {
108+
return err
109+
}
110+
105111
// Validate metadata file for inclusion in tar
106112
// Validation is based on the passed metadata directory, e.g. META-INF/statedb/couchdb/indexes
107-
err = ccmetadata.ValidateMetadataFile(path, filepath.Dir(newPath))
113+
// Clean metadata directory to remove trailing slash
114+
err = metadata.ValidateMetadataFile(filename, fileBytes, filepath.Clean(packageDir))
108115
if err != nil {
109116
return err
110117
}
111118

112119
} else { // file is not metadata, include in src
113-
newPath = fmt.Sprintf("src%s", path[rootDirLen:])
120+
packagepath = fmt.Sprintf("src%s", localpath[rootDirLen:])
114121
}
115122

116-
err = WriteFileToPackage(path, newPath, tw)
123+
err = WriteFileToPackage(localpath, packagepath, tw)
117124
if err != nil {
118125
return fmt.Errorf("Error writing file to package: %s", err)
119126
}

0 commit comments

Comments
 (0)