diff --git a/command/install.go b/command/install.go index ddee672b7..a619dd59a 100644 --- a/command/install.go +++ b/command/install.go @@ -42,6 +42,6 @@ var installCmd = &cobra.Command{ func init() { installCmd.Flags().BoolVarP(&force, "force", "f", false, "Force a new installation") - installCmd.Flags().StringVarP(&libDir, "libDir", "d", "/usr/local/lib", "Target directory to install the library") + installCmd.Flags().StringVarP(&libDir, "libDir", "d", "", "Target directory to install the library") RootCmd.AddCommand(installCmd) } diff --git a/examples/v3/provider_test.go b/examples/v3/provider_test.go index 5064fb856..0f25b2d07 100644 --- a/examples/v3/provider_test.go +++ b/examples/v3/provider_test.go @@ -23,6 +23,7 @@ var pactDir = fmt.Sprintf("%s/pacts", dir) // 2. go test -v -run TestProvider func TestV3HTTPProvider(t *testing.T) { v3.SetLogLevel("DEBUG") + v3.CheckVersion() // Start provider API in the background go startServer() diff --git a/v3/http.go b/v3/http.go index b4e2b836f..844327126 100644 --- a/v3/http.go +++ b/v3/http.go @@ -18,12 +18,12 @@ import ( "time" "github.com/pact-foundation/pact-go/utils" + "github.com/pact-foundation/pact-go/v3/internal/native/mockserver" native "github.com/pact-foundation/pact-go/v3/internal/native/mockserver" ) func init() { initLogging() - native.Init() } // QueryStringStyle allows a user to specific the v2 query string serialisation format @@ -121,7 +121,8 @@ type httpMockProvider struct { v3Interactions []*InteractionV3 // fsm state of the interaction - state string + state string + mockserver *mockserver.MockServer } // MockServerConfig stores the address configuration details of the server for the current executing test @@ -168,6 +169,9 @@ func (p *httpMockProvider) validateConfig() error { return fmt.Errorf("error: unable to find free port, mock server will fail to start") } + p.mockserver = &mockserver.MockServer{} + p.mockserver.Init() + return nil } @@ -195,8 +199,8 @@ func (p *httpMockProvider) ExecuteTest(integrationTest func(MockServerConfig) er // Clean interactions p.cleanInteractions() - port, err := native.CreateMockServer(formatJSONObject(serialisedPact), fmt.Sprintf("%s:%d", p.config.Host, p.config.Port), p.config.TLS) - defer native.CleanupMockServer(p.config.Port) + port, err := p.mockserver.CreateMockServer(formatJSONObject(serialisedPact), fmt.Sprintf("%s:%d", p.config.Host, p.config.Port), p.config.TLS) + defer p.mockserver.CleanupMockServer(p.config.Port) if err != nil { return err } @@ -213,7 +217,7 @@ func (p *httpMockProvider) ExecuteTest(integrationTest func(MockServerConfig) er } // Run Verification Process - res, mismatches := native.Verify(p.config.Port, p.config.PactDir) + res, mismatches := p.mockserver.Verify(p.config.Port, p.config.PactDir) p.displayMismatches(mismatches) if !res { @@ -225,7 +229,7 @@ func (p *httpMockProvider) ExecuteTest(integrationTest func(MockServerConfig) er // TODO: pretty print this to make it really easy to understand the problems // See existing Pact/Ruby code examples -func (p *httpMockProvider) displayMismatches(mismatches []native.MismatchedRequest) { +func (p *httpMockProvider) displayMismatches(mismatches []mockserver.MismatchedRequest) { if len(mismatches) > 0 { log.Println("[INFO] pact validation failed, errors: ") for _, m := range mismatches { @@ -259,7 +263,7 @@ func (p *httpMockProvider) displayMismatches(mismatches []native.MismatchedReque func (p *httpMockProvider) WritePact() error { log.Println("[DEBUG] write pact file") if p.config.Port != 0 { - return native.WritePactFile(p.config.Port, p.config.PactDir) + return p.mockserver.WritePactFile(p.config.Port, p.config.PactDir) } return errors.New("pact server not yet started") } diff --git a/v3/installer/installer.go b/v3/installer/installer.go index ca53dbb0a..b3b8f9c83 100644 --- a/v3/installer/installer.go +++ b/v3/installer/installer.go @@ -5,6 +5,7 @@ package installer import ( "fmt" "log" + "os" "os/exec" "path" "runtime" @@ -13,20 +14,9 @@ import ( getter "github.com/hashicorp/go-getter" goversion "github.com/hashicorp/go-version" - // can't use these packages, because then the CLI installer wouldn't work - go won't run without it! - // "github.com/pact-foundation/pact-go/v3/internal/native/verifier" - // mockserver "github.com/pact-foundation/pact-go/v3/internal/native/mockserver" - "github.com/spf13/afero" ) -// Installer manages the underlying Ruby installation -// (eventual) implementation requirements -// 1. Download OS specific artifacts if not pre-installed - DONE -// 1. Check the semver range of pre-installed artifacts - DONE -// 1. Enable global configuration (environment vars, config files, code options e.g. (`PACT_GO_SHARED_LIBRARY_PATH`)) -// 1. Allow users to specify where they pre-install their artifacts (e.g. /usr/local/lib) - // Installer is used to check the Pact Go installation is setup correctly, and can automatically install // packages if required type Installer struct { @@ -80,18 +70,13 @@ func (i *Installer) Force(force bool) { func (i *Installer) CheckInstallation() error { // Check if files exist - // --> Check versions of existing installed files + // --> Check if existing installed files if !i.force { if err := i.checkPackageInstall(); err == nil { return nil } } - // Check if override package path exists - // -> if it does, copy files from existing location - // --> Check versions of these files - // --> copy files to lib dir - // Download dependencies if err := i.downloadDependencies(); err != nil { return err @@ -102,29 +87,32 @@ func (i *Installer) CheckInstallation() error { return err } + // Double check files landed correctly (can't execute 'version' call here, + // because of dependency on the native libs we're trying to download!) if err := i.checkPackageInstall(); err != nil { return fmt.Errorf("unable to verify downloaded/installed dependencies: %s", err) } - // --> Check if download is disabled (return error if downloads are disabled) - // --> download files to lib dir - return nil } func (i *Installer) getLibDir() string { - if i.libDir == "" { - return "/usr/local/lib" + if i.libDir != "" { + return i.libDir } - return i.libDir + env := os.Getenv(downloadEnvVar) + if env != "" { + return env + } + + return "/usr/local/lib" } // checkPackageInstall discovers any existing packages, and checks installation of a given binary using semver-compatible checks func (i *Installer) checkPackageInstall() error { for pkg, info := range packages { - log.Println("[DEBUG] checking version for lib", info.libName, "semver range", info.semverRange) dst, _ := i.getLibDstForPackage(pkg) if _, err := i.fs.Stat(dst); err != nil { @@ -132,9 +120,16 @@ func (i *Installer) checkPackageInstall() error { return err } - // if err := checkVersion(info.libName, info.testCommand(), info.semverRange); err != nil { - // return err - // } + lib, ok := LibRegistry[pkg] + + if ok { + log.Println("[INFO] checking version", lib.Version(), "for lib", info.libName, "within semver range", info.semverRange) + if err := checkVersion(info.libName, lib.Version(), info.semverRange); err != nil { + return err + } + } else { + log.Println("[DEBUG] lib registry is currently not populated for package", pkg, "this is probably because the package is currently being installed") + } } return nil @@ -226,6 +221,27 @@ var setOSXInstallName = func(file string, lib string) error { return err } +func checkVersion(lib, version, versionRange string) error { + log.Println("[DEBUG] checking version", version, "of", lib, "against semver constraint", versionRange) + + v, err := goversion.NewVersion(version) + if err != nil { + return err + } + + constraints, err := goversion.NewConstraint(versionRange) + if err != nil { + return err + } + + if constraints.Check(v) { + log.Println("[DEBUG]", v, "satisfies constraints", v, constraints) + return nil + } + + return fmt.Errorf("version %s of %s does not match constraint %s", version, lib, versionRange) +} + // download template structure: "https://github.com/pact-foundation/pact-reference/releases/download/PACKAGE-vVERSION/LIBNAME-OS-ARCH.EXTENSION.gz" var downloadTemplate = "https://github.com/pact-foundation/pact-reference/releases/download/%s-v%s/%s-%s-%s.%s.gz" @@ -245,12 +261,12 @@ type packageInfo struct { libName string version string semverRange string - testCommand func() string } const ( - verifierPackage = "pact_verifier_ffi" - mockServerPackage = "libpact_mock_server_ffi" + VerifierPackage = "pact_verifier_ffi" + MockServerPackage = "libpact_mock_server_ffi" + downloadEnvVar = "PACT_GO_LIB_DOWNLOAD_PATH" linux = "linux" windows = "windows" osx = "osx" @@ -258,45 +274,24 @@ const ( ) var packages = map[string]packageInfo{ - verifierPackage: { + VerifierPackage: { libName: "libpact_verifier_ffi", version: "0.0.2", semverRange: ">= 0.0.2, < 1.0.0", - // testCommand: func() string { - // return (&verifier.Verifier{}).Version() - // }, }, - mockServerPackage: { + MockServerPackage: { libName: "libpact_mock_server_ffi", version: "0.0.15", semverRange: ">= 0.0.15, < 1.0.0", - // testCommand: func() string { - // return mockserver.Version() - // }, }, } -func checkVersion(lib, version, versionRange string) error { - log.Println("[DEBUG] checking version", version, "of", lib, "against semver constraint", versionRange) - - v, err := goversion.NewVersion(version) - if err != nil { - return err - } - - constraints, err := goversion.NewConstraint(versionRange) - if err != nil { - return err - } - - if constraints.Check(v) { - log.Println("[DEBUG]", v, "satisfies constraints", v, constraints) - return nil - } - - return fmt.Errorf("version %s of %s does not match constraint %s", version, lib, versionRange) +type Versioner interface { + Version() string } +var LibRegistry = map[string]Versioner{} + type downloader interface { download(src string, dst string) error } diff --git a/v3/installer/installer_test.go b/v3/installer/installer_test.go index a76378fa5..f22bb1dff 100644 --- a/v3/installer/installer_test.go +++ b/v3/installer/installer_test.go @@ -24,7 +24,7 @@ func TestInstallerDownloader(t *testing.T) { }{ { name: "mock server - linux x86", - pkg: mockServerPackage, + pkg: MockServerPackage, want: "https://github.com/pact-foundation/pact-reference/releases/download/libpact_mock_server_ffi-v0.0.15/libpact_mock_server_ffi-linux-x86_64.so.gz", test: Installer{ os: linux, @@ -33,7 +33,7 @@ func TestInstallerDownloader(t *testing.T) { }, { name: "mock server - osx x86", - pkg: mockServerPackage, + pkg: MockServerPackage, want: "https://github.com/pact-foundation/pact-reference/releases/download/libpact_mock_server_ffi-v0.0.15/libpact_mock_server_ffi-osx-x86_64.dylib.gz", test: Installer{ os: osx, @@ -42,7 +42,7 @@ func TestInstallerDownloader(t *testing.T) { }, { name: "mock server - linux x86", - pkg: mockServerPackage, + pkg: MockServerPackage, want: "https://github.com/pact-foundation/pact-reference/releases/download/libpact_mock_server_ffi-v0.0.15/libpact_mock_server_ffi-windows-x86_64.dll.gz", test: Installer{ os: windows, @@ -80,7 +80,7 @@ func TestInstallerDownloader(t *testing.T) { defer func() { packages = oldPackages }() packages = map[string]packageInfo{ - verifierPackage: { + VerifierPackage: { libName: "libpact_verifier_ffi", version: "0.0.2", semverRange: ">= 0.8.3, < 1.0.0", @@ -88,7 +88,7 @@ func TestInstallerDownloader(t *testing.T) { return "0.8.0" }, }, - mockServerPackage: { + MockServerPackage: { libName: "libpact_mock_server_ffi", version: "0.0.15", semverRange: ">= 0.0.15, < 1.0.0", diff --git a/v3/internal/checker/checker.go b/v3/internal/checker/checker.go new file mode 100644 index 000000000..b0b2823de --- /dev/null +++ b/v3/internal/checker/checker.go @@ -0,0 +1,20 @@ +package checker + +import ( + "github.com/pact-foundation/pact-go/v3/installer" + "github.com/pact-foundation/pact-go/v3/internal/native/mockserver" + "github.com/pact-foundation/pact-go/v3/internal/native/verifier" +) + +func CheckInstall() error { + // initialised the lib registry + installer.LibRegistry[installer.MockServerPackage] = &mockserver.MockServer{} + installer.LibRegistry[installer.VerifierPackage] = &verifier.Verifier{} + + i, err := installer.NewInstaller() + if err != nil { + return err + } + + return i.CheckInstallation() +} diff --git a/v3/internal/native/mockserver/mock_server.go b/v3/internal/native/mockserver/mock_server.go index adf561dcd..1e4e1b0a0 100644 --- a/v3/internal/native/mockserver/mock_server.go +++ b/v3/internal/native/mockserver/mock_server.go @@ -72,8 +72,10 @@ type MismatchedRequest struct { Type string } +type MockServer struct{} + // Init initialises the library -func Init() { +func (m *MockServer) Init() { log.Println("[DEBUG] initialising rust mock server interface") logLevel := C.CString("LOG_LEVEL") defer free(logLevel) @@ -83,7 +85,7 @@ func Init() { // CreateMockServer creates a new Mock Server from a given Pact file. // Returns the port number it started on or an error if failed -func CreateMockServer(pact string, address string, tls bool) (int, error) { +func (m *MockServer) CreateMockServer(pact string, address string, tls bool) (int, error) { log.Println("[DEBUG] mock server starting on address:", address) cPact := C.CString(pact) cAddress := C.CString(address) @@ -129,10 +131,10 @@ func CreateMockServer(pact string, address string, tls bool) (int, error) { // Verify verifies that all interactions were successful. If not, returns a slice // of Mismatch-es. Does not write the pact or cleanup server. -func Verify(port int, dir string) (bool, []MismatchedRequest) { +func (m *MockServer) Verify(port int, dir string) (bool, []MismatchedRequest) { res := C.mock_server_matched(C.int(port)) - mismatches := MockServerMismatchedRequests(port) + mismatches := m.MockServerMismatchedRequests(port) log.Println("[DEBUG] mock server mismatches:", len(mismatches)) return int(res) == 1, mismatches @@ -140,7 +142,7 @@ func Verify(port int, dir string) (bool, []MismatchedRequest) { // MockServerMismatchedRequests returns a JSON object containing any mismatches from // the last set of interactions. -func MockServerMismatchedRequests(port int) []MismatchedRequest { +func (m *MockServer) MockServerMismatchedRequests(port int) []MismatchedRequest { log.Println("[DEBUG] mock server determining mismatches:", port) var res []MismatchedRequest @@ -151,7 +153,7 @@ func MockServerMismatchedRequests(port int) []MismatchedRequest { } // CleanupMockServer frees the memory from the previous mock server. -func CleanupMockServer(port int) bool { +func (m *MockServer) CleanupMockServer(port int) bool { log.Println("[DEBUG] mock server cleaning up port:", port) res := C.cleanup_mock_server(C.int(port)) @@ -186,7 +188,7 @@ var ( ) // WritePactFile writes the Pact to file. -func WritePactFile(port int, dir string) error { +func (m *MockServer) WritePactFile(port int, dir string) error { log.Println("[DEBUG] writing pact file for mock server on port:", port, ", dir:", dir) cDir := C.CString(dir) defer free(cDir) @@ -228,7 +230,7 @@ func GetTLSConfig() *tls.Config { } // Version returns the current semver FFI interface version -func Version() string { +func (m *MockServer) Version() string { v := C.version() return C.GoString(v) diff --git a/v3/internal/native/mockserver/mock_server_lib.go b/v3/internal/native/mockserver/mock_server_lib.go index f203ba0c4..6dd89e952 100644 --- a/v3/internal/native/mockserver/mock_server_lib.go +++ b/v3/internal/native/mockserver/mock_server_lib.go @@ -1,9 +1,9 @@ package mockserver /* -#cgo darwin,amd64 LDFLAGS: -lpact_mock_server_ffi +#cgo darwin,amd64 LDFLAGS: -L/opt/pact/lib -Wl,-rpath -Wl,/opt/pact/lib -Wl,-rpath -Wl,/tmp -Wl,-rpath -Wl,/usr/local/lib -lpact_mock_server_ffi #cgo windows,amd64 LDFLAGS: -lpact_mock_server_ffi -#cgo linux,amd64 LDFLAGS: -L/tmp -L/usr/local/lib -Wl,-rpath -Wl,/tmp -Wl,-rpath -Wl,/usr/local/lib -lpact_mock_server_ffi +#cgo linux,amd64 LDFLAGS: -Wl,-rpath -Wl,/opt/pact/lib -Wl,-rpath -Wl,/tmp -Wl,-rpath -Wl,/usr/local/lib -lpact_mock_server_ffi // Mac OSX (until https://github.com/pact-foundation/pact-reference/pull/93 is done) // install_name_tool -id "libpact_mock_server_ffi" libpact_mock_server_ffi.dylib diff --git a/v3/internal/native/verifier/verifier_lib.go b/v3/internal/native/verifier/verifier_lib.go index 861a5d0e6..87cb7126a 100644 --- a/v3/internal/native/verifier/verifier_lib.go +++ b/v3/internal/native/verifier/verifier_lib.go @@ -1,9 +1,9 @@ package verifier /* -#cgo darwin,amd64 LDFLAGS: -lpact_verifier_ffi +#cgo darwin,amd64 LDFLAGS: -L/opt/pact/lib -Wl,-rpath -Wl,/opt/pact/lib -Wl,-rpath -Wl,/tmp -Wl,-rpath -Wl,/usr/local/lib -lpact_verifier_ffi #cgo windows,amd64 LDFLAGS: -lpact_verifier_ffi -#cgo linux,amd64 LDFLAGS: -L/tmp -L/usr/local/lib -Wl,-rpath -Wl,/tmp -Wl,-rpath -Wl,/usr/local/lib -lpact_verifier_ffi +#cgo linux,amd64 LDFLAGS: -Wl,-rpath -Wl,/opt/pact/lib -Wl,-rpath -Wl,/tmp -Wl,-rpath -Wl,/usr/local/lib -lpact_verifier_ffi // Mac OSX (until https://github.com/pact-foundation/pact-reference/pull/93 is done) // install_name_tool -id "libpact_verifier_ffi.dylib" /usr/local/lib/libpact_verifier_ffi.dylib diff --git a/v3/version.go b/v3/version.go new file mode 100644 index 000000000..267a47ef0 --- /dev/null +++ b/v3/version.go @@ -0,0 +1,18 @@ +package v3 + +import ( + "log" + + "github.com/pact-foundation/pact-go/v3/internal/checker" +) + +// CheckVersion checks if the currently installed version is within semver range +// and will attempt to download the files to the default or configured directory if +// incorrect +func CheckVersion() { + if err := checker.CheckInstall(); err != nil { + log.Fatal("check version failed:", err) + } + + log.Println("[DEBUG] version check completed") +}