diff --git a/.gitignore b/.gitignore index 47cb050..f276a92 100644 --- a/.gitignore +++ b/.gitignore @@ -16,5 +16,6 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out -vendor/ +vendor*/ _output/ +**/go-mock-reflect* diff --git a/Makefile b/Makefile index c02082c..91b4004 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ #---------------------------------------------------------------------------------- ROOTDIR := $(shell pwd) -PACKAGE_PATH:=github.com/solo-io/protodep +PACKAGE_PATH:=github.com/solo-io/anyvendor OUTPUT_DIR ?= $(ROOTDIR)/_output SOURCES := $(shell find . -name "*.go" | grep -v test.go) VERSION ?= $(shell git describe --tags) @@ -23,10 +23,10 @@ init: .PHONY: update-deps update-deps: mod-download - $(shell cd vendor/github.com/solo-io/protoc-gen-ext; make install) GO111MODULE=off go get -u golang.org/x/tools/cmd/goimports GO111MODULE=off go get -u github.com/golang/protobuf/protoc-gen-go GO111MODULE=off go get -u github.com/envoyproxy/protoc-gen-validate + GO111MODULE=off go install github.com/envoyproxy/protoc-gen-validate GO111MODULE=off go get -u github.com/golang/mock/gomock GO111MODULE=off go install github.com/golang/mock/mockgen @@ -42,10 +42,9 @@ mod-download: .PHONY: generated-code generated-code: $(OUTPUT_DIR)/.generated-code -SUBDIRS:=pkg test +SUBDIRS:=pkg anyvendor $(OUTPUT_DIR)/.generated-code: mkdir -p ${OUTPUT_DIR} $(GO_BUILD_FLAGS) go generate ./... - gofmt -w $(SUBDIRS) goimports -w $(SUBDIRS) touch $@ diff --git a/README.md b/README.md index bf84ed3..51fb323 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ -# protodep +# anyvendor -protodep is an all purpose dependency management tool originally created to manage +anyvendor is an all purpose dependency management tool originally created to manage vendoring protobuf files. However, it can also handle any non-language specific files available through it's multiple gathering mechanisms. ## configuration -protodep is currently only available as a library, but the plan is to turn it into a standalone tool. +anyvendor is currently only available as a library, but the plan is to turn it into a standalone tool. -To use protodep create a new protodep manager by calling `NewManager()` and supplying the working -directory of the project. protodep is meant to work at any level of a repo/project, so therefore +To use anyvendor create a new anyvendor manager by calling `NewManager()` and supplying the working +directory of the project. anyvendor is meant to work at any level of a repo/project, so therefore the working directory must be supplied. Then the `Ensure` function can be called to vendor in -all of the deps. The api for the `Ensure` function is reflected in the `protodep.proto` file in this +all of the deps. The api for the `Ensure` function is reflected in the `anyvendor.proto` file in this directory. Currently only gomod style dependencies are enabled, but git repo ones are coming soon. @@ -36,7 +36,7 @@ imports: patterns: - api/**/*.proto ``` -The package is the name of the gomod package which protodep will search for the files. It will call +The package is the name of the gomod package which anyvendor will search for the files. It will call `go list -m all` to find the correct version, and then search the local go mod cache for it. In order to use a package which is not explicitly required by any go projects, it can be brought in using the `tools.go` pattern. More information on tools in go mod can be found [here](https://github.com/golang/go/wiki/Modules#how-can-i-track-tool-dependencies-for-a-module). diff --git a/protodep/protodep.pb.go b/anyvendor/anyvendor.pb.go similarity index 66% rename from protodep/protodep.pb.go rename to anyvendor/anyvendor.pb.go index c0104ee..88bbeeb 100644 --- a/protodep/protodep.pb.go +++ b/anyvendor/anyvendor.pb.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-go. DO NOT EDIT. -// source: protodep.proto +// source: anyvendor.proto -package protodep +package anyvendor import ( fmt "fmt" @@ -22,6 +22,11 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package +// +//Config object used for running anyvendor. The top level config consists of 2 main sections. +// +//Local is a set of matchers will be taken directly from the local module, and vendored in. +//Imports is a list of import types which will be run, and then vendored. type Config struct { Local *Local `protobuf:"bytes,1,opt,name=local,proto3" json:"local,omitempty"` Imports []*Import `protobuf:"bytes,2,rep,name=imports,proto3" json:"imports,omitempty"` @@ -34,7 +39,7 @@ func (m *Config) Reset() { *m = Config{} } func (m *Config) String() string { return proto.CompactTextString(m) } func (*Config) ProtoMessage() {} func (*Config) Descriptor() ([]byte, []int) { - return fileDescriptor_7fec50c21b53b759, []int{0} + return fileDescriptor_2a8ec572c73c9b71, []int{0} } func (m *Config) XXX_Unmarshal(b []byte) error { @@ -82,7 +87,7 @@ func (m *Import) Reset() { *m = Import{} } func (m *Import) String() string { return proto.CompactTextString(m) } func (*Import) ProtoMessage() {} func (*Import) Descriptor() ([]byte, []int) { - return fileDescriptor_7fec50c21b53b759, []int{1} + return fileDescriptor_2a8ec572c73c9b71, []int{1} } func (m *Import) XXX_Unmarshal(b []byte) error { @@ -134,6 +139,7 @@ func (*Import) XXX_OneofWrappers() []interface{} { } } +// A set of glob patters to be grabbed from the current module type Local struct { Patterns []string `protobuf:"bytes,1,rep,name=patterns,proto3" json:"patterns,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` @@ -145,7 +151,7 @@ func (m *Local) Reset() { *m = Local{} } func (m *Local) String() string { return proto.CompactTextString(m) } func (*Local) ProtoMessage() {} func (*Local) Descriptor() ([]byte, []int) { - return fileDescriptor_7fec50c21b53b759, []int{2} + return fileDescriptor_2a8ec572c73c9b71, []int{2} } func (m *Local) XXX_Unmarshal(b []byte) error { @@ -173,6 +179,15 @@ func (m *Local) GetPatterns() []string { return nil } +// +//A go mod import represents a set of imports from a go module +// +//patterns is a set glob matchers to find files in a go module. +// +//package is the name of the go module which these should be pulled from. +// +//The GoModImport uses the command `go list -f '{{.Path}}' -m all` to find +//all of the package names type GoModImport struct { Patterns []string `protobuf:"bytes,1,rep,name=patterns,proto3" json:"patterns,omitempty"` Package string `protobuf:"bytes,2,opt,name=package,proto3" json:"package,omitempty"` @@ -185,7 +200,7 @@ func (m *GoModImport) Reset() { *m = GoModImport{} } func (m *GoModImport) String() string { return proto.CompactTextString(m) } func (*GoModImport) ProtoMessage() {} func (*GoModImport) Descriptor() ([]byte, []int) { - return fileDescriptor_7fec50c21b53b759, []int{3} + return fileDescriptor_2a8ec572c73c9b71, []int{3} } func (m *GoModImport) XXX_Unmarshal(b []byte) error { @@ -221,32 +236,32 @@ func (m *GoModImport) GetPackage() string { } func init() { - proto.RegisterType((*Config)(nil), "protodep.Config") - proto.RegisterType((*Import)(nil), "protodep.Import") - proto.RegisterType((*Local)(nil), "protodep.Local") - proto.RegisterType((*GoModImport)(nil), "protodep.GoModImport") -} - -func init() { proto.RegisterFile("protodep.proto", fileDescriptor_7fec50c21b53b759) } - -var fileDescriptor_7fec50c21b53b759 = []byte{ - // 286 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x5c, 0x8f, 0x41, 0x4a, 0xc3, 0x40, - 0x14, 0x86, 0x9d, 0xc4, 0xa4, 0xed, 0x2b, 0x68, 0x19, 0x11, 0x43, 0x57, 0x25, 0x45, 0x09, 0x05, - 0x13, 0xa8, 0x37, 0x88, 0x0b, 0x15, 0xec, 0x26, 0xb8, 0xaa, 0x0b, 0x99, 0x26, 0x71, 0x1c, 0x92, - 0xf4, 0x0d, 0xc9, 0x28, 0x78, 0x0d, 0x4f, 0x23, 0xae, 0x7a, 0x9d, 0xde, 0x42, 0x32, 0x43, 0x1a, - 0x71, 0x35, 0xef, 0xff, 0xff, 0xef, 0xfd, 0xbc, 0x81, 0x13, 0x59, 0xa3, 0xc2, 0x2c, 0x97, 0xa1, - 0x1e, 0xe8, 0xb0, 0xd3, 0xd3, 0x8b, 0x0f, 0x56, 0x8a, 0x8c, 0xa9, 0x3c, 0xea, 0x06, 0x83, 0xf8, - 0xcf, 0xe0, 0xde, 0xe2, 0xf6, 0x55, 0x70, 0x7a, 0x09, 0x4e, 0x89, 0x29, 0x2b, 0x3d, 0x32, 0x23, - 0xc1, 0x78, 0x79, 0x1a, 0x1e, 0xca, 0x1e, 0x5b, 0x3b, 0x31, 0x29, 0x5d, 0xc0, 0x40, 0x54, 0x12, - 0x6b, 0xd5, 0x78, 0xd6, 0xcc, 0x0e, 0xc6, 0xcb, 0x49, 0x0f, 0x3e, 0xe8, 0x20, 0xe9, 0x00, 0x7f, - 0x05, 0xae, 0xb1, 0x68, 0x08, 0x2e, 0xc7, 0x97, 0x0a, 0x33, 0xcf, 0xd2, 0xed, 0xe7, 0xfd, 0xd2, - 0x1d, 0xae, 0x30, 0x33, 0xd8, 0xfd, 0x51, 0xe2, 0xf0, 0x56, 0xc6, 0x67, 0x00, 0xc6, 0x7a, 0xfa, - 0x94, 0x39, 0x75, 0xbe, 0xf7, 0x3b, 0x9b, 0xf8, 0x73, 0x70, 0xf4, 0x29, 0x74, 0x0a, 0x43, 0xc9, - 0x94, 0xca, 0xeb, 0x6d, 0xe3, 0x91, 0x99, 0x1d, 0x8c, 0x92, 0x83, 0xf6, 0xd7, 0x30, 0xfe, 0xd3, - 0x48, 0xaf, 0xfe, 0xa3, 0x31, 0xfc, 0xec, 0x77, 0xb6, 0xf3, 0x45, 0xac, 0x21, 0xe9, 0xd7, 0xe8, - 0x1c, 0x06, 0x92, 0xa5, 0x05, 0xe3, 0xb9, 0xbe, 0x70, 0x14, 0x8f, 0x5a, 0xec, 0xb8, 0xb6, 0x26, - 0x24, 0xe9, 0x92, 0x78, 0xb1, 0x0e, 0xb8, 0x50, 0x6f, 0xef, 0x9b, 0x30, 0xc5, 0x2a, 0x6a, 0xb0, - 0xc4, 0x6b, 0x81, 0xe6, 0x2d, 0x84, 0x8a, 0x64, 0xc1, 0xa3, 0xee, 0x5b, 0x1b, 0x57, 0x4f, 0x37, - 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x7e, 0x21, 0x9f, 0xde, 0x94, 0x01, 0x00, 0x00, + proto.RegisterType((*Config)(nil), "anyvendor.Config") + proto.RegisterType((*Import)(nil), "anyvendor.Import") + proto.RegisterType((*Local)(nil), "anyvendor.Local") + proto.RegisterType((*GoModImport)(nil), "anyvendor.GoModImport") +} + +func init() { proto.RegisterFile("anyvendor.proto", fileDescriptor_2a8ec572c73c9b71) } + +var fileDescriptor_2a8ec572c73c9b71 = []byte{ + // 283 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4f, 0xcc, 0xab, 0x2c, + 0x4b, 0xcd, 0x4b, 0xc9, 0x2f, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x84, 0x0b, 0x48, + 0x89, 0x97, 0x25, 0xe6, 0x64, 0xa6, 0x24, 0x96, 0xa4, 0xea, 0xc3, 0x18, 0x10, 0x35, 0x4a, 0xb1, + 0x5c, 0x6c, 0xce, 0xf9, 0x79, 0x69, 0x99, 0xe9, 0x42, 0x6a, 0x5c, 0xac, 0x39, 0xf9, 0xc9, 0x89, + 0x39, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0xdc, 0x46, 0x02, 0x7a, 0x08, 0xe3, 0x7c, 0x40, 0xe2, 0x41, + 0x10, 0x69, 0x21, 0x6d, 0x2e, 0xf6, 0xcc, 0xdc, 0x82, 0xfc, 0xa2, 0x92, 0x62, 0x09, 0x26, 0x05, + 0x66, 0x0d, 0x6e, 0x23, 0x41, 0x24, 0x95, 0x9e, 0x60, 0x99, 0x20, 0x98, 0x0a, 0x25, 0x3f, 0x2e, + 0x36, 0x88, 0x90, 0x90, 0x3e, 0x17, 0x5b, 0x7a, 0x7e, 0x7c, 0x6e, 0x7e, 0x8a, 0x04, 0x13, 0xd8, + 0x7c, 0x31, 0x24, 0x5d, 0xee, 0xf9, 0xbe, 0xf9, 0x29, 0x10, 0x75, 0x1e, 0x0c, 0x41, 0xac, 0xe9, + 0x20, 0xae, 0x93, 0x30, 0x17, 0x17, 0x44, 0x28, 0xa4, 0xb2, 0x20, 0x55, 0x88, 0x75, 0xc7, 0xcb, + 0x03, 0xcc, 0x8c, 0x4a, 0xfa, 0x5c, 0xac, 0x60, 0xc7, 0x08, 0xa9, 0x71, 0x71, 0x14, 0x24, 0x96, + 0x94, 0xa4, 0x16, 0xe5, 0x15, 0x4b, 0x30, 0x2a, 0x30, 0x6b, 0x70, 0x3a, 0x71, 0xed, 0x7a, 0x79, + 0x80, 0x99, 0x75, 0x12, 0x23, 0x13, 0x07, 0x63, 0x10, 0x5c, 0x4e, 0x29, 0x8a, 0x8b, 0x1b, 0xc9, + 0x74, 0x62, 0xb5, 0x09, 0x29, 0x73, 0xb1, 0x17, 0x24, 0x26, 0x67, 0x27, 0xa6, 0xa7, 0x82, 0x9d, + 0xcb, 0xe9, 0xc4, 0x09, 0x52, 0xc6, 0x52, 0xc4, 0x24, 0xc0, 0x18, 0x04, 0x93, 0x71, 0xd2, 0x88, + 0x52, 0x4b, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x2f, 0xce, 0xcf, 0xc9, + 0xd7, 0xcd, 0xcc, 0xd7, 0x87, 0x7b, 0x0b, 0xc1, 0x4a, 0x62, 0x03, 0x07, 0xb6, 0x31, 0x20, 0x00, + 0x00, 0xff, 0xff, 0xff, 0x41, 0x40, 0xfa, 0xa3, 0x01, 0x00, 0x00, } diff --git a/protodep/protodep.pb.validate.go b/anyvendor/anyvendor.pb.validate.go similarity index 92% rename from protodep/protodep.pb.validate.go rename to anyvendor/anyvendor.pb.validate.go index 8500acf..feb4d8b 100644 --- a/protodep/protodep.pb.validate.go +++ b/anyvendor/anyvendor.pb.validate.go @@ -1,7 +1,7 @@ // Code generated by protoc-gen-validate. DO NOT EDIT. -// source: protodep.proto +// source: anyvendor.proto -package protodep +package anyvendor import ( "bytes" @@ -33,9 +33,6 @@ var ( _ = ptypes.DynamicAny{} ) -// define the regex for a UUID once up-front -var _protodep_uuidPattern = regexp.MustCompile("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$") - // Validate checks the field values on Config with the rules defined in the // proto definition for this message. If any rules are violated, an error is returned. func (m *Config) Validate() error { @@ -146,6 +143,12 @@ func (m *Import) Validate() error { } } + default: + return ImportValidationError{ + field: "ImportType", + reason: "value is required", + } + } return nil @@ -212,6 +215,13 @@ func (m *Local) Validate() error { return nil } + if len(m.GetPatterns()) < 1 { + return LocalValidationError{ + field: "Patterns", + reason: "value must contain at least 1 item(s)", + } + } + return nil } @@ -277,7 +287,19 @@ func (m *GoModImport) Validate() error { return nil } - // no validation rules for Package + if len(m.GetPatterns()) < 1 { + return GoModImportValidationError{ + field: "Patterns", + reason: "value must contain at least 1 item(s)", + } + } + + if utf8.RuneCountInString(m.GetPackage()) < 1 { + return GoModImportValidationError{ + field: "Package", + reason: "value length must be at least 1 runes", + } + } return nil } diff --git a/anyvendor/anyvendor.proto b/anyvendor/anyvendor.proto new file mode 100644 index 0000000..08292e1 --- /dev/null +++ b/anyvendor/anyvendor.proto @@ -0,0 +1,44 @@ +syntax = "proto3"; +package anyvendor; +option go_package = "github.com/solo-io/anyvendor/anyvendor"; + +import "validate/validate.proto"; + +/* + Config object used for running anyvendor. The top level config consists of 2 main sections. + + Local is a set of matchers will be taken directly from the local module, and vendored in. + Imports is a list of import types which will be run, and then vendored. +*/ +message Config { + Local local = 1; + + repeated Import imports = 2; +} + +message Import { + oneof ImportType { + option (validate.required) = true; + GoModImport go_mod = 2; + } +} + +// A set of glob patters to be grabbed from the current module +message Local { + repeated string patterns = 1 [(validate.rules).repeated = { min_items: 1}]; +} + +/* + A go mod import represents a set of imports from a go module + + patterns is a set glob matchers to find files in a go module. + + package is the name of the go module which these should be pulled from. + + The GoModImport uses the command `go list -f '{{.Path}}' -m all` to find + all of the package names +*/ +message GoModImport { + repeated string patterns = 1 [(validate.rules).repeated = { min_items: 1}]; + string package = 2 [(validate.rules).string = { min_len: 1}]; +} diff --git a/protodep/defaults.go b/anyvendor/defaults.go similarity index 61% rename from protodep/defaults.go rename to anyvendor/defaults.go index 1c310f8..1083367 100644 --- a/protodep/defaults.go +++ b/anyvendor/defaults.go @@ -1,4 +1,4 @@ -package protodep +package anyvendor // need to reenable this once the functionality to vendor the protos is enabled //-go:generate bash generate.sh @@ -7,5 +7,9 @@ const ( // default directory into which proto, and other files will be vendored. // Originally this was meant to be the vendor directory, but clashes with the go vendor directory // meant it would be easier for this to inhabit it's own folder - DefaultDepDir = ".proto" + // See this section for more info: https://tip.golang.org/doc/go1.14#go-command + // This tool should not force to projects to build using vendor. + DefaultDepDir = "vendor_any" + + ProtoMatchPattern = "**/*.proto" ) diff --git a/protodep/generate.sh b/anyvendor/generate.sh similarity index 83% rename from protodep/generate.sh rename to anyvendor/generate.sh index 6375a07..3e38e90 100755 --- a/protodep/generate.sh +++ b/anyvendor/generate.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash +# script to generate anyvendor.proto set -e @@ -9,7 +10,7 @@ set -o pipefail IN=$(dirname "${BASH_SOURCE[0]}") ROOT=$(go env GOMOD | rev | cut -c8- | rev) -VALIDATE=${ROOT}/.proto/github.com/envoyprocy/protoc-gen-validate +VALIDATE=${ROOT}/vendor_any/github.com/envoyproxy/protoc-gen-validate # code-generator does work with go.mod but makes assumptions about # the project living in $GOPATH/src. To work around this and support @@ -39,6 +40,6 @@ protoc ${IMPORTS} \ ${VALIDATE_FLAG} \ ${INPUT_PROTOS} -cp -r ${TEMP_DIR}/github.com/solo-io/protodep/* ${ROOT} +cp -r ${TEMP_DIR}/github.com/solo-io/anyvendor/* ${ROOT} goimports -w . \ No newline at end of file diff --git a/changelog/v0.0.1/api.yaml b/changelog/v0.0.1/api.yaml index a6d26ee..57c2f2f 100644 --- a/changelog/v0.0.1/api.yaml +++ b/changelog/v0.0.1/api.yaml @@ -1,4 +1,4 @@ changelog: - type: NEW_FEATURE - description: Create API for protodep. - issueLink: https://github.com/solo-io/protodep/issues/1 \ No newline at end of file + description: Create API for anyvendor. + issueLink: https://github.com/solo-io/anyvendor/issues/1 \ No newline at end of file diff --git a/changelog/v0.0.1/lib.yaml b/changelog/v0.0.1/lib.yaml new file mode 100644 index 0000000..4a9654b --- /dev/null +++ b/changelog/v0.0.1/lib.yaml @@ -0,0 +1,4 @@ +changelog: +- type: NEW_FEATURE + description: Expose anyvendor as a library. + issueLink: https://github.com/solo-io/anyvendor/issues/3 \ No newline at end of file diff --git a/ci/check-code-gen.sh b/ci/check-code-gen.sh index 3a50992..4ccbc80 100755 --- a/ci/check-code-gen.sh +++ b/ci/check-code-gen.sh @@ -8,14 +8,6 @@ if [ ! -f .gitignore ]; then echo "_output" > .gitignore fi - -git init -git config user.email "you@example.com" -git config --global user.name "Your Name" -git add . -git commit -m "set up dummy repo for diffing" -q - - PATH=/workspace/gopath/bin:$PATH set +e diff --git a/ci/tools.go b/ci/tools.go index fd048be..9b2e65b 100644 --- a/ci/tools.go +++ b/ci/tools.go @@ -17,6 +17,5 @@ limitations under the License. package tools import ( - _ "github.com/solo-io/protoc-gen-ext" _ "github.com/envoyproxy/protoc-gen-validate" ) diff --git a/cloudbuild-cache.yaml b/cloudbuild-cache.yaml index a572522..16b2c16 100644 --- a/cloudbuild-cache.yaml +++ b/cloudbuild-cache.yaml @@ -1,7 +1,7 @@ steps: - name: gcr.io/cloud-builders/gsutil entrypoint: 'bash' - args: ['-c', 'mkdir -p /go/pkg && cd /go/pkg && gsutil cat gs://$PROJECT_ID-cache/protodep/protodep-mod.tar.gz | tar -xzf -'] + args: ['-c', 'mkdir -p /go/pkg && cd /go/pkg && gsutil cat gs://$PROJECT_ID-cache/anyvendor/anyvendor-mod.tar.gz | tar -xzf -'] env: volumes: &vol - name: 'gopath' @@ -21,11 +21,11 @@ steps: - name: 'golang:1.13' entrypoint: 'bash' volumes: *vol - args: ['-c', ' cd /go/pkg && tar -zvcf protodep-mod.tar.gz mod'] + args: ['-c', ' cd /go/pkg && tar -zvcf anyvendor-mod.tar.gz mod'] id: 'tar-cache' - name: gcr.io/cloud-builders/gsutil - args: ['cp', '/go/pkg/protodep-mod.tar.gz', 'gs://$PROJECT_ID-cache/protodep/protodep-mod.tar.gz'] + args: ['cp', '/go/pkg/anyvendor-mod.tar.gz', 'gs://$PROJECT_ID-cache/anyvendor/anyvendor-mod.tar.gz'] volumes: *vol id: 'upload-cache' diff --git a/cloudbuild.yaml b/cloudbuild.yaml index 3517f8e..648173c 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -2,9 +2,9 @@ steps: - name: gcr.io/cloud-builders/gsutil entrypoint: 'bash' - args: ['-c', 'mkdir -p /go/pkg && cd /go/pkg && gsutil cat gs://$PROJECT_ID-cache/protodep/protodep-mod.tar.gz | tar -xzf -'] + args: ['-c', 'mkdir -p /go/pkg && cd /go/pkg && gsutil cat gs://$PROJECT_ID-cache/anyvendor/anyvendor-mod.tar.gz | tar -xzf -'] id: 'untar-mod-cache' - dir: &dir '/workspace/protodep' + dir: &dir '/workspace/repo/anyvendor' # prepare-workspace to set up the project so it can be built and tested - name: 'gcr.io/$PROJECT_ID/prepare-go-workspace:0.2.2' @@ -12,17 +12,17 @@ steps: - "--repo-owner" - "solo-io" - "--repo-name" - - protodep + - anyvendor - "--repo-sha" - "$COMMIT_SHA" - "--repo-output-dir" - - "." + - "repo" env: - 'GIT_SSH_CONFIG=FALSE' id: 'prepare-workspace' # download massive container in parallel -- name: 'gcr.io/$PROJECT_ID/go-mod-ginkgo:0.2.1' +- name: 'gcr.io/$PROJECT_ID/go-mod-ginkgo:0.2.2' entrypoint: 'bash' dir: *dir args: ['-c', 'ls'] @@ -40,7 +40,7 @@ steps: entrypoint: 'bash' args: ['ci/check-code-gen.sh'] env: - - 'PROJECT_ROOT=github.com/solo-io/protodep' + - 'PROJECT_ROOT=github.com/solo-io/anyvendor' - 'TAGGED_VERSION=$TAG_NAME' # waitFor: ['update-deps'] dir: *dir @@ -50,14 +50,14 @@ steps: # e2e-ginkgo is produced from https://github.com/solo-io/cloud-builders/e2e-ginkgo # sets up redis, consul, kubectl, go with required environment variables # need to use the provided entrypoint -- name: 'gcr.io/$PROJECT_ID/go-mod-ginkgo:0.2.1' +- name: 'gcr.io/$PROJECT_ID/go-mod-ginkgo:0.2.2' args: ['-r', '-v', '-race', '-p', '-compilers=2'] dir: *dir id: 'test' timeout: 1500s -tags: ['protodep'] +tags: ['anyvendor'] options: machineType: 'N1_HIGHCPU_32' volumes: diff --git a/go.mod b/go.mod index 20777b6..3a1cd06 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,13 @@ go 1.13 require ( github.com/envoyproxy/protoc-gen-validate v0.1.0 + github.com/golang/mock v1.3.1 github.com/golang/protobuf v1.3.2 + github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 // indirect + github.com/lyft/protoc-gen-star v0.4.14 // indirect + github.com/mattn/go-zglob v0.0.1 github.com/onsi/ginkgo v1.11.0 github.com/onsi/gomega v1.8.1 github.com/rotisserie/eris v0.1.1 + github.com/spf13/afero v1.2.2 ) diff --git a/go.sum b/go.sum index 3fcf4dc..661e694 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,17 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= +github.com/lyft/protoc-gen-star v0.4.14/go.mod h1:mE8fbna26u7aEA2QCVvvfBU/ZrPgocG1206xAFPcs94= +github.com/mattn/go-zglob v0.0.1 h1:xsEx/XUoVlI6yXjqBK062zYhRTZltCNmYPx6v+8DNaY= +github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.11.0 h1:JAKSXpt1YjtLA7YpPiqO9ss6sNXEsPfSGdwN0UHqzrw= github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -13,13 +19,20 @@ github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34= github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/rotisserie/eris v0.1.1 h1:C0wEdnJ6+3jYx2r8RS4xBM+ZW+mVrXGocIaFbTdRYCA= github.com/rotisserie/eris v0.1.1/go.mod h1:2ik3CyJrzlOjGyDGrKfqZivSfmkhCS3ktE+T1mNzzLk= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= +github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/manager/common.go b/pkg/manager/common.go new file mode 100644 index 0000000..8d447d1 --- /dev/null +++ b/pkg/manager/common.go @@ -0,0 +1,122 @@ +package manager + +import ( + "fmt" + "io" + "log" + "os" + "path/filepath" + "strings" + "unicode" + + "github.com/mattn/go-zglob" + "github.com/rotisserie/eris" + "github.com/solo-io/anyvendor/anyvendor" + "github.com/spf13/afero" +) + +/* + This interface is used to abstract away the methods which require ENV vars or other + system things. This is mostly for unit testing purposes. +*/ +type FileCopier interface { + Copy(src, dst string) (int64, error) + GetMatches(copyPat []string, dir string) ([]string, error) +} + +var matchListFilter = fmt.Sprintf("%s/", anyvendor.DefaultDepDir) + +type copier struct { + fs afero.Fs +} + +func (c *copier) GetMatches(copyPat []string, dir string) ([]string, error) { + var vendorList []string + + for _, pat := range copyPat { + matches, err := zglob.Glob(filepath.Join(dir, pat)) + if err != nil { + return nil, eris.Wrapf(err, "Error! glob match failure") + } + // Filter out all matches which contain a vendor folder, those are leftovers from a previous run. + // Might be worth clearing the vendor folder before every run. + for _, match := range matches { + vendorFolders := strings.Count(match, matchListFilter) + if vendorFolders > 0 { + continue + } + vendorList = append(vendorList, match) + } + } + + return vendorList, nil +} + +func (c *copier) PkgModPath(importPath, version string) string { + goPath := os.Getenv("GOPATH") + if goPath == "" { + // the default GOPATH for go v1.11 + goPath = filepath.Join(os.Getenv("HOME"), "go") + } + + var normPath string + + // go mod replaces capital letters with "!" and then the lower case. + // This checks for that and switches it so we can find the file + for _, char := range importPath { + if unicode.IsUpper(char) { + normPath += "!" + string(unicode.ToLower(char)) + } else { + normPath += string(char) + } + } + + return filepath.Join(goPath, "pkg", "mod", fmt.Sprintf("%s@%s", normPath, version)) +} + +func NewCopier(fs afero.Fs) *copier { + return &copier{ + fs: fs, + } +} + +var ( + IrregularFileError = func(file string) error { + return eris.Errorf("%s is not a regular file", file) + } +) + +func NewDefaultCopier() *copier { + return &copier{fs: afero.NewOsFs()} +} + +func (c *copier) Copy(src, dst string) (int64, error) { + log.Printf("copying %v -> %v", src, dst) + + if err := c.fs.MkdirAll(filepath.Dir(dst), os.ModePerm); err != nil { + return 0, err + } + + srcStat, err := c.fs.Stat(src) + if err != nil { + return 0, err + } + + if !srcStat.Mode().IsRegular() { + return 0, IrregularFileError(src) + } + + srcFile, err := c.fs.Open(src) + if err != nil { + return 0, err + } + defer srcFile.Close() + + dstFile, err := c.fs.Create(dst) + if err != nil { + return 0, err + } + defer dstFile.Close() + + return io.Copy(dstFile, srcFile) +} diff --git a/pkg/manager/common_test.go b/pkg/manager/common_test.go new file mode 100644 index 0000000..a40b303 --- /dev/null +++ b/pkg/manager/common_test.go @@ -0,0 +1,136 @@ +package manager + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/golang/mock/gomock" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/rotisserie/eris" + mock_manager "github.com/solo-io/anyvendor/pkg/manager/mocks" + "github.com/spf13/afero" +) + +//go:generate mockgen -package mock_manager -destination ./mocks/afero.go github.com/spf13/afero Fs,File +//go:generate mockgen -package mock_manager -destination ./mocks/fileinfo.go os FileInfo +//go:generate mockgen -package mock_manager -destination ./mocks/copier.go -source ./common.go + +var _ = Describe("common", func() { + var ( + ctrl *gomock.Controller + cp *copier + ) + BeforeEach(func() { + ctrl = gomock.NewController(GinkgoT()) + }) + AfterEach(func() { + ctrl.Finish() + }) + Context("pkgModPath", func() { + BeforeEach(func() { + cp = &copier{} + }) + It("can translate a pkgModPath with a !", func() { + importPath := "github.com/Microsoft/package" + version := "this_is_a_hash" + result := cp.PkgModPath(importPath, version) + resultTest := filepath.Join(os.Getenv("GOPATH"), "pkg", "mod", + fmt.Sprintf("%s@%s", "github.com/!microsoft/package", version)) + Expect(result).To(Equal(resultTest)) + }) + It("can translate a standard pkgModPath", func() { + importPath := "github.com/microsoft/package" + version := "this_is_a_hash" + result := cp.PkgModPath(importPath, version) + resultTest := filepath.Join(os.Getenv("GOPATH"), "pkg", "mod", + fmt.Sprintf("%s@%s", importPath, version)) + Expect(result).To(Equal(resultTest)) + }) + }) + Context("copier", func() { + Context("mocks", func() { + var ( + mockFs *mock_manager.MockFs + mockFileInfo *mock_manager.MockFileInfo + mockFile *mock_manager.MockFile + ) + BeforeEach(func() { + ctrl = gomock.NewController(GinkgoT()) + mockFs = mock_manager.NewMockFs(ctrl) + mockFileInfo = mock_manager.NewMockFileInfo(ctrl) + mockFile = mock_manager.NewMockFile(ctrl) + cp = NewCopier(mockFs) + }) + It("will return error if mkdir fails", func() { + src, dst := "src/src.go", "dst/dstgo." + fakeErr := eris.New("hello") + mockFs.EXPECT().MkdirAll(filepath.Dir(dst), os.ModePerm).Return(fakeErr) + _, err := cp.Copy(src, dst) + Expect(err).To(HaveOccurred()) + Expect(err).To(Equal(fakeErr)) + }) + It("will return error if Stat fails", func() { + src, dst := "src/src.go", "dst/dst.go" + fakeErr := eris.New("hello") + mockFs.EXPECT().MkdirAll(filepath.Dir(dst), os.ModePerm).Return(nil) + mockFs.EXPECT().Stat(src).Return(nil, fakeErr) + _, err := cp.Copy(src, dst) + Expect(err).To(HaveOccurred()) + Expect(err).To(Equal(fakeErr)) + }) + It("will return error if fileinfo returns error fails", func() { + src, dst := "src/src.go", "dst/dst.go" + mockFs.EXPECT().MkdirAll(filepath.Dir(dst), os.ModePerm).Return(nil) + mockFs.EXPECT().Stat(src).Return(mockFileInfo, nil) + mockFileInfo.EXPECT().Mode().Return(os.ModeIrregular) + _, err := cp.Copy(src, dst) + Expect(err).To(HaveOccurred()) + isErr := eris.Is(err, IrregularFileError(src)) + Expect(isErr).To(BeTrue()) + }) + It("will return error if open fails", func() { + src, dst := "src/src.go", "dst/dst.go" + mockFs.EXPECT().MkdirAll(filepath.Dir(dst), os.ModePerm).Return(nil) + mockFs.EXPECT().Stat(src).Return(mockFileInfo, nil) + mockFileInfo.EXPECT().Mode().Return(os.ModePerm) + fakeErr := eris.New("hello") + mockFs.EXPECT().Open(src).Return(nil, fakeErr) + _, err := cp.Copy(src, dst) + Expect(err).To(HaveOccurred()) + Expect(err).To(Equal(fakeErr)) + }) + It("will return error if create fails", func() { + src, dst := "src/src.go", "dst/dst.go" + mockFs.EXPECT().MkdirAll(filepath.Dir(dst), os.ModePerm).Return(nil) + mockFs.EXPECT().Stat(src).Return(mockFileInfo, nil) + mockFileInfo.EXPECT().Mode().Return(os.ModePerm) + fakeErr := eris.New("hello") + mockFs.EXPECT().Open(src).Return(mockFile, nil) + mockFile.EXPECT().Close().Return(nil) + mockFs.EXPECT().Create(dst).Return(nil, fakeErr) + _, err := cp.Copy(src, dst) + Expect(err).To(HaveOccurred()) + Expect(err).To(Equal(fakeErr)) + }) + }) + Context("real copy", func() { + It("Can copy", func() { + fs := afero.NewOsFs() + cp := &copier{fs: fs} + tmpFile, err := afero.TempFile(fs, "", "") + Expect(err).NotTo(HaveOccurred()) + defer fs.Remove(tmpFile.Name()) + tmpDir, err := afero.TempDir(fs, "", "") + Expect(err).NotTo(HaveOccurred()) + defer fs.Remove(tmpDir) + dstFileName := "test" + _, err = cp.Copy(tmpFile.Name(), filepath.Join(tmpDir, dstFileName)) + Expect(err).NotTo(HaveOccurred()) + _, err = fs.Stat(filepath.Join(tmpDir, dstFileName)) + Expect(err).NotTo(HaveOccurred()) + }) + }) + }) +}) diff --git a/pkg/manager/gomod.go b/pkg/manager/gomod.go new file mode 100644 index 0000000..4ee3adc --- /dev/null +++ b/pkg/manager/gomod.go @@ -0,0 +1,193 @@ +package manager + +import ( + "context" + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/rotisserie/eris" + "github.com/solo-io/anyvendor/anyvendor" + "github.com/solo-io/anyvendor/pkg/modutils" + "github.com/spf13/afero" +) + +var ( + // offer sane defaults for proto vendoring + DefaultMatchPatterns = []string{anyvendor.ProtoMatchPattern} +) + +type goModOptions struct { + MatchOptions []*anyvendor.GoModImport + LocalMatchers []string +} + +// struct which represents a go module package in the module package list +type moduleWithImports struct { + module *modutils.Module + vendorList []string // files to vendor +} + +func NewGoModFactory(cwd string) (*goModFactory, error) { + if !filepath.IsAbs(cwd) { + absoluteDir, err := filepath.Abs(cwd) + if err != nil { + return nil, err + } + cwd = absoluteDir + } + fs := afero.NewOsFs() + return &goModFactory{ + WorkingDirectory: cwd, + fs: fs, + fileCopier: NewCopier(fs), + }, nil +} + +type goModFactory struct { + WorkingDirectory string + packageName bool + fs afero.Fs + fileCopier FileCopier +} + +func (m *goModFactory) Ensure(ctx context.Context, opts *anyvendor.Config) error { + var packages []*anyvendor.GoModImport + for _, cfg := range opts.Imports { + if cfg.GetGoMod() != nil { + packages = append(packages, cfg.GetGoMod()) + } + } + mods, err := m.gather(goModOptions{ + MatchOptions: packages, + LocalMatchers: opts.GetLocal().GetPatterns(), + }) + if err != nil { + return err + } + + err = m.copy(mods) + if err != nil { + return err + } + return nil +} + +// gather up all packages for a given go module +// currently this function uses the cmd `go list -m all` to figure out the list of dep +// all of the logic surrounding go.mod and the go cli calls are in the modutils package +func (m *goModFactory) gather(opts goModOptions) ([]*moduleWithImports, error) { + // Ensure go.mod file exists and we're running from the project root, + modPackageFile, err := modutils.GetCurrentModPackageFile() + if err != nil { + return nil, err + } + + packageName, err := modutils.GetCurrentModPackageName(modPackageFile) + if err != nil { + return nil, err + } + + var moduleNames []string + for _, v := range opts.MatchOptions { + moduleNames = append(moduleNames, v.Package) + } + moduleNames = append([]string{packageName}, moduleNames...) + modPackages, err := modutils.GetCurrentPackageListJson(moduleNames) + if err != nil { + return nil, err + } + + // handle local pacakges, should never be length 0 + localImports := []*anyvendor.GoModImport{ + { + Patterns: opts.LocalMatchers, + Package: packageName, + }, + } + + var modules []*moduleWithImports + // handle all packages + for _, modPackage := range modPackages { + imports := opts.MatchOptions + if modPackage.Path == packageName { + imports = localImports + } + mod, err := m.handleSingleModule(modPackage, imports) + if err != nil { + return nil, err + } + if len(mod.vendorList) > 0 { + modules = append(modules, mod) + } + } + + return modules, nil +} + +func (m *goModFactory) handleSingleModule(module *modutils.Module, matchOptions []*anyvendor.GoModImport) (*moduleWithImports, error) { + // make sure module exists + if _, err := m.fs.Stat(module.Dir); os.IsNotExist(err) { + return nil, eris.Wrapf(err, "Error! %q module path does not exist, check $GOPATH/pkg/mod. "+ + "Try running go mod download\n", module.Dir) + } + + // If no match options have been supplied, match on all packages using default match patterns + if matchOptions == nil { + // Build list of files to module path source to project vendor folder + vendorList, err := m.fileCopier.GetMatches(DefaultMatchPatterns, module.Dir) + if err != nil { + return nil, err + } + return &moduleWithImports{ + module: module, + vendorList: vendorList, + }, nil + } + + var result []string + for _, matchOpt := range matchOptions { + // only check module if is in imports list, or imports list in empty + if len(matchOpt.Package) != 0 && + !strings.Contains(module.Path, matchOpt.Package) { + continue + } + // Build list of files to module path source to project vendor folder + vendorList, err := m.fileCopier.GetMatches(matchOpt.Patterns, module.Dir) + if err != nil { + return nil, err + } + result = vendorList + } + return &moduleWithImports{ + module: module, + vendorList: result, + }, nil +} + +func (m *goModFactory) copy(modules []*moduleWithImports) error { + // Copy mod vendor list files to ./vendor/ + for _, mod := range modules { + if mod.module.Main == true { + for _, vendorFile := range mod.vendorList { + localPath := strings.TrimPrefix(vendorFile, m.WorkingDirectory+"/") + localFile := filepath.Join(m.WorkingDirectory, anyvendor.DefaultDepDir, mod.module.Path, localPath) + if _, err := m.fileCopier.Copy(vendorFile, localFile); err != nil { + return eris.Wrapf(err, fmt.Sprintf("Error! %s - unable to copy file %s\n", + err.Error(), vendorFile)) + } + } + } else { + for _, vendorFile := range mod.vendorList { + localPath := filepath.Join(mod.module.Path, vendorFile[len(mod.module.Dir):]) + localFile := filepath.Join(m.WorkingDirectory, anyvendor.DefaultDepDir, localPath) + if _, err := m.fileCopier.Copy(vendorFile, localFile); err != nil { + return eris.Wrapf(err, fmt.Sprintf("Error! %s - unable to copy file %s\n", + err.Error(), vendorFile)) + } + } + } + } + return nil +} diff --git a/pkg/manager/gomod_test.go b/pkg/manager/gomod_test.go new file mode 100644 index 0000000..4587380 --- /dev/null +++ b/pkg/manager/gomod_test.go @@ -0,0 +1,242 @@ +package manager + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/golang/mock/gomock" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "github.com/rotisserie/eris" + "github.com/solo-io/anyvendor/anyvendor" + mock_manager "github.com/solo-io/anyvendor/pkg/manager/mocks" + "github.com/solo-io/anyvendor/pkg/modutils" +) + +var _ = Describe("anyvendor", func() { + var ( + modPathString string + mgr *goModFactory + ctrl *gomock.Controller + + EnvoyValidateProtoMatcher = &anyvendor.GoModImport{ + Package: "github.com/envoyproxy/protoc-gen-validate", + Patterns: []string{"validate/*.proto"}, + } + ) + BeforeEach(func() { + ctrl = gomock.NewController(GinkgoT()) + }) + AfterEach(func() { + ctrl.Finish() + }) + + Context("helper functions", func() { + Context("handleSingleModule", func() { + type testCase struct { + nonSplitImport string + splitImport []string + err error + setupMocks func(mockFs *mock_manager.MockFs, mockCp *mock_manager.MockFileCopier) + } + var ( + mockFs *mock_manager.MockFs + mockCp *mock_manager.MockFileCopier + fakeErr = eris.New("test error") + fakeDir = "fake/dir" + standardModule = &modutils.Module{ + Path: "github.com/envoyproxy/protoc-gen-validate", + Version: "v0.1.0", + Indirect: false, + Dir: fakeDir, + } + ) + BeforeEach(func() { + mockCp = mock_manager.NewMockFileCopier(ctrl) + mockFs = mock_manager.NewMockFs(ctrl) + mgr = &goModFactory{ + fileCopier: mockCp, + fs: mockFs, + } + }) + Context("errors", func() { + It("will error if dir does not exist", func() { + mockFs.EXPECT().Stat(fakeDir).Return(nil, os.ErrNotExist) + _, err := mgr.handleSingleModule(standardModule, nil) + Expect(err).To(HaveOccurred()) + Expect(eris.Cause(err).Error()).To(Equal(os.ErrNotExist.Error())) + }) + It("nil match opts, will error if get matches fails", func() { + mockFs.EXPECT().Stat(fakeDir).Return(nil, nil) + mockCp.EXPECT().GetMatches(gomock.Any(), gomock.Any()).Return(nil, fakeErr) + _, err := mgr.handleSingleModule(standardModule, nil) + Expect(err).To(HaveOccurred()) + Expect(eris.Cause(err)).To(Equal(fakeErr)) + }) + It("real match opts, will error if get matches fails", func() { + matchOptions := []*anyvendor.GoModImport{ + EnvoyValidateProtoMatcher, + } + mockFs.EXPECT().Stat(fakeDir).Return(nil, nil) + mockCp.EXPECT().GetMatches(gomock.Any(), gomock.Any()).Return(nil, fakeErr) + _, err := mgr.handleSingleModule(standardModule, matchOptions) + Expect(err).To(HaveOccurred()) + Expect(eris.Cause(err)).To(Equal(fakeErr)) + }) + }) + Context("basic imports", func() { + It("nil match opts", func() { + vendorList := []string{"vendorLIst"} + mockFs.EXPECT().Stat(fakeDir).Return(nil, nil) + mockCp.EXPECT().GetMatches(DefaultMatchPatterns, fakeDir).Return(vendorList, nil) + mod, err := mgr.handleSingleModule(standardModule, nil) + Expect(err).NotTo(HaveOccurred()) + Expect(mod.vendorList).To(Equal(vendorList)) + Expect(mod.module.Dir).To(Equal(fakeDir)) + }) + It("real match opts", func() { + matchOptions := []*anyvendor.GoModImport{ + EnvoyValidateProtoMatcher, + } + vendorList := []string{"vendorLIst"} + mockFs.EXPECT().Stat(fakeDir).Return(nil, nil) + mockCp.EXPECT().GetMatches(matchOptions[0].Patterns, fakeDir).Return(vendorList, nil) + mod, err := mgr.handleSingleModule(standardModule, matchOptions) + Expect(err).NotTo(HaveOccurred()) + Expect(mod.vendorList).To(Equal(vendorList)) + Expect(mod.module.Dir).To(Equal(fakeDir)) + }) + }) + }) + + Context("copy", func() { + type testCase struct { + mod *moduleWithImports + vendorFile string + localFile string + err error + } + var ( + mockCp *mock_manager.MockFileCopier + ) + BeforeEach(func() { + mockCp = mock_manager.NewMockFileCopier(ctrl) + mgr = &goModFactory{ + fileCopier: mockCp, + } + }) + + It("can handle errors from the copier (normal)", func() { + fakeErr := eris.New("test error") + testCases := []testCase{ + { + mod: &moduleWithImports{ + vendorList: []string{"test"}, + module: &modutils.Module{}, + }, + err: fakeErr, + }, + { + mod: &moduleWithImports{ + vendorList: []string{"test"}, + module: &modutils.Module{ + Main: true, + }, + }, + err: fakeErr, + }, + } + // input isn't important here, just checking for error state + mockCp.EXPECT().Copy(gomock.Any(), gomock.Any()).Times(2).Return(int64(0), fakeErr) + for _, v := range testCases { + err := mgr.copy([]*moduleWithImports{v.mod}) + Expect(eris.Cause(err)).To(Equal(fakeErr)) + } + }) + It("can handle a single local module", func() { + testDir := "/fake/test/dir" + importPath := "/import/path" + tc := &testCase{ + mod: &moduleWithImports{ + module: &modutils.Module{ + Path: importPath, + Dir: testDir, + Main: true, + }, + vendorList: []string{filepath.Join(testDir, "package", "1", "hello.proto")}, + }, + vendorFile: "/fake/test/dir/package/1/hello.proto", + localFile: "/fake/test/dir/vendor_any/import/path/package/1/hello.proto", + } + mgr.WorkingDirectory = testDir + mockCp.EXPECT().Copy(tc.vendorFile, tc.localFile).Return(int64(0), nil) + Expect(mgr.copy([]*moduleWithImports{tc.mod})).NotTo(HaveOccurred()) + + }) + Context("multiple standard", func() { + var ( + testDir = "/fake/test/dir" + importPath = "/import/path" + testCases = []testCase{ + { + mod: &moduleWithImports{ + module: &modutils.Module{ + Path: importPath, + Dir: testDir, + }, + vendorList: []string{filepath.Join(testDir, "package", "1", "hello.proto")}, + }, + vendorFile: "/fake/test/dir/package/1/hello.proto", + localFile: "vendor_any/import/path/package/1/hello.proto", + }, + { + mod: &moduleWithImports{ + module: &modutils.Module{ + Path: importPath, + Dir: testDir, + }, + vendorList: []string{filepath.Join(testDir, "package", "2", "hello.proto")}, + }, + vendorFile: "/fake/test/dir/package/2/hello.proto", + localFile: "vendor_any/import/path/package/2/hello.proto", + }, + } + ) + + for i, v := range testCases { + It(fmt.Sprintf("testcase %d", i), func() { + mockCp.EXPECT().Copy(v.vendorFile, v.localFile).Return(int64(0), nil) + Expect(mgr.copy([]*moduleWithImports{v.mod})).NotTo(HaveOccurred()) + }) + } + }) + + }) + }) + + Context("vendor protos", func() { + BeforeEach(func() { + modBytes, err := modutils.GetCurrentModPackageFile() + modFileString := strings.TrimSpace(modBytes) + Expect(err).NotTo(HaveOccurred()) + modPathString = filepath.Dir(modFileString) + mgr, err = NewGoModFactory(modPathString) + Expect(err).NotTo(HaveOccurred()) + }) + It("can vendor protos", func() { + modules, err := mgr.gather(goModOptions{ + MatchOptions: []*anyvendor.GoModImport{ + EnvoyValidateProtoMatcher, + }, + LocalMatchers: []string{"anyvendor/**/*.proto"}, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(modules).To(HaveLen(2)) + Expect(modules[0].module.Path).To(Equal("github.com/solo-io/anyvendor")) + Expect(modules[1].module.Path).To(Equal(EnvoyValidateProtoMatcher.Package)) + Expect(mgr.copy(modules)).NotTo(HaveOccurred()) + }) + }) +}) diff --git a/pkg/manager/mocks/afero.go b/pkg/manager/mocks/afero.go new file mode 100644 index 0000000..e15a908 --- /dev/null +++ b/pkg/manager/mocks/afero.go @@ -0,0 +1,423 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/spf13/afero (interfaces: Fs,File) + +// Package mock_manager is a generated GoMock package. +package mock_manager + +import ( + os "os" + reflect "reflect" + time "time" + + gomock "github.com/golang/mock/gomock" + afero "github.com/spf13/afero" +) + +// MockFs is a mock of Fs interface +type MockFs struct { + ctrl *gomock.Controller + recorder *MockFsMockRecorder +} + +// MockFsMockRecorder is the mock recorder for MockFs +type MockFsMockRecorder struct { + mock *MockFs +} + +// NewMockFs creates a new mock instance +func NewMockFs(ctrl *gomock.Controller) *MockFs { + mock := &MockFs{ctrl: ctrl} + mock.recorder = &MockFsMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockFs) EXPECT() *MockFsMockRecorder { + return m.recorder +} + +// Chmod mocks base method +func (m *MockFs) Chmod(arg0 string, arg1 os.FileMode) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Chmod", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Chmod indicates an expected call of Chmod +func (mr *MockFsMockRecorder) Chmod(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Chmod", reflect.TypeOf((*MockFs)(nil).Chmod), arg0, arg1) +} + +// Chtimes mocks base method +func (m *MockFs) Chtimes(arg0 string, arg1, arg2 time.Time) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Chtimes", arg0, arg1, arg2) + ret0, _ := ret[0].(error) + return ret0 +} + +// Chtimes indicates an expected call of Chtimes +func (mr *MockFsMockRecorder) Chtimes(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Chtimes", reflect.TypeOf((*MockFs)(nil).Chtimes), arg0, arg1, arg2) +} + +// Create mocks base method +func (m *MockFs) Create(arg0 string) (afero.File, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", arg0) + ret0, _ := ret[0].(afero.File) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Create indicates an expected call of Create +func (mr *MockFsMockRecorder) Create(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockFs)(nil).Create), arg0) +} + +// Mkdir mocks base method +func (m *MockFs) Mkdir(arg0 string, arg1 os.FileMode) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Mkdir", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Mkdir indicates an expected call of Mkdir +func (mr *MockFsMockRecorder) Mkdir(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Mkdir", reflect.TypeOf((*MockFs)(nil).Mkdir), arg0, arg1) +} + +// MkdirAll mocks base method +func (m *MockFs) MkdirAll(arg0 string, arg1 os.FileMode) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MkdirAll", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// MkdirAll indicates an expected call of MkdirAll +func (mr *MockFsMockRecorder) MkdirAll(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MkdirAll", reflect.TypeOf((*MockFs)(nil).MkdirAll), arg0, arg1) +} + +// Name mocks base method +func (m *MockFs) Name() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Name") + ret0, _ := ret[0].(string) + return ret0 +} + +// Name indicates an expected call of Name +func (mr *MockFsMockRecorder) Name() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockFs)(nil).Name)) +} + +// Open mocks base method +func (m *MockFs) Open(arg0 string) (afero.File, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Open", arg0) + ret0, _ := ret[0].(afero.File) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Open indicates an expected call of Open +func (mr *MockFsMockRecorder) Open(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Open", reflect.TypeOf((*MockFs)(nil).Open), arg0) +} + +// OpenFile mocks base method +func (m *MockFs) OpenFile(arg0 string, arg1 int, arg2 os.FileMode) (afero.File, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "OpenFile", arg0, arg1, arg2) + ret0, _ := ret[0].(afero.File) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// OpenFile indicates an expected call of OpenFile +func (mr *MockFsMockRecorder) OpenFile(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenFile", reflect.TypeOf((*MockFs)(nil).OpenFile), arg0, arg1, arg2) +} + +// Remove mocks base method +func (m *MockFs) Remove(arg0 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Remove", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Remove indicates an expected call of Remove +func (mr *MockFsMockRecorder) Remove(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Remove", reflect.TypeOf((*MockFs)(nil).Remove), arg0) +} + +// RemoveAll mocks base method +func (m *MockFs) RemoveAll(arg0 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemoveAll", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// RemoveAll indicates an expected call of RemoveAll +func (mr *MockFsMockRecorder) RemoveAll(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveAll", reflect.TypeOf((*MockFs)(nil).RemoveAll), arg0) +} + +// Rename mocks base method +func (m *MockFs) Rename(arg0, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Rename", arg0, arg1) + ret0, _ := ret[0].(error) + return ret0 +} + +// Rename indicates an expected call of Rename +func (mr *MockFsMockRecorder) Rename(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Rename", reflect.TypeOf((*MockFs)(nil).Rename), arg0, arg1) +} + +// Stat mocks base method +func (m *MockFs) Stat(arg0 string) (os.FileInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Stat", arg0) + ret0, _ := ret[0].(os.FileInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Stat indicates an expected call of Stat +func (mr *MockFsMockRecorder) Stat(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stat", reflect.TypeOf((*MockFs)(nil).Stat), arg0) +} + +// MockFile is a mock of File interface +type MockFile struct { + ctrl *gomock.Controller + recorder *MockFileMockRecorder +} + +// MockFileMockRecorder is the mock recorder for MockFile +type MockFileMockRecorder struct { + mock *MockFile +} + +// NewMockFile creates a new mock instance +func NewMockFile(ctrl *gomock.Controller) *MockFile { + mock := &MockFile{ctrl: ctrl} + mock.recorder = &MockFileMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockFile) EXPECT() *MockFileMockRecorder { + return m.recorder +} + +// Close mocks base method +func (m *MockFile) Close() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Close") + ret0, _ := ret[0].(error) + return ret0 +} + +// Close indicates an expected call of Close +func (mr *MockFileMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockFile)(nil).Close)) +} + +// Name mocks base method +func (m *MockFile) Name() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Name") + ret0, _ := ret[0].(string) + return ret0 +} + +// Name indicates an expected call of Name +func (mr *MockFileMockRecorder) Name() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockFile)(nil).Name)) +} + +// Read mocks base method +func (m *MockFile) Read(arg0 []byte) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Read", arg0) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Read indicates an expected call of Read +func (mr *MockFileMockRecorder) Read(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockFile)(nil).Read), arg0) +} + +// ReadAt mocks base method +func (m *MockFile) ReadAt(arg0 []byte, arg1 int64) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReadAt", arg0, arg1) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ReadAt indicates an expected call of ReadAt +func (mr *MockFileMockRecorder) ReadAt(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadAt", reflect.TypeOf((*MockFile)(nil).ReadAt), arg0, arg1) +} + +// Readdir mocks base method +func (m *MockFile) Readdir(arg0 int) ([]os.FileInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Readdir", arg0) + ret0, _ := ret[0].([]os.FileInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Readdir indicates an expected call of Readdir +func (mr *MockFileMockRecorder) Readdir(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Readdir", reflect.TypeOf((*MockFile)(nil).Readdir), arg0) +} + +// Readdirnames mocks base method +func (m *MockFile) Readdirnames(arg0 int) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Readdirnames", arg0) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Readdirnames indicates an expected call of Readdirnames +func (mr *MockFileMockRecorder) Readdirnames(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Readdirnames", reflect.TypeOf((*MockFile)(nil).Readdirnames), arg0) +} + +// Seek mocks base method +func (m *MockFile) Seek(arg0 int64, arg1 int) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Seek", arg0, arg1) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Seek indicates an expected call of Seek +func (mr *MockFileMockRecorder) Seek(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Seek", reflect.TypeOf((*MockFile)(nil).Seek), arg0, arg1) +} + +// Stat mocks base method +func (m *MockFile) Stat() (os.FileInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Stat") + ret0, _ := ret[0].(os.FileInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Stat indicates an expected call of Stat +func (mr *MockFileMockRecorder) Stat() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stat", reflect.TypeOf((*MockFile)(nil).Stat)) +} + +// Sync mocks base method +func (m *MockFile) Sync() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Sync") + ret0, _ := ret[0].(error) + return ret0 +} + +// Sync indicates an expected call of Sync +func (mr *MockFileMockRecorder) Sync() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sync", reflect.TypeOf((*MockFile)(nil).Sync)) +} + +// Truncate mocks base method +func (m *MockFile) Truncate(arg0 int64) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Truncate", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Truncate indicates an expected call of Truncate +func (mr *MockFileMockRecorder) Truncate(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Truncate", reflect.TypeOf((*MockFile)(nil).Truncate), arg0) +} + +// Write mocks base method +func (m *MockFile) Write(arg0 []byte) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Write", arg0) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Write indicates an expected call of Write +func (mr *MockFileMockRecorder) Write(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockFile)(nil).Write), arg0) +} + +// WriteAt mocks base method +func (m *MockFile) WriteAt(arg0 []byte, arg1 int64) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WriteAt", arg0, arg1) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WriteAt indicates an expected call of WriteAt +func (mr *MockFileMockRecorder) WriteAt(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteAt", reflect.TypeOf((*MockFile)(nil).WriteAt), arg0, arg1) +} + +// WriteString mocks base method +func (m *MockFile) WriteString(arg0 string) (int, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WriteString", arg0) + ret0, _ := ret[0].(int) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// WriteString indicates an expected call of WriteString +func (mr *MockFileMockRecorder) WriteString(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteString", reflect.TypeOf((*MockFile)(nil).WriteString), arg0) +} diff --git a/pkg/manager/mocks/copier.go b/pkg/manager/mocks/copier.go new file mode 100644 index 0000000..d8d1f87 --- /dev/null +++ b/pkg/manager/mocks/copier.go @@ -0,0 +1,64 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./common.go + +// Package mock_manager is a generated GoMock package. +package mock_manager + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" +) + +// MockFileCopier is a mock of FileCopier interface +type MockFileCopier struct { + ctrl *gomock.Controller + recorder *MockFileCopierMockRecorder +} + +// MockFileCopierMockRecorder is the mock recorder for MockFileCopier +type MockFileCopierMockRecorder struct { + mock *MockFileCopier +} + +// NewMockFileCopier creates a new mock instance +func NewMockFileCopier(ctrl *gomock.Controller) *MockFileCopier { + mock := &MockFileCopier{ctrl: ctrl} + mock.recorder = &MockFileCopierMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockFileCopier) EXPECT() *MockFileCopierMockRecorder { + return m.recorder +} + +// Copy mocks base method +func (m *MockFileCopier) Copy(src, dst string) (int64, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Copy", src, dst) + ret0, _ := ret[0].(int64) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Copy indicates an expected call of Copy +func (mr *MockFileCopierMockRecorder) Copy(src, dst interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Copy", reflect.TypeOf((*MockFileCopier)(nil).Copy), src, dst) +} + +// GetMatches mocks base method +func (m *MockFileCopier) GetMatches(copyPat []string, dir string) ([]string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetMatches", copyPat, dir) + ret0, _ := ret[0].([]string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetMatches indicates an expected call of GetMatches +func (mr *MockFileCopierMockRecorder) GetMatches(copyPat, dir interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetMatches", reflect.TypeOf((*MockFileCopier)(nil).GetMatches), copyPat, dir) +} diff --git a/pkg/manager/mocks/fileinfo.go b/pkg/manager/mocks/fileinfo.go new file mode 100644 index 0000000..b2bb8d9 --- /dev/null +++ b/pkg/manager/mocks/fileinfo.go @@ -0,0 +1,120 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: os (interfaces: FileInfo) + +// Package mock_manager is a generated GoMock package. +package mock_manager + +import ( + os "os" + reflect "reflect" + time "time" + + gomock "github.com/golang/mock/gomock" +) + +// MockFileInfo is a mock of FileInfo interface +type MockFileInfo struct { + ctrl *gomock.Controller + recorder *MockFileInfoMockRecorder +} + +// MockFileInfoMockRecorder is the mock recorder for MockFileInfo +type MockFileInfoMockRecorder struct { + mock *MockFileInfo +} + +// NewMockFileInfo creates a new mock instance +func NewMockFileInfo(ctrl *gomock.Controller) *MockFileInfo { + mock := &MockFileInfo{ctrl: ctrl} + mock.recorder = &MockFileInfoMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *MockFileInfo) EXPECT() *MockFileInfoMockRecorder { + return m.recorder +} + +// IsDir mocks base method +func (m *MockFileInfo) IsDir() bool { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "IsDir") + ret0, _ := ret[0].(bool) + return ret0 +} + +// IsDir indicates an expected call of IsDir +func (mr *MockFileInfoMockRecorder) IsDir() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsDir", reflect.TypeOf((*MockFileInfo)(nil).IsDir)) +} + +// ModTime mocks base method +func (m *MockFileInfo) ModTime() time.Time { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ModTime") + ret0, _ := ret[0].(time.Time) + return ret0 +} + +// ModTime indicates an expected call of ModTime +func (mr *MockFileInfoMockRecorder) ModTime() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ModTime", reflect.TypeOf((*MockFileInfo)(nil).ModTime)) +} + +// Mode mocks base method +func (m *MockFileInfo) Mode() os.FileMode { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Mode") + ret0, _ := ret[0].(os.FileMode) + return ret0 +} + +// Mode indicates an expected call of Mode +func (mr *MockFileInfoMockRecorder) Mode() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Mode", reflect.TypeOf((*MockFileInfo)(nil).Mode)) +} + +// Name mocks base method +func (m *MockFileInfo) Name() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Name") + ret0, _ := ret[0].(string) + return ret0 +} + +// Name indicates an expected call of Name +func (mr *MockFileInfoMockRecorder) Name() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Name", reflect.TypeOf((*MockFileInfo)(nil).Name)) +} + +// Size mocks base method +func (m *MockFileInfo) Size() int64 { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Size") + ret0, _ := ret[0].(int64) + return ret0 +} + +// Size indicates an expected call of Size +func (mr *MockFileInfoMockRecorder) Size() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Size", reflect.TypeOf((*MockFileInfo)(nil).Size)) +} + +// Sys mocks base method +func (m *MockFileInfo) Sys() interface{} { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Sys") + ret0, _ := ret[0].(interface{}) + return ret0 +} + +// Sys indicates an expected call of Sys +func (mr *MockFileInfoMockRecorder) Sys() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Sys", reflect.TypeOf((*MockFileInfo)(nil).Sys)) +} diff --git a/pkg/manager/protodep.go b/pkg/manager/protodep.go new file mode 100644 index 0000000..8565c89 --- /dev/null +++ b/pkg/manager/protodep.go @@ -0,0 +1,50 @@ +package manager + +import ( + "context" + + "github.com/solo-io/anyvendor/anyvendor" +) + +/* + An internal only interface used to represent the different types of available sources + for non-go vendored files. +*/ +type depFactory interface { + Ensure(ctx context.Context, opts *anyvendor.Config) error +} + +/* + The manager is the external facing object that will be responsible for ensuring + a given anyvendor config, as outlined by the `anyvendor.Config` object. +*/ +type Manager struct { + depFactories []depFactory +} + +func NewManager(ctx context.Context, cwd string) (*Manager, error) { + if ctx == nil { + ctx = context.Background() + } + goMod, err := NewGoModFactory(cwd) + if err != nil { + return nil, err + } + return &Manager{ + depFactories: []depFactory{ + goMod, + }, + }, nil +} + +func (m *Manager) Ensure(ctx context.Context, opts *anyvendor.Config) error { + if err := opts.Validate(); err != nil { + return err + } + for _, v := range m.depFactories { + if err := v.Ensure(ctx, opts); err != nil { + return err + } + } + return nil +} diff --git a/pkg/manager/protodep_suite_test.go b/pkg/manager/protodep_suite_test.go new file mode 100644 index 0000000..bc45c45 --- /dev/null +++ b/pkg/manager/protodep_suite_test.go @@ -0,0 +1,13 @@ +package manager_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestAnyVendor(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "anyvendor Suite") +} diff --git a/pkg/modutils/mirror.go b/pkg/modutils/mirror.go new file mode 100644 index 0000000..ec28c11 --- /dev/null +++ b/pkg/modutils/mirror.go @@ -0,0 +1,26 @@ +package modutils + +import ( + "time" +) + +// mirror of https://golang.org/src/cmd/go/internal/list/list.go +// used to unmarshal output of `go list -m json` +type Module struct { + Path string // module path + Version string // module version + Versions []string // available module versions (with -versions) + Replace *Module // replaced by this module + Time *time.Time // time version was created + Update *Module // available update, if any (with -u) + Main bool // is this the main module? + Indirect bool // is this module only an indirect dependency of main module? + Dir string // directory holding files for this module, if any + GoMod string // path to go.mod file for this module, if any + GoVersion string // go version used in module + Error *ModuleError // error loading module +} + +type ModuleError struct { + Err string // the error itself +} diff --git a/pkg/modutils/mod.go b/pkg/modutils/mod.go index 75ca4ef..07886ad 100644 --- a/pkg/modutils/mod.go +++ b/pkg/modutils/mod.go @@ -3,6 +3,7 @@ package modutils import ( "bufio" "bytes" + "encoding/json" "os" "os/exec" "strings" @@ -62,9 +63,35 @@ func GetCurrentModPackageFile() (string, error) { return trimmedModFile, nil } -func GetCurrentPackageList() (*bytes.Buffer, error) { +func GetCurrentPackageListAll() (*bytes.Buffer, error) { + return goModListWrapper(nil, "") +} + +func GetCurrentPackageListJson(modules []string) ([]*Module, error) { + var packages []*Module + for _, v := range modules { + jsonByt, err := goModListWrapper([]string{"-json"}, v) + if err != nil { + return nil, err + } + var jsonModule Module + if err := json.Unmarshal(jsonByt.Bytes(), &jsonModule); err != nil { + return nil, err + } + packages = append(packages, &jsonModule) + } + return packages, nil +} + +func goModListWrapper(args []string, packageName string) (*bytes.Buffer, error) { + args = append([]string{"list", "-m"}, args...) + if packageName != "" { + args = append(args, packageName) + } else { + args = append(args, "all") + } + packageListCmd := exec.Command("go", args...) modPackageReader := &bytes.Buffer{} - packageListCmd := exec.Command("go", "list", "-m", "all") packageListCmd.Stdout = modPackageReader packageListCmd.Stderr = modPackageReader err := packageListCmd.Run() diff --git a/pkg/modutils/mod_test.go b/pkg/modutils/mod_test.go index 7b5be51..59b063c 100644 --- a/pkg/modutils/mod_test.go +++ b/pkg/modutils/mod_test.go @@ -17,10 +17,19 @@ var _ = Describe("modutils", func() { Expect(err).NotTo(HaveOccurred()) pacakgeName, err := GetCurrentModPackageName(name) Expect(err).NotTo(HaveOccurred()) - Expect(pacakgeName).To(Equal("github.com/solo-io/protodep")) + Expect(pacakgeName).To(Equal("github.com/solo-io/anyvendor")) }) It("can list the packages used by this module", func() { - _, err := GetCurrentPackageList() + _, err := GetCurrentPackageListAll() + Expect(err).NotTo(HaveOccurred()) + }) + It("can list the packages used by this module (json)", func() { + modules := []string{ + "github.com/solo-io/anyvendor", + "github.com/envoyproxy/protoc-gen-validate", + } + list, err := GetCurrentPackageListJson(modules) + Expect(list[0].Path).To(Equal("github.com/solo-io/anyvendor")) Expect(err).NotTo(HaveOccurred()) }) }) diff --git a/protodep/protodep.proto b/protodep/protodep.proto deleted file mode 100644 index eb07ca8..0000000 --- a/protodep/protodep.proto +++ /dev/null @@ -1,27 +0,0 @@ -syntax = "proto3"; -package protodep; -option go_package = "github.com/solo-io/protodep/protodep"; - -import "validate/validate.proto"; - -message Config { - Local local = 1; - - repeated Import imports = 2; -} - -message Import { - oneof ImportType { - option (validate.required) = true; - GoModImport go_mod = 2; - } -} - -message Local { - repeated string patterns = 1; -} - -message GoModImport { - repeated string patterns = 1 [(validate.rules).repeated = { min_items: 1}]; - string package = 2 [(validate.rules).string = { min_len: 1}]; -}