From bb7370b8663128f26eb3c501f96f1bc080a00b4f Mon Sep 17 00:00:00 2001 From: Marc Campbell Date: Fri, 14 Jun 2019 02:09:08 +0000 Subject: [PATCH] Vendored --- Gopkg.lock | 24 + Gopkg.toml | 4 + .../github.com/hashicorp/logutils/.gitignore | 22 + vendor/github.com/hashicorp/logutils/LICENSE | 354 +++++++++ .../github.com/hashicorp/logutils/README.md | 36 + vendor/github.com/hashicorp/logutils/go.mod | 1 + vendor/github.com/hashicorp/logutils/level.go | 81 ++ .../pact-foundation/pact-go/LICENSE | 21 + .../pact-go/client/message_service.go | 24 + .../pact-go/client/mock_service.go | 21 + .../pact-go/client/publish_service.go | 39 + .../pact-foundation/pact-go/client/service.go | 26 + .../pact-go/client/service_manager.go | 178 +++++ .../pact-go/client/verification_service.go | 37 + .../pact-foundation/pact-go/dsl/client.go | 423 +++++++++++ .../pact-go/dsl/interaction.go | 84 +++ .../pact-foundation/pact-go/dsl/matcher.go | 399 ++++++++++ .../pact-foundation/pact-go/dsl/message.go | 97 +++ .../pact-go/dsl/mock_client.go | 79 ++ .../pact-go/dsl/mock_service.go | 114 +++ .../pact-foundation/pact-go/dsl/pact.go | 706 ++++++++++++++++++ .../pact-foundation/pact-go/dsl/publish.go | 45 ++ .../pact-foundation/pact-go/dsl/request.go | 10 + .../pact-foundation/pact-go/dsl/response.go | 8 + .../pact-go/dsl/service_mock.go | 65 ++ .../pact-go/dsl/verify_mesage_request.go | 80 ++ .../pact-go/install/installer.go | 101 +++ .../pact-foundation/pact-go/proxy/http.go | 84 +++ .../pact-go/types/command_response.go | 12 + .../pact-foundation/pact-go/types/handler.go | 14 + .../pact-go/types/mock_server.go | 9 + .../pact-go/types/pact_message_request.go | 49 ++ .../pact-go/types/pact_reification_request.go | 32 + .../pact-go/types/provider_state.go | 15 + .../types/provider_verifier_response.go | 30 + .../pact-go/types/publish_request.go | 90 +++ .../pact-go/types/reification_response.go | 10 + .../pact-go/types/verify_request.go | 161 ++++ .../pact-foundation/pact-go/utils/port.go | 87 +++ 39 files changed, 3672 insertions(+) create mode 100644 vendor/github.com/hashicorp/logutils/.gitignore create mode 100644 vendor/github.com/hashicorp/logutils/LICENSE create mode 100644 vendor/github.com/hashicorp/logutils/README.md create mode 100644 vendor/github.com/hashicorp/logutils/go.mod create mode 100644 vendor/github.com/hashicorp/logutils/level.go create mode 100644 vendor/github.com/pact-foundation/pact-go/LICENSE create mode 100644 vendor/github.com/pact-foundation/pact-go/client/message_service.go create mode 100644 vendor/github.com/pact-foundation/pact-go/client/mock_service.go create mode 100644 vendor/github.com/pact-foundation/pact-go/client/publish_service.go create mode 100644 vendor/github.com/pact-foundation/pact-go/client/service.go create mode 100644 vendor/github.com/pact-foundation/pact-go/client/service_manager.go create mode 100644 vendor/github.com/pact-foundation/pact-go/client/verification_service.go create mode 100644 vendor/github.com/pact-foundation/pact-go/dsl/client.go create mode 100644 vendor/github.com/pact-foundation/pact-go/dsl/interaction.go create mode 100644 vendor/github.com/pact-foundation/pact-go/dsl/matcher.go create mode 100644 vendor/github.com/pact-foundation/pact-go/dsl/message.go create mode 100644 vendor/github.com/pact-foundation/pact-go/dsl/mock_client.go create mode 100644 vendor/github.com/pact-foundation/pact-go/dsl/mock_service.go create mode 100644 vendor/github.com/pact-foundation/pact-go/dsl/pact.go create mode 100644 vendor/github.com/pact-foundation/pact-go/dsl/publish.go create mode 100644 vendor/github.com/pact-foundation/pact-go/dsl/request.go create mode 100644 vendor/github.com/pact-foundation/pact-go/dsl/response.go create mode 100644 vendor/github.com/pact-foundation/pact-go/dsl/service_mock.go create mode 100644 vendor/github.com/pact-foundation/pact-go/dsl/verify_mesage_request.go create mode 100644 vendor/github.com/pact-foundation/pact-go/install/installer.go create mode 100644 vendor/github.com/pact-foundation/pact-go/proxy/http.go create mode 100644 vendor/github.com/pact-foundation/pact-go/types/command_response.go create mode 100644 vendor/github.com/pact-foundation/pact-go/types/handler.go create mode 100644 vendor/github.com/pact-foundation/pact-go/types/mock_server.go create mode 100644 vendor/github.com/pact-foundation/pact-go/types/pact_message_request.go create mode 100644 vendor/github.com/pact-foundation/pact-go/types/pact_reification_request.go create mode 100644 vendor/github.com/pact-foundation/pact-go/types/provider_state.go create mode 100644 vendor/github.com/pact-foundation/pact-go/types/provider_verifier_response.go create mode 100644 vendor/github.com/pact-foundation/pact-go/types/publish_request.go create mode 100644 vendor/github.com/pact-foundation/pact-go/types/reification_response.go create mode 100644 vendor/github.com/pact-foundation/pact-go/types/verify_request.go create mode 100644 vendor/github.com/pact-foundation/pact-go/utils/port.go diff --git a/Gopkg.lock b/Gopkg.lock index a958ffdf0..4c96ba686 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -793,6 +793,14 @@ pruneopts = "UT" revision = "fa9f258a92500514cc8e9c67020487709df92432" +[[projects]] + branch = "master" + digest = "1:16ae35b3a854c667baaf55ff5d455c486f7c2baf040a2727f2ef0e4b096b2a95" + name = "github.com/hashicorp/logutils" + packages = ["."] + pruneopts = "UT" + revision = "a335183dfd075f638afcc820c90591ca3c97eba6" + [[projects]] digest = "1:2f41d2e82a74475b356fcc25c7a2fcbe156c6ee1c9def67706a4c5b6e72ba828" name = "github.com/hashicorp/terraform" @@ -1121,6 +1129,21 @@ revision = "d60099175f88c47cd379c4738d158884749ed235" version = "v1.0.1" +[[projects]] + digest = "1:3bf1b548c360f000cd76b577960e7c4536de51dae3b3d125371f2d117b2ef00e" + name = "github.com/pact-foundation/pact-go" + packages = [ + "client", + "dsl", + "install", + "proxy", + "types", + "utils", + ] + pruneopts = "UT" + revision = "b5a493b9157808d187d372288d092cfef9cec25f" + version = "v1.0.0-beta.5" + [[projects]] digest = "1:342c898ef85c5032840d1518520957db169673495e4e1455f3f1f0eaca9a4994" name = "github.com/pelletier/go-toml" @@ -2157,6 +2180,7 @@ "github.com/onsi/ginkgo", "github.com/onsi/gomega", "github.com/onsi/gomega/format", + "github.com/pact-foundation/pact-go/dsl", "github.com/pkg/errors", "github.com/pmezard/go-difflib/difflib", "github.com/replicatedhq/libyaml", diff --git a/Gopkg.toml b/Gopkg.toml index c2fd542e1..5b21a29b1 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -142,3 +142,7 @@ ignored = [ [[override]] name = "gopkg.in/fsnotify.v1" source = "https://github.com/fsnotify/fsnotify.git" + +[[constraint]] + name = "github.com/pact-foundation/pact-go" + version = "1.0.0-beta" diff --git a/vendor/github.com/hashicorp/logutils/.gitignore b/vendor/github.com/hashicorp/logutils/.gitignore new file mode 100644 index 000000000..00268614f --- /dev/null +++ b/vendor/github.com/hashicorp/logutils/.gitignore @@ -0,0 +1,22 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe diff --git a/vendor/github.com/hashicorp/logutils/LICENSE b/vendor/github.com/hashicorp/logutils/LICENSE new file mode 100644 index 000000000..c33dcc7c9 --- /dev/null +++ b/vendor/github.com/hashicorp/logutils/LICENSE @@ -0,0 +1,354 @@ +Mozilla Public License, version 2.0 + +1. Definitions + +1.1. “Contributor” + + means each individual or legal entity that creates, contributes to the + creation of, or owns Covered Software. + +1.2. “Contributor Version” + + means the combination of the Contributions of others (if any) used by a + Contributor and that particular Contributor’s Contribution. + +1.3. “Contribution” + + means Covered Software of a particular Contributor. + +1.4. “Covered Software” + + means Source Code Form to which the initial Contributor has attached the + notice in Exhibit A, the Executable Form of such Source Code Form, and + Modifications of such Source Code Form, in each case including portions + thereof. + +1.5. “Incompatible With Secondary Licenses” + means + + a. that the initial Contributor has attached the notice described in + Exhibit B to the Covered Software; or + + b. that the Covered Software was made available under the terms of version + 1.1 or earlier of the License, but not also under the terms of a + Secondary License. + +1.6. “Executable Form” + + means any form of the work other than Source Code Form. + +1.7. “Larger Work” + + means a work that combines Covered Software with other material, in a separate + file or files, that is not Covered Software. + +1.8. “License” + + means this document. + +1.9. “Licensable” + + means having the right to grant, to the maximum extent possible, whether at the + time of the initial grant or subsequently, any and all of the rights conveyed by + this License. + +1.10. “Modifications” + + means any of the following: + + a. any file in Source Code Form that results from an addition to, deletion + from, or modification of the contents of Covered Software; or + + b. any new file in Source Code Form that contains any Covered Software. + +1.11. “Patent Claims” of a Contributor + + means any patent claim(s), including without limitation, method, process, + and apparatus claims, in any patent Licensable by such Contributor that + would be infringed, but for the grant of the License, by the making, + using, selling, offering for sale, having made, import, or transfer of + either its Contributions or its Contributor Version. + +1.12. “Secondary License” + + means either the GNU General Public License, Version 2.0, the GNU Lesser + General Public License, Version 2.1, the GNU Affero General Public + License, Version 3.0, or any later versions of those licenses. + +1.13. “Source Code Form” + + means the form of the work preferred for making modifications. + +1.14. “You” (or “Your”) + + means an individual or a legal entity exercising rights under this + License. For legal entities, “You” includes any entity that controls, is + controlled by, or is under common control with You. For purposes of this + definition, “control” means (a) the power, direct or indirect, to cause + the direction or management of such entity, whether by contract or + otherwise, or (b) ownership of more than fifty percent (50%) of the + outstanding shares or beneficial ownership of such entity. + + +2. License Grants and Conditions + +2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + a. under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or as + part of a Larger Work; and + + b. under Patent Claims of such Contributor to make, use, sell, offer for + sale, have made, import, and otherwise transfer either its Contributions + or its Contributor Version. + +2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution become + effective for each Contribution on the date the Contributor first distributes + such Contribution. + +2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under this + License. No additional rights or licenses will be implied from the distribution + or licensing of Covered Software under this License. Notwithstanding Section + 2.1(b) above, no patent license is granted by a Contributor: + + a. for any code that a Contributor has removed from Covered Software; or + + b. for infringements caused by: (i) Your and any other third party’s + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + c. under Patent Claims infringed by Covered Software in the absence of its + Contributions. + + This License does not grant any rights in the trademarks, service marks, or + logos of any Contributor (except as may be necessary to comply with the + notice requirements in Section 3.4). + +2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this License + (see Section 10.2) or under the terms of a Secondary License (if permitted + under the terms of Section 3.3). + +2.5. Representation + + Each Contributor represents that the Contributor believes its Contributions + are its original creation(s) or it has sufficient rights to grant the + rights to its Contributions conveyed by this License. + +2.6. Fair Use + + This License is not intended to limit any rights You have under applicable + copyright doctrines of fair use, fair dealing, or other equivalents. + +2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in + Section 2.1. + + +3. Responsibilities + +3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under the + terms of this License. You must inform recipients that the Source Code Form + of the Covered Software is governed by the terms of this License, and how + they can obtain a copy of this License. You may not attempt to alter or + restrict the recipients’ rights in the Source Code Form. + +3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + a. such Covered Software must also be made available in Source Code Form, + as described in Section 3.1, and You must inform recipients of the + Executable Form how they can obtain a copy of such Source Code Form by + reasonable means in a timely manner, at a charge no more than the cost + of distribution to the recipient; and + + b. You may distribute such Executable Form under the terms of this License, + or sublicense it under different terms, provided that the license for + the Executable Form does not attempt to limit or alter the recipients’ + rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for the + Covered Software. If the Larger Work is a combination of Covered Software + with a work governed by one or more Secondary Licenses, and the Covered + Software is not Incompatible With Secondary Licenses, this License permits + You to additionally distribute such Covered Software under the terms of + such Secondary License(s), so that the recipient of the Larger Work may, at + their option, further distribute the Covered Software under the terms of + either this License or such Secondary License(s). + +3.4. Notices + + You may not remove or alter the substance of any license notices (including + copyright notices, patent notices, disclaimers of warranty, or limitations + of liability) contained within the Source Code Form of the Covered + Software, except that You may alter any license notices to the extent + required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on behalf + of any Contributor. You must make it absolutely clear that any such + warranty, support, indemnity, or liability obligation is offered by You + alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + +4. Inability to Comply Due to Statute or Regulation + + If it is impossible for You to comply with any of the terms of this License + with respect to some or all of the Covered Software due to statute, judicial + order, or regulation then You must: (a) comply with the terms of this License + to the maximum extent possible; and (b) describe the limitations and the code + they affect. Such description must be placed in a text file included with all + distributions of the Covered Software under this License. Except to the + extent prohibited by statute or regulation, such description must be + sufficiently detailed for a recipient of ordinary skill to be able to + understand it. + +5. Termination + +5.1. The rights granted under this License will terminate automatically if You + fail to comply with any of its terms. However, if You become compliant, + then the rights granted under this License from a particular Contributor + are reinstated (a) provisionally, unless and until such Contributor + explicitly and finally terminates Your grants, and (b) on an ongoing basis, + if such Contributor fails to notify You of the non-compliance by some + reasonable means prior to 60 days after You have come back into compliance. + Moreover, Your grants from a particular Contributor are reinstated on an + ongoing basis if such Contributor notifies You of the non-compliance by + some reasonable means, this is the first time You have received notice of + non-compliance with this License from such Contributor, and You become + compliant prior to 30 days after Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, counter-claims, + and cross-claims) alleging that a Contributor Version directly or + indirectly infringes any patent, then the rights granted to You by any and + all Contributors for the Covered Software under Section 2.1 of this License + shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user + license agreements (excluding distributors and resellers) which have been + validly granted by You or Your distributors under this License prior to + termination shall survive termination. + +6. Disclaimer of Warranty + + Covered Software is provided under this License on an “as is” basis, without + warranty of any kind, either expressed, implied, or statutory, including, + without limitation, warranties that the Covered Software is free of defects, + merchantable, fit for a particular purpose or non-infringing. The entire + risk as to the quality and performance of the Covered Software is with You. + Should any Covered Software prove defective in any respect, You (not any + Contributor) assume the cost of any necessary servicing, repair, or + correction. This disclaimer of warranty constitutes an essential part of this + License. No use of any Covered Software is authorized under this License + except under this disclaimer. + +7. Limitation of Liability + + Under no circumstances and under no legal theory, whether tort (including + negligence), contract, or otherwise, shall any Contributor, or anyone who + distributes Covered Software as permitted above, be liable to You for any + direct, indirect, special, incidental, or consequential damages of any + character including, without limitation, damages for lost profits, loss of + goodwill, work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses, even if such party shall have been + informed of the possibility of such damages. This limitation of liability + shall not apply to liability for death or personal injury resulting from such + party’s negligence to the extent applicable law prohibits such limitation. + Some jurisdictions do not allow the exclusion or limitation of incidental or + consequential damages, so this exclusion and limitation may not apply to You. + +8. Litigation + + Any litigation relating to this License may be brought only in the courts of + a jurisdiction where the defendant maintains its principal place of business + and such litigation shall be governed by laws of that jurisdiction, without + reference to its conflict-of-law provisions. Nothing in this Section shall + prevent a party’s ability to bring cross-claims or counter-claims. + +9. Miscellaneous + + This License represents the complete agreement concerning the subject matter + hereof. If any provision of this License is held to be unenforceable, such + provision shall be reformed only to the extent necessary to make it + enforceable. Any law or regulation which provides that the language of a + contract shall be construed against the drafter shall not be used to construe + this License against a Contributor. + + +10. Versions of the License + +10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + +10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version of + the License under which You originally received the Covered Software, or + under the terms of any subsequent version published by the license + steward. + +10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a modified + version of this License if you rename the license and remove any + references to the name of the license steward (except to note that such + modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice + + This Source Code Form is subject to the + terms of the Mozilla Public License, v. + 2.0. If a copy of the MPL was not + distributed with this file, You can + obtain one at + http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, then +You may include the notice in a location (such as a LICENSE file in a relevant +directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - “Incompatible With Secondary Licenses” Notice + + This Source Code Form is “Incompatible + With Secondary Licenses”, as defined by + the Mozilla Public License, v. 2.0. + diff --git a/vendor/github.com/hashicorp/logutils/README.md b/vendor/github.com/hashicorp/logutils/README.md new file mode 100644 index 000000000..49490eaeb --- /dev/null +++ b/vendor/github.com/hashicorp/logutils/README.md @@ -0,0 +1,36 @@ +# logutils + +logutils is a Go package that augments the standard library "log" package +to make logging a bit more modern, without fragmenting the Go ecosystem +with new logging packages. + +## The simplest thing that could possibly work + +Presumably your application already uses the default `log` package. To switch, you'll want your code to look like the following: + +```go +package main + +import ( + "log" + "os" + + "github.com/hashicorp/logutils" +) + +func main() { + filter := &logutils.LevelFilter{ + Levels: []logutils.LogLevel{"DEBUG", "WARN", "ERROR"}, + MinLevel: logutils.LogLevel("WARN"), + Writer: os.Stderr, + } + log.SetOutput(filter) + + log.Print("[DEBUG] Debugging") // this will not print + log.Print("[WARN] Warning") // this will + log.Print("[ERROR] Erring") // and so will this + log.Print("Message I haven't updated") // and so will this +} +``` + +This logs to standard error exactly like go's standard logger. Any log messages you haven't converted to have a level will continue to print as before. diff --git a/vendor/github.com/hashicorp/logutils/go.mod b/vendor/github.com/hashicorp/logutils/go.mod new file mode 100644 index 000000000..ba38a4576 --- /dev/null +++ b/vendor/github.com/hashicorp/logutils/go.mod @@ -0,0 +1 @@ +module github.com/hashicorp/logutils diff --git a/vendor/github.com/hashicorp/logutils/level.go b/vendor/github.com/hashicorp/logutils/level.go new file mode 100644 index 000000000..6381bf162 --- /dev/null +++ b/vendor/github.com/hashicorp/logutils/level.go @@ -0,0 +1,81 @@ +// Package logutils augments the standard log package with levels. +package logutils + +import ( + "bytes" + "io" + "sync" +) + +type LogLevel string + +// LevelFilter is an io.Writer that can be used with a logger that +// will filter out log messages that aren't at least a certain level. +// +// Once the filter is in use somewhere, it is not safe to modify +// the structure. +type LevelFilter struct { + // Levels is the list of log levels, in increasing order of + // severity. Example might be: {"DEBUG", "WARN", "ERROR"}. + Levels []LogLevel + + // MinLevel is the minimum level allowed through + MinLevel LogLevel + + // The underlying io.Writer where log messages that pass the filter + // will be set. + Writer io.Writer + + badLevels map[LogLevel]struct{} + once sync.Once +} + +// Check will check a given line if it would be included in the level +// filter. +func (f *LevelFilter) Check(line []byte) bool { + f.once.Do(f.init) + + // Check for a log level + var level LogLevel + x := bytes.IndexByte(line, '[') + if x >= 0 { + y := bytes.IndexByte(line[x:], ']') + if y >= 0 { + level = LogLevel(line[x+1 : x+y]) + } + } + + _, ok := f.badLevels[level] + return !ok +} + +func (f *LevelFilter) Write(p []byte) (n int, err error) { + // Note in general that io.Writer can receive any byte sequence + // to write, but the "log" package always guarantees that we only + // get a single line. We use that as a slight optimization within + // this method, assuming we're dealing with a single, complete line + // of log data. + + if !f.Check(p) { + return len(p), nil + } + + return f.Writer.Write(p) +} + +// SetMinLevel is used to update the minimum log level +func (f *LevelFilter) SetMinLevel(min LogLevel) { + f.MinLevel = min + f.init() +} + +func (f *LevelFilter) init() { + badLevels := make(map[LogLevel]struct{}) + for _, level := range f.Levels { + if level == f.MinLevel { + break + } + badLevels[level] = struct{}{} + } + f.badLevels = badLevels +} diff --git a/vendor/github.com/pact-foundation/pact-go/LICENSE b/vendor/github.com/pact-foundation/pact-go/LICENSE new file mode 100644 index 000000000..1a6b7e066 --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Matt Fellows + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/pact-foundation/pact-go/client/message_service.go b/vendor/github.com/pact-foundation/pact-go/client/message_service.go new file mode 100644 index 000000000..9dd50f09b --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/client/message_service.go @@ -0,0 +1,24 @@ +package client + +import ( + "log" +) + +// MessageService is a wrapper for the Pact Message service. +type MessageService struct { + ServiceManager +} + +// NewService creates a new MessageService with default settings. +// Named Arguments allowed: +// --consumer +// --provider +// --pact-dir +func (v *MessageService) NewService(args []string) Service { + v.Args = args + + log.Printf("[DEBUG] starting message service with args: %v\n", v.Args) + v.Cmd = "pact-message" + + return v +} diff --git a/vendor/github.com/pact-foundation/pact-go/client/mock_service.go b/vendor/github.com/pact-foundation/pact-go/client/mock_service.go new file mode 100644 index 000000000..f8f3870ba --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/client/mock_service.go @@ -0,0 +1,21 @@ +package client + +// MockService is a wrapper for the Pact Mock Service. +type MockService struct { + ServiceManager +} + +// NewService creates a new MockService with default settings. +func (m *MockService) NewService(args []string) Service { + m.Args = []string{ + "service", + } + m.Args = append(m.Args, args...) + + m.Cmd = getMockServiceCommandPath() + return m +} + +func getMockServiceCommandPath() string { + return "pact-mock-service" +} diff --git a/vendor/github.com/pact-foundation/pact-go/client/publish_service.go b/vendor/github.com/pact-foundation/pact-go/client/publish_service.go new file mode 100644 index 000000000..18ba03ac4 --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/client/publish_service.go @@ -0,0 +1,39 @@ +package client + +import ( + "log" +) + +// PublishService is a wrapper for the Pact Provider Verifier Service. +type PublishService struct { + ServiceManager +} + +// NewService creates a new PublishService with default settings. +// Arguments allowed: +// +// --provider-base-url +// --pact-urls +// --provider-states-url +// --provider-states-setup-url +// --broker-username +// --broker-password +// --publish-verification-results +// --provider-app-version +// --custom-provider-headers +func (v *PublishService) NewService(args []string) Service { + log.Printf("[DEBUG] starting verification service with args: %v\n", args) + + v.Args = []string{ + "publish", + } + + v.Args = append(v.Args, args...) + v.Cmd = getPublisherCommandPath() + + return v +} + +func getPublisherCommandPath() string { + return "pact-broker" +} diff --git a/vendor/github.com/pact-foundation/pact-go/client/service.go b/vendor/github.com/pact-foundation/pact-go/client/service.go new file mode 100644 index 000000000..e08ce3b38 --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/client/service.go @@ -0,0 +1,26 @@ +/* +Package client is an internal package, implementing the raw interface to the +Pact CLI tools: The Pact Mock Service and Provider Verification "binaries." + +See https://github.com/pact-foundation/pact-provider-verifier and +https://github.com/bethesque/pact-mock_service for more on the Ruby "binaries". + +NOTE: The ultimate goal here is to replace the Ruby dependencies with a shared +library (Pact Reference - (https://github.com/pact-foundation/pact-reference/). +*/ +package client + +import ( + "os/exec" +) + +// Service is a process wrapper for 3rd party binaries. It will spawn an instance +// of the binary and manage the life-cycle and IO of the process. +type Service interface { + Setup() + Stop(pid int) (bool, error) + List() map[int]*exec.Cmd + Command() *exec.Cmd + Start() *exec.Cmd + NewService(args []string) Service +} diff --git a/vendor/github.com/pact-foundation/pact-go/client/service_manager.go b/vendor/github.com/pact-foundation/pact-go/client/service_manager.go new file mode 100644 index 000000000..a8ae74d11 --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/client/service_manager.go @@ -0,0 +1,178 @@ +package client + +import ( + "bufio" + "log" + "os" + "os/exec" + "sync" + "time" +) + +// ServiceManager is the default implementation of the Service interface. +type ServiceManager struct { + Cmd string + processMap processMap + Args []string + Env []string + commandCompleteChan chan *exec.Cmd + commandCreatedChan chan *exec.Cmd +} + +// Setup the Management services. +func (s *ServiceManager) Setup() { + log.Println("[DEBUG] setting up a service manager") + + s.commandCreatedChan = make(chan *exec.Cmd) + s.commandCompleteChan = make(chan *exec.Cmd) + s.processMap = processMap{processes: make(map[int]*exec.Cmd)} + + // Listen for service create/kill + go s.addServiceMonitor() + go s.removeServiceMonitor() +} + +// addServiceMonitor watches a channel to add services into operation. +func (s *ServiceManager) addServiceMonitor() { + log.Println("[DEBUG] starting service creation monitor") + for { + select { + case p := <-s.commandCreatedChan: + if p != nil && p.Process != nil { + s.processMap.Set(p.Process.Pid, p) + } + } + } +} + +// removeServiceMonitor watches a channel to remove services from operation. +func (s *ServiceManager) removeServiceMonitor() { + log.Println("[DEBUG] starting service removal monitor") + var p *exec.Cmd + for { + select { + case p = <-s.commandCompleteChan: + if p != nil && p.Process != nil { + p.Process.Signal(os.Interrupt) + s.processMap.Delete(p.Process.Pid) + } + } + } +} + +// Stop a Service and returns the exit status. +func (s *ServiceManager) Stop(pid int) (bool, error) { + log.Println("[DEBUG] stopping service with pid", pid) + cmd := s.processMap.Get(pid) + + // Remove service from registry + go func() { + s.commandCompleteChan <- cmd + }() + + // Wait for error, kill if it takes too long + var err error + done := make(chan error, 1) + go func() { + done <- cmd.Wait() + }() + + select { + case <-time.After(3 * time.Second): + if err = cmd.Process.Kill(); err != nil { + log.Println("[ERROR] timeout reached, killing pid", pid) + + return false, err + } + case err = <-done: + if err != nil { + log.Println("[ERROR] error waiting for process to complete", err) + return false, err + } + } + + return true, nil +} + +// List all Service PIDs. +func (s *ServiceManager) List() map[int]*exec.Cmd { + log.Println("[DEBUG] listing services") + return s.processMap.processes +} + +// Command creates an os command to be run +func (s *ServiceManager) Command() *exec.Cmd { + cmd := exec.Command(s.Cmd, s.Args...) + env := os.Environ() + env = append(env, s.Env...) + cmd.Env = env + + return cmd +} + +// Start a Service and log its output. +func (s *ServiceManager) Start() *exec.Cmd { + log.Println("[DEBUG] starting service") + cmd := exec.Command(s.Cmd, s.Args...) + env := os.Environ() + env = append(env, s.Env...) + cmd.Env = env + + cmdReader, err := cmd.StdoutPipe() + if err != nil { + log.Printf("[ERROR] unable to create output pipe for cmd: %s\n", err.Error()) + } + + cmdReaderErr, err := cmd.StderrPipe() + if err != nil { + log.Printf("[ERROR] unable to create error pipe for cmd: %s\n", err.Error()) + } + + scanner := bufio.NewScanner(cmdReader) + go func() { + for scanner.Scan() { + log.Printf("[INFO] %s\n", scanner.Text()) + } + }() + + scanner2 := bufio.NewScanner(cmdReaderErr) + go func() { + for scanner2.Scan() { + log.Printf("[ERROR] service: %s\n", scanner2.Text()) + } + }() + + err = cmd.Start() + if err != nil { + log.Println("[ERROR] service", err.Error()) + } + + // Add service to registry + s.commandCreatedChan <- cmd + + return cmd +} + +type processMap struct { + sync.RWMutex + processes map[int]*exec.Cmd +} + +func (pm *processMap) Get(k int) *exec.Cmd { + pm.RLock() + defer pm.RUnlock() + v, _ := pm.processes[k] + return v +} + +func (pm *processMap) Set(k int, v *exec.Cmd) { + pm.Lock() + defer pm.Unlock() + pm.processes[k] = v +} + +func (pm *processMap) Delete(k int) { + pm.Lock() + defer pm.Unlock() + delete(pm.processes, k) +} diff --git a/vendor/github.com/pact-foundation/pact-go/client/verification_service.go b/vendor/github.com/pact-foundation/pact-go/client/verification_service.go new file mode 100644 index 000000000..62fe143cb --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/client/verification_service.go @@ -0,0 +1,37 @@ +package client + +import ( + "log" + "os" +) + +// VerificationService is a wrapper for the Pact Provider Verifier Service. +type VerificationService struct { + ServiceManager +} + +// NewService creates a new VerificationService with default settings. +// Arguments allowed: +// +// --provider-base-url +// --pact-urls +// --provider-states-url +// --provider-states-setup-url +// --broker-username +// --broker-password +// --publish-verification-results +// --provider-app-version +// --custom-provider-headers +func (v *VerificationService) NewService(args []string) Service { + log.Printf("[DEBUG] starting verification service with args: %v\n", args) + + v.Args = args + v.Cmd = getVerifierCommandPath() + v.Env = append(os.Environ(), `PACT_INTERACTION_RERUN_COMMAND="To re-run this specific test, set the following environment variables and run your test again: PACT_DESCRIPTION=\"\" PACT_PROVIDER_STATE=\"\""`) + + return v +} + +func getVerifierCommandPath() string { + return "pact-provider-verifier" +} diff --git a/vendor/github.com/pact-foundation/pact-go/dsl/client.go b/vendor/github.com/pact-foundation/pact-go/dsl/client.go new file mode 100644 index 000000000..b0325d711 --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/dsl/client.go @@ -0,0 +1,423 @@ +package dsl + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net" + "net/url" + "regexp" + "strconv" + "strings" + "time" + + "github.com/pact-foundation/pact-go/client" + "github.com/pact-foundation/pact-go/types" +) + +// Client is the interface +type Client interface { + // StartServer starts a remote Pact Mock Server. + StartServer(args []string, port int) *types.MockServer + + // ListServers lists all known Mock Servers + ListServers() []*types.MockServer + + // StopServer stops a remote Pact Mock Server. + StopServer(server *types.MockServer) (*types.MockServer, error) + + // RemoveAllServers stops all remote Pact Mock Servers. + RemoveAllServers(server *types.MockServer) []*types.MockServer + + // VerifyProvider runs the verification process against a running Provider. + VerifyProvider(request types.VerifyRequest) (types.ProviderVerifierResponse, error) + + // UpdateMessagePact adds a pact message to a contract file + UpdateMessagePact(request types.PactMessageRequest) error + + // ReifyMessage takes a structured object, potentially containing nested Matchers + // and returns an object with just the example (generated) content + // The object may be a simple JSON primitive e.g. string or number or a complex object + ReifyMessage(request *types.PactReificationRequest) (res *types.ReificationResponse, err error) + + // PublishPacts publishes pact files to a Pact Broker + PublishPacts(request types.PublishRequest) error +} + +// PactClient is the main interface into starting/stopping +// the underlying Pact CLI subsystem +type PactClient struct { + pactMockSvcManager client.Service + verificationSvcManager client.Service + messageSvcManager client.Service + publishSvcManager client.Service + + // Track mock servers + Servers []MockService + + // Network Daemon is listening on + Network string + + // Address the Daemon is listening on + Address string + + // TimeoutDuration specifies how long to wait for Pact CLI to start + TimeoutDuration time.Duration +} + +// newClient creates a new Pact client manager with the provided services +func newClient(MockServiceManager client.Service, verificationServiceManager client.Service, messageServiceManager client.Service, publishServiceManager client.Service) *PactClient { + MockServiceManager.Setup() + verificationServiceManager.Setup() + messageServiceManager.Setup() + publishServiceManager.Setup() + + return &PactClient{ + pactMockSvcManager: MockServiceManager, + verificationSvcManager: verificationServiceManager, + messageSvcManager: messageServiceManager, + publishSvcManager: publishServiceManager, + TimeoutDuration: 10 * time.Second, + } +} + +// NewClient creates a new Pact client manager with defaults +func NewClient() *PactClient { + return newClient(&client.MockService{}, &client.VerificationService{}, &client.MessageService{}, &client.PublishService{}) +} + +// StartServer starts a remote Pact Mock Server. +func (p *PactClient) StartServer(args []string, port int) *types.MockServer { + log.Println("[DEBUG] client: starting a server with args:", args, "port:", port) + args = append(args, []string{"--port", strconv.Itoa(port)}...) + svc := p.pactMockSvcManager.NewService(args) + cmd := svc.Start() + + waitForPort(port, p.getNetworkInterface(), p.Address, p.TimeoutDuration, + fmt.Sprintf(`Timed out waiting for Mock Server to start on port %d - are you sure it's running?`, port)) + + return &types.MockServer{ + Pid: cmd.Process.Pid, + Port: port, + } +} + +// ListServers lists all known Mock Servers +func (p *PactClient) ListServers() []*types.MockServer { + log.Println("[DEBUG] client: starting a server") + + var servers []*types.MockServer + + for port, s := range p.pactMockSvcManager.List() { + servers = append(servers, &types.MockServer{ + Pid: s.Process.Pid, + Port: port, + }) + } + + return servers +} + +// StopServer stops a remote Pact Mock Server. +func (p *PactClient) StopServer(server *types.MockServer) (*types.MockServer, error) { + log.Println("[DEBUG] client: stop server") + + // TODO: Need to be able to get a non-zero exit code here! + _, server.Error = p.pactMockSvcManager.Stop(server.Pid) + return server, server.Error +} + +// RemoveAllServers stops all remote Pact Mock Servers. +func (p *PactClient) RemoveAllServers(server *types.MockServer) []*types.MockServer { + log.Println("[DEBUG] client: stop server") + + for _, s := range p.verificationSvcManager.List() { + if s != nil { + p.pactMockSvcManager.Stop(s.Process.Pid) + } + } + return nil +} + +// VerifyProvider runs the verification process against a running Provider. +// TODO: extract/refactor the stdout/error streaems from these functions +func (p *PactClient) VerifyProvider(request types.VerifyRequest) (types.ProviderVerifierResponse, error) { + log.Println("[DEBUG] client: verifying a provider") + var response types.ProviderVerifierResponse + + // Convert request into flags, and validate request + err := request.Validate() + if err != nil { + return response, err + } + + address := getAddress(request.ProviderBaseURL) + port := getPort(request.ProviderBaseURL) + + waitForPort(port, p.getNetworkInterface(), address, p.TimeoutDuration, + fmt.Sprintf(`Timed out waiting for Provider API to start on port %d - are you sure it's running?`, port)) + + // Run command, splitting out stderr and stdout. The command can fail for + // several reasons: + // 1. Command is unable to run at all. + // 2. Command runs, but fails for unknown reason. + // 3. Command runs, and returns exit status 1 because the tests fail. + // + // First, attempt to decode the response of the stdout. + // If that is successful, we are at case 3. Return stdout as message, no error. + // Else, return an error, include stderr and stdout in both the error and message. + svc := p.verificationSvcManager.NewService(request.Args) + cmd := svc.Command() + + stdOutPipe, err := cmd.StdoutPipe() + if err != nil { + return response, err + } + stdErrPipe, err := cmd.StderrPipe() + if err != nil { + return response, err + } + err = cmd.Start() + if err != nil { + return response, err + } + stdOut, err := ioutil.ReadAll(stdOutPipe) + if err != nil { + return response, err + } + stdErr, err := ioutil.ReadAll(stdErrPipe) + if err != nil { + return response, err + } + + err = cmd.Wait() + + // Split by lines, as the content is now JSONL + // See https://github.com/pact-foundation/pact-go/issues/88#issuecomment-404686337 + verifications := strings.Split(string(stdOut), "\n") + + var verification types.ProviderVerifierResponse + for _, v := range verifications { + v = strings.TrimSpace(v) + + // TODO: fix once https://github.com/pact-foundation/pact-provider-verifier/issues/26 + // is addressed + // logging to stdout breaks the JSON response + // https://github.com/pact-foundation/pact-ruby/commit/06fa61581512ba5570c315d089f2c0fc23c8cb11 + if v != "" && strings.Index(v, "INFO") != 0 { + dErr := json.Unmarshal([]byte(v), &verification) + + response.Examples = append(response.Examples, verification.Examples...) + + if dErr != nil { + err = dErr + } + } + } + + if err == nil { + return response, err + } + + return response, fmt.Errorf("error verifying provider: %s\n\nSTDERR:\n%s\n\nSTDOUT:\n%s", err, stdErr, stdOut) +} + +// UpdateMessagePact adds a pact message to a contract file +func (p *PactClient) UpdateMessagePact(request types.PactMessageRequest) error { + log.Println("[DEBUG] client: adding pact message...") + + // Convert request into flags, and validate request + err := request.Validate() + if err != nil { + return err + } + + svc := p.messageSvcManager.NewService(request.Args) + cmd := svc.Command() + + stdOutPipe, err := cmd.StdoutPipe() + if err != nil { + return err + } + stdErrPipe, err := cmd.StderrPipe() + if err != nil { + return err + } + err = cmd.Start() + if err != nil { + return err + } + stdOut, err := ioutil.ReadAll(stdOutPipe) + if err != nil { + return err + } + stdErr, err := ioutil.ReadAll(stdErrPipe) + if err != nil { + return err + } + + err = cmd.Wait() + + if err == nil { + return nil + } + + return fmt.Errorf("error creating message: %s\n\nSTDERR:\n%s\n\nSTDOUT:\n%s", err, stdErr, stdOut) +} + +// PublishPacts publishes a set of pacts to a pact broker +func (p *PactClient) PublishPacts(request types.PublishRequest) error { + svc := p.publishSvcManager.NewService(request.Args) + log.Println("[DEBUG] about to publish pacts") + cmd := svc.Start() + + log.Println("[DEBUG] waiting for response") + err := cmd.Wait() + + log.Println("[DEBUG] response from publish", err) + + return err +} + +// ReifyMessage takes a structured object, potentially containing nested Matchers +// and returns an object with just the example (generated) content +// The object may be a simple JSON primitive e.g. string or number or a complex object +func (p *PactClient) ReifyMessage(request *types.PactReificationRequest) (res *types.ReificationResponse, err error) { + log.Println("[DEBUG] client: adding pact message...") + + var responseObject interface{} + res = &types.ReificationResponse{ + Response: responseObject, + } + + // Convert request into flags, and validate request + err = request.Validate() + if err != nil { + return + } + + svc := p.messageSvcManager.NewService(request.Args) + cmd := svc.Command() + + stdOutPipe, err := cmd.StdoutPipe() + if err != nil { + return + } + stdErrPipe, err := cmd.StderrPipe() + if err != nil { + return + } + err = cmd.Start() + if err != nil { + return + } + stdOut, err := ioutil.ReadAll(stdOutPipe) + if err != nil { + return + } + stdErr, err := ioutil.ReadAll(stdErrPipe) + if err != nil { + return + } + + err = cmd.Wait() + + res.ResponseRaw = stdOut + decoder := json.NewDecoder(bytes.NewReader(stdOut)) + + dErr := decoder.Decode(&res.Response) + if dErr == nil { + return + } + + if err == nil { + err = dErr + } + + if err == nil { + return + } + + err = fmt.Errorf("error creating message: %s\n\nSTDERR:\n%s\n\nSTDOUT:\n%s", err, stdErr, stdOut) + + return +} + +// Get a port given a URL +func getPort(rawURL string) int { + parsedURL, err := url.Parse(rawURL) + if err == nil { + splitHost := strings.Split(parsedURL.Host, ":") + if len(splitHost) == 2 { + port, err := strconv.Atoi(splitHost[1]) + if err == nil { + return port + } + } + if parsedURL.Scheme == "https" { + return 443 + } + return 80 + } + + return -1 +} + +// Get the address given a URL +func getAddress(rawURL string) string { + parsedURL, err := url.Parse(rawURL) + if err != nil { + return "" + } + + splitHost := strings.Split(parsedURL.Host, ":") + return splitHost[0] +} + +// Use this to wait for a port to be running prior +// to running tests. +var waitForPort = func(port int, network string, address string, timeoutDuration time.Duration, message string) error { + log.Println("[DEBUG] waiting for port", port, "to become available") + timeout := time.After(timeoutDuration) + + for { + select { + case <-timeout: + log.Printf("[ERROR] Expected server to start < %s. %s", timeoutDuration, message) + return fmt.Errorf("Expected server to start < %s. %s", timeoutDuration, message) + case <-time.After(50 * time.Millisecond): + _, err := net.Dial(network, fmt.Sprintf("%s:%d", address, port)) + if err == nil { + return nil + } + } + } +} + +// sanitiseRubyResponse removes Ruby-isms from the response content +// making the output much more human readable +func sanitiseRubyResponse(response string) string { + log.Println("[TRACE] response from Ruby process pre-sanitisation:", response) + + r := regexp.MustCompile("(?m)^\\s*#.*$") + s := r.ReplaceAllString(response, "") + + r = regexp.MustCompile("(?m).*bundle exec rake pact:verify.*$") + s = r.ReplaceAllString(s, "") + + r = regexp.MustCompile("\\n+") + s = r.ReplaceAllString(s, "\n") + + return s +} + +// getNetworkInterface returns a default interface to communicate to the Daemon +// if none specified +func (p *PactClient) getNetworkInterface() string { + if p.Network == "" { + return "tcp" + } + return p.Network +} diff --git a/vendor/github.com/pact-foundation/pact-go/dsl/interaction.go b/vendor/github.com/pact-foundation/pact-go/dsl/interaction.go new file mode 100644 index 000000000..a94c933d2 --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/dsl/interaction.go @@ -0,0 +1,84 @@ +package dsl + +import ( + "encoding/json" + "log" +) + +// Interaction is the main implementation of the Pact interface. +type Interaction struct { + // Request + Request Request `json:"request"` + + // Response + Response Response `json:"response"` + + // Description to be written into the Pact file + Description string `json:"description"` + + // Provider state to be written into the Pact file + State string `json:"providerState,omitempty"` +} + +// Given specifies a provider state. Optional. +func (i *Interaction) Given(state string) *Interaction { + i.State = state + + return i +} + +// UponReceiving specifies the name of the test case. This becomes the name of +// the consumer/provider pair in the Pact file. Mandatory. +func (i *Interaction) UponReceiving(description string) *Interaction { + i.Description = description + + return i +} + +// WithRequest specifies the details of the HTTP request that will be used to +// confirm that the Provider provides an API listening on the given interface. +// Mandatory. +func (i *Interaction) WithRequest(request Request) *Interaction { + i.Request = request + + // Check if someone tried to add an object as a string representation + // as per original allowed implementation, e.g. + // { "foo": "bar", "baz": like("bat") } + if isJSONFormattedObject(request.Body) { + log.Println("[WARN] request body appears to be a JSON formatted object, " + + "no structural matching will occur. Support for structured strings has been" + + "deprecated as of 0.13.0") + } + + return i +} + +// WillRespondWith specifies the details of the HTTP response that will be used to +// confirm that the Provider must satisfy. Mandatory. +func (i *Interaction) WillRespondWith(response Response) *Interaction { + i.Response = response + + return i +} + +// Checks to see if someone has tried to submit a JSON string +// for an object, which is no longer supported +func isJSONFormattedObject(stringOrObject interface{}) bool { + switch content := stringOrObject.(type) { + case []byte: + case string: + var obj interface{} + err := json.Unmarshal([]byte(content), &obj) + + if err != nil { + return false + } + + // Check if a map type + if _, ok := obj.(map[string]interface{}); ok { + return true + } + } + + return false +} diff --git a/vendor/github.com/pact-foundation/pact-go/dsl/matcher.go b/vendor/github.com/pact-foundation/pact-go/dsl/matcher.go new file mode 100644 index 000000000..2790b61bb --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/dsl/matcher.go @@ -0,0 +1,399 @@ +package dsl + +import ( + "encoding/json" + "fmt" + "log" + "reflect" + "regexp" + "strings" + "time" +) + +// Term Matcher regexes +const ( + hexadecimal = `[0-9a-fA-F]+` + ipAddress = `(\d{1,3}\.)+\d{1,3}` + ipv6Address = `(\A([0-9a-f]{1,4}:){1,1}(:[0-9a-f]{1,4}){1,6}\Z)|(\A([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,5}\Z)|(\A([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,4}\Z)|(\A([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,3}\Z)|(\A([0-9a-f]{1,4}:){1,5}(:[0-9a-f]{1,4}){1,2}\Z)|(\A([0-9a-f]{1,4}:){1,6}(:[0-9a-f]{1,4}){1,1}\Z)|(\A(([0-9a-f]{1,4}:){1,7}|:):\Z)|(\A:(:[0-9a-f]{1,4}){1,7}\Z)|(\A((([0-9a-f]{1,4}:){6})(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3})\Z)|(\A(([0-9a-f]{1,4}:){5}[0-9a-f]{1,4}:(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3})\Z)|(\A([0-9a-f]{1,4}:){5}:[0-9a-f]{1,4}:(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\Z)|(\A([0-9a-f]{1,4}:){1,1}(:[0-9a-f]{1,4}){1,4}:(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\Z)|(\A([0-9a-f]{1,4}:){1,2}(:[0-9a-f]{1,4}){1,3}:(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\Z)|(\A([0-9a-f]{1,4}:){1,3}(:[0-9a-f]{1,4}){1,2}:(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\Z)|(\A([0-9a-f]{1,4}:){1,4}(:[0-9a-f]{1,4}){1,1}:(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\Z)|(\A(([0-9a-f]{1,4}:){1,5}|:):(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\Z)|(\A:(:[0-9a-f]{1,4}){1,5}:(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\Z)` + uuid = `[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}` + timestamp = `^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([T\s]((([01]\d|2[0-3])((:?)[0-5]\d)?|24\:?00)([\.,]\d+(?!:))?)?(\17[0-5]\d([\.,]\d+)?)?([zZ]|([\+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$` + date = `^([\+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-2])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))?)` + timeRegex = `^(T\d\d:\d\d(:\d\d)?(\.\d+)?(([+-]\d\d:\d\d)|Z)?)?$` +) + +var timeExample = time.Date(2000, 2, 1, 12, 30, 0, 0, time.UTC) + +type eachLike struct { + Contents interface{} `json:"contents"` + Min int `json:"min"` +} + +func (m eachLike) GetValue() interface{} { + return m.Contents +} + +func (m eachLike) isMatcher() { +} + +func (m eachLike) MarshalJSON() ([]byte, error) { + type marshaler eachLike + + return json.Marshal(struct { + Type string `json:"json_class"` + marshaler + }{"Pact::ArrayLike", marshaler(m)}) +} + +type like struct { + Contents interface{} `json:"contents"` +} + +func (m like) GetValue() interface{} { + return m.Contents +} + +func (m like) isMatcher() { +} + +func (m like) MarshalJSON() ([]byte, error) { + type marshaler like + + return json.Marshal(struct { + Type string `json:"json_class"` + marshaler + }{"Pact::SomethingLike", marshaler(m)}) +} + +type term struct { + Data termData `json:"data"` +} + +func (m term) GetValue() interface{} { + return m.Data.Generate +} + +func (m term) isMatcher() { +} + +func (m term) MarshalJSON() ([]byte, error) { + type marshaler term + + return json.Marshal(struct { + Type string `json:"json_class"` + marshaler + }{"Pact::Term", marshaler(m)}) +} + +type termData struct { + Generate interface{} `json:"generate"` + Matcher termMatcher `json:"matcher"` +} + +type termMatcher struct { + Type string `json:"json_class"` + O int `json:"o"` + Regex interface{} `json:"s"` +} + +// EachLike specifies that a given element in a JSON body can be repeated +// "minRequired" times. Number needs to be 1 or greater +func EachLike(content interface{}, minRequired int) Matcher { + return eachLike{ + Contents: content, + Min: minRequired, + } +} + +// Like specifies that the given content type should be matched based +// on type (int, string etc.) instead of a verbatim match. +func Like(content interface{}) Matcher { + return like{ + Contents: content, + } +} + +// Term specifies that the matching should generate a value +// and also match using a regular expression. +func Term(generate string, matcher string) Matcher { + return term{ + Data: termData{ + Generate: generate, + Matcher: termMatcher{ + Type: "Regexp", + O: 0, + Regex: matcher, + }, + }, + } +} + +// HexValue defines a matcher that accepts hexidecimal values. +func HexValue() Matcher { + return Regex("3F", hexadecimal) +} + +// Identifier defines a matcher that accepts integer values. +func Identifier() Matcher { + return Like(42) +} + +// Integer defines a matcher that accepts ints. Identical to Identifier. +var Integer = Identifier + +// IPAddress defines a matcher that accepts valid IPv4 addresses. +func IPAddress() Matcher { + return Regex("127.0.0.1", ipAddress) +} + +// IPv4Address matches valid IPv4 addresses. +var IPv4Address = IPAddress + +// IPv6Address defines a matcher that accepts IP addresses. +func IPv6Address() Matcher { + return Regex("::ffff:192.0.2.128", ipAddress) +} + +// Decimal defines a matcher that accepts any decimal value. +func Decimal() Matcher { + return Like(42.0) +} + +// Timestamp matches a pattern corresponding to the ISO_DATETIME_FORMAT, which +// is "yyyy-MM-dd'T'HH:mm:ss". The current date and time is used as the eaxmple. +func Timestamp() Matcher { + return Regex(timeExample.Format(time.RFC3339), timestamp) +} + +// Date matches a pattern corresponding to the ISO_DATE_FORMAT, which +// is "yyyy-MM-dd". The current date is used as the eaxmple. +func Date() Matcher { + return Regex(timeExample.Format("2006-01-02"), date) +} + +// Time matches a pattern corresponding to the ISO_DATE_FORMAT, which +// is "'T'HH:mm:ss". The current tem is used as the eaxmple. +func Time() Matcher { + return Regex(timeExample.Format("T15:04:05"), timeRegex) +} + +// UUID defines a matcher that accepts UUIDs. Produces a v4 UUID as the example. +func UUID() Matcher { + return Regex("fc763eba-0905-41c5-a27f-3934ab26786c", uuid) +} + +// Regex is a more appropriately named alias for the "Term" matcher +var Regex = Term + +// Matcher allows various implementations such String or StructMatcher +// to be provided in when matching with the DSL +// We use the strategy outlined at http://www.jerf.org/iri/post/2917 +// to create a "sum" or "union" type. +type Matcher interface { + // isMatcher is how we tell the compiler that strings + // and other types are the same / allowed + isMatcher() + + // GetValue returns the raw generated value for the matcher + // without any of the matching detail context + GetValue() interface{} +} + +// S is the string primitive wrapper (alias) for the Matcher type, +// it allows plain strings to be matched +// To keep backwards compatible with previous versions +// we aren't using an alias here +type S string + +func (s S) isMatcher() {} + +// GetValue returns the raw generated value for the matcher +// without any of the matching detail context +func (s S) GetValue() interface{} { + return s +} + +// String is the longer named form of the string primitive wrapper, +// it allows plain strings to be matched +type String string + +func (s String) isMatcher() {} + +// GetValue returns the raw generated value for the matcher +// without any of the matching detail context +func (s String) GetValue() interface{} { + return s +} + +// StructMatcher matches a complex object structure, which may itself +// contain nested Matchers +type StructMatcher map[string]interface{} + +func (m StructMatcher) isMatcher() {} + +// GetValue returns the raw generated value for the matcher +// without any of the matching detail context +func (m StructMatcher) GetValue() interface{} { + return nil +} + +// MapMatcher allows a map[string]string-like object +// to also contain complex matchers +type MapMatcher map[string]Matcher + +// UnmarshalJSON is a custom JSON parser for MapMatcher +// It treats the matchers as strings +func (m *MapMatcher) UnmarshalJSON(bytes []byte) (err error) { + sk := make(map[string]string) + err = json.Unmarshal(bytes, &sk) + if err != nil { + return + } + + *m = make(map[string]Matcher) + for k, v := range sk { + (*m)[k] = String(v) + } + + return +} + +// Takes an object and converts it to a JSON representation +func objectToString(obj interface{}) string { + switch content := obj.(type) { + case string: + return content + default: + jsonString, err := json.Marshal(obj) + if err != nil { + log.Println("[DEBUG] objectToString: error unmarshaling object into string:", err.Error()) + return "" + } + return string(jsonString) + } +} + +// Match recursively traverses the provided type and outputs a +// matcher string for it that is compatible with the Pact dsl. +// By default, it requires slices to have a minimum of 1 element. +// For concrete types, it uses `dsl.Like` to assert that types match. +// Optionally, you may override these defaults by supplying custom +// pact tags on your structs. +// +// Supported Tag Formats +// Minimum Slice Size: `pact:"min=2"` +// String RegEx: `pact:"example=2000-01-01,regex=^\\d{4}-\\d{2}-\\d{2}$"` +func Match(src interface{}) Matcher { + return match(reflect.TypeOf(src), getDefaults()) +} + +// match recursively traverses the provided type and outputs a +// matcher string for it that is compatible with the Pact dsl. +func match(srcType reflect.Type, params params) Matcher { + switch kind := srcType.Kind(); kind { + case reflect.Ptr: + return match(srcType.Elem(), params) + case reflect.Slice, reflect.Array: + return EachLike(match(srcType.Elem(), getDefaults()), params.slice.min) + case reflect.Struct: + result := StructMatcher{} + + for i := 0; i < srcType.NumField(); i++ { + field := srcType.Field(i) + result[field.Tag.Get("json")] = match(field.Type, pluckParams(field.Type, field.Tag.Get("pact"))) + } + return result + case reflect.String: + if params.str.regEx != "" { + return Term(params.str.example, params.str.regEx) + } + if params.str.example != "" { + return Like(params.str.example) + } + + return Like("string") + case reflect.Bool: + return Like(true) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + return Like(1) + case reflect.Float32, reflect.Float64: + return Like(1.1) + default: + panic(fmt.Sprintf("match: unhandled type: %v", srcType)) + } +} + +// params are plucked from 'pact' struct tags as match() traverses +// struct fields. They are passed back into match() along with their +// associated type to serve as parameters for the dsl functions. +type params struct { + slice sliceParams + str stringParams +} + +type sliceParams struct { + min int +} + +type stringParams struct { + example string + regEx string +} + +// getDefaults returns the default params +func getDefaults() params { + return params{ + slice: sliceParams{ + min: 1, + }, + } +} + +// pluckParams converts a 'pact' tag into a pactParams struct +// Supported Tag Formats +// Minimum Slice Size: `pact:"min=2"` +// String RegEx: `pact:"example=2000-01-01,regex=^\\d{4}-\\d{2}-\\d{2}$"` +func pluckParams(srcType reflect.Type, pactTag string) params { + params := getDefaults() + if pactTag == "" { + return params + } + + switch kind := srcType.Kind(); kind { + case reflect.Slice: + if _, err := fmt.Sscanf(pactTag, "min=%d", ¶ms.slice.min); err != nil { + triggerInvalidPactTagPanic(pactTag, err) + } + case reflect.String: + fullRegex, _ := regexp.Compile(`regex=(.*)$`) + exampleRegex, _ := regexp.Compile(`^example=(.*)`) + + if fullRegex.Match([]byte(pactTag)) { + components := strings.Split(pactTag, ",regex=") + + if len(components[1]) == 0 { + triggerInvalidPactTagPanic(pactTag, fmt.Errorf("invalid format: regex must not be empty")) + } + + if _, err := fmt.Sscanf(components[0], "example=%s", ¶ms.str.example); err != nil { + triggerInvalidPactTagPanic(pactTag, err) + } + params.str.regEx = strings.Replace(components[1], `\`, `\\`, -1) + + } else if exampleRegex.Match([]byte(pactTag)) { + components := strings.Split(pactTag, "example=") + + if len(components) != 2 || strings.TrimSpace(components[1]) == "" { + triggerInvalidPactTagPanic(pactTag, fmt.Errorf("invalid format: example must not be empty")) + } + + params.str.example = components[1] + } + } + + return params +} + +func triggerInvalidPactTagPanic(tag string, err error) { + panic(fmt.Sprintf("match: encountered invalid pact tag %q . . . parsing failed with error: %v", tag, err)) +} diff --git a/vendor/github.com/pact-foundation/pact-go/dsl/message.go b/vendor/github.com/pact-foundation/pact-go/dsl/message.go new file mode 100644 index 000000000..ba7cb349f --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/dsl/message.go @@ -0,0 +1,97 @@ +package dsl + +import ( + "fmt" + "reflect" +) + +// StateHandler is a provider function that sets up a given state before +// the provider interaction is validated +type StateHandler func(State) error + +// StateHandlers is a list of StateHandler's +type StateHandlers map[string]StateHandler + +// MessageHandler is a provider function that generates a +// message for a Consumer given a Message context (state, description etc.) +type MessageHandler func(Message) (interface{}, error) + +// MessageHandlers is a list of handlers ordered by description +type MessageHandlers map[string]MessageHandler + +// MessageConsumer receives a message and must be able to parse +// the content +type MessageConsumer func(Message) error + +// Message is a representation of a single, unidirectional message +// e.g. MQ, pub/sub, Websocket, Lambda +// Message is the main implementation of the Pact Message interface. +type Message struct { + // Message Body + Content interface{} `json:"contents,omitempty"` + + // Message Body as a Raw JSON string + ContentRaw interface{} `json:"-"` + + // Provider state to be written into the Pact file + States []State `json:"providerStates,omitempty"` + + // Message metadata + Metadata MapMatcher `json:"metadata,omitempty"` + + // Description to be written into the Pact file + Description string `json:"description"` + + // Type to Marshall content into when sending back to the consumer + // Defaults to interface{} + Type interface{} + + Args []string `json:"-"` +} + +// State specifies how the system should be configured when +// verified. e.g. "user A exists" +type State struct { + Name string `json:"name"` + Params map[string]interface{} `json:"params,omitempty"` +} + +// Given specifies a provider state. Optional. +func (p *Message) Given(state string) *Message { + p.States = []State{State{Name: state}} + + return p +} + +// ExpectsToReceive specifies the content it is expecting to be +// given from the Provider. The function must be able to handle this +// message for the interaction to succeed. +func (p *Message) ExpectsToReceive(description string) *Message { + p.Description = description + return p +} + +// WithMetadata specifies message-implementation specific metadata +// to go with the content +func (p *Message) WithMetadata(metadata MapMatcher) *Message { + p.Metadata = metadata + return p +} + +// WithContent specifies the details of the HTTP request that will be used to +// confirm that the Provider provides an API listening on the given interface. +// Mandatory. +func (p *Message) WithContent(content interface{}) *Message { + p.Content = content + + return p +} + +// AsType specifies that the content sent through to the +// consumer handler should be sent as the given type +func (p *Message) AsType(t interface{}) *Message { + fmt.Println("[DEBUG] setting Message decoding to type:", reflect.TypeOf(t)) + p.Type = t + + return p +} diff --git a/vendor/github.com/pact-foundation/pact-go/dsl/mock_client.go b/vendor/github.com/pact-foundation/pact-go/dsl/mock_client.go new file mode 100644 index 000000000..c83734440 --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/dsl/mock_client.go @@ -0,0 +1,79 @@ +package dsl + +import ( + "github.com/pact-foundation/pact-go/types" +) + +// TODO: Migrate tests from createMockClient to this package +// where possible + +// Mock Client for testing the DSL package +type mockClient struct { + VerifyProviderResponse types.ProviderVerifierResponse + VerifyProviderError error + Servers []*types.MockServer + StopServerResponse *types.MockServer + StopServerError error + RemoveAllServersResponse []*types.MockServer + MockServer *types.MockServer + ReifyMessageResponse *types.ReificationResponse + ReifyMessageError error + UpdateMessagePactError error + PublishPactsError error +} + +func newMockClient() *mockClient { + return &mockClient{ + MockServer: &types.MockServer{ + Pid: 0, + Port: 0, + }, + ReifyMessageResponse: &types.ReificationResponse{ + Response: map[string]string{ + "foo": "bar", + }, + }, + } +} + +// StartServer starts a remote Pact Mock Server. +func (p *mockClient) StartServer(args []string, port int) *types.MockServer { + return p.MockServer +} + +// ListServers lists all known Mock Servers +func (p *mockClient) ListServers() []*types.MockServer { + return p.Servers +} + +// StopServer stops a remote Pact Mock Server. +func (p *mockClient) StopServer(server *types.MockServer) (*types.MockServer, error) { + return p.StopServerResponse, p.StopServerError +} + +// RemoveAllServers stops all remote Pact Mock Servers. +func (p *mockClient) RemoveAllServers(server *types.MockServer) []*types.MockServer { + return p.RemoveAllServersResponse +} + +// VerifyProvider runs the verification process against a running Provider. +func (p *mockClient) VerifyProvider(request types.VerifyRequest) (types.ProviderVerifierResponse, error) { + return p.VerifyProviderResponse, p.VerifyProviderError +} + +// UpdateMessagePact adds a pact message to a contract file +func (p *mockClient) UpdateMessagePact(request types.PactMessageRequest) error { + return p.UpdateMessagePactError +} + +// ReifyMessage takes a structured object, potentially containing nested Matchers +// and returns an object with just the example (generated) content +// The object may be a simple JSON primitive e.g. string or number or a complex object +func (p *mockClient) ReifyMessage(request *types.PactReificationRequest) (res *types.ReificationResponse, err error) { + return p.ReifyMessageResponse, p.ReifyMessageError +} + +// PublishPacts publishes pacts to a broker +func (p *mockClient) PublishPacts(request types.PublishRequest) error { + return p.PublishPactsError +} diff --git a/vendor/github.com/pact-foundation/pact-go/dsl/mock_service.go b/vendor/github.com/pact-foundation/pact-go/dsl/mock_service.go new file mode 100644 index 000000000..76eb8ab80 --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/dsl/mock_service.go @@ -0,0 +1,114 @@ +package dsl + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "log" + "net/http" +) + +// MockService is the HTTP interface to setup the Pact Mock Service +// See https://github.com/bethesque/pact-mock_service and +// https://gist.github.com/bethesque/9d81f21d6f77650811f4. +type MockService struct { + // BaseURL is the base host for the Pact Mock Service. + BaseURL string + + // Consumer name. + Consumer string + + // Provider name. + Provider string + + // PactFileWriteMode specifies how to write to the Pact file, for the life + // of a Mock Service. + // "overwrite" will always truncate and replace the pact after each run + // "update" will append to the pact file, which is useful if your tests + // are split over multiple files and instantiations of a Mock Server + // See https://github.com/pact-foundation/pact-ruby/blob/master/documentation/configuration.md#pactfile_write_mode + PactFileWriteMode string +} + +// call sends a message to the Pact service +func (m *MockService) call(method string, url string, content interface{}) error { + body, err := json.Marshal(content) + if err != nil { + fmt.Println(err) + return err + } + + client := &http.Client{} + var req *http.Request + if method == "POST" { + req, err = http.NewRequest(method, url, bytes.NewReader(body)) + } else { + req, err = http.NewRequest(method, url, nil) + } + if err != nil { + return err + } + + req.Header.Set("X-Pact-Mock-Service", "true") + req.Header.Set("Content-Type", "application/json") + + res, err := client.Do(req) + if err != nil { + return err + } + + responseBody, err := ioutil.ReadAll(res.Body) + res.Body.Close() + if res.StatusCode < 200 || res.StatusCode >= 300 { + return errors.New(string(responseBody)) + } + return err +} + +// DeleteInteractions removes any previous Mock Service Interactions. +func (m *MockService) DeleteInteractions() error { + log.Println("[DEBUG] mock service delete interactions") + url := fmt.Sprintf("%s/interactions", m.BaseURL) + return m.call("DELETE", url, nil) +} + +// AddInteraction adds a new Pact Mock Service interaction. +func (m *MockService) AddInteraction(interaction *Interaction) error { + log.Println("[DEBUG] mock service add interaction") + url := fmt.Sprintf("%s/interactions", m.BaseURL) + return m.call("POST", url, interaction) +} + +// Verify confirms that all interactions were called. +func (m *MockService) Verify() error { + log.Println("[DEBUG] mock service verify") + url := fmt.Sprintf("%s/interactions/verification", m.BaseURL) + return m.call("GET", url, nil) +} + +// WritePact writes the pact file to disk. +func (m *MockService) WritePact() error { + log.Println("[DEBUG] mock service write pact") + + if m.Consumer == "" || m.Provider == "" { + return errors.New("Consumer and Provider name need to be provided") + } + if m.PactFileWriteMode == "" { + m.PactFileWriteMode = "overwrite" + } + + pact := map[string]interface{}{ + "consumer": map[string]string{ + "name": m.Consumer, + }, + "provider": map[string]string{ + "name": m.Provider, + }, + "pactFileWriteMode": m.PactFileWriteMode, + } + + url := fmt.Sprintf("%s/pact", m.BaseURL) + return m.call("POST", url, pact) +} diff --git a/vendor/github.com/pact-foundation/pact-go/dsl/pact.go b/vendor/github.com/pact-foundation/pact-go/dsl/pact.go new file mode 100644 index 000000000..5a98da429 --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/dsl/pact.go @@ -0,0 +1,706 @@ +/* +Package dsl contains the main Pact DSL used in the Consumer +collaboration test cases, and Provider contract test verification. +*/ +package dsl + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "log" + "net" + "net/http" + "net/url" + "os" + "path/filepath" + "reflect" + "testing" + "time" + + "github.com/hashicorp/logutils" + "github.com/pact-foundation/pact-go/install" + "github.com/pact-foundation/pact-go/proxy" + "github.com/pact-foundation/pact-go/types" + "github.com/pact-foundation/pact-go/utils" +) + +// Pact is the container structure to run the Consumer Pact test cases. +type Pact struct { + // Current server for the consumer. + Server *types.MockServer + + // Pact RPC Client. + pactClient Client + + // Consumer is the name of the Consumer/Client. + Consumer string + + // Provider is the name of the Providing service. + Provider string + + // Interactions contains all of the Mock Service Interactions to be setup. + Interactions []*Interaction + + // MessageInteractions contains all of the Message based interactions to be setup. + MessageInteractions []*Message + + // Log levels. + LogLevel string + + // Used to detect if logging has been configured. + logFilter *logutils.LevelFilter + + // Location of Pact external service invocation output logging. + // Defaults to `/logs`. + LogDir string + + // Pact files will be saved in this folder. + // Defaults to `/pacts`. + PactDir string + + // PactFileWriteMode specifies how to write to the Pact file, for the life + // of a Mock Service. + // "overwrite" will always truncate and replace the pact after each run + // "merge" will append to the pact file, which is useful if your tests + // are split over multiple files and instantiations of a Mock Server + // See https://github.com/pact-foundation/pact-ruby/blob/master/documentation/configuration.md#pactfile_write_mode + PactFileWriteMode string + + // Specify which version of the Pact Specification should be used (1 or 2). + // Defaults to 2. + SpecificationVersion int + + // Host is the address of the Mock and Verification Service runs on + // Examples include 'localhost', '127.0.0.1', '[::1]' + // Defaults to 'localhost' + Host string + + // Network is the network of the Mock and Verification Service + // Examples include 'tcp', 'tcp4', 'tcp6' + // Defaults to 'tcp' + Network string + + // Ports MockServer can be deployed to, can be CSV or Range with a dash + // Example "1234", "12324,5667", "1234-5667" + AllowedMockServerPorts string + + // DisableToolValidityCheck prevents CLI version checking - use this carefully! + // The ideal situation is to check the tool installation with before running + // the tests, which should speed up large test suites significantly + DisableToolValidityCheck bool + + // ClientTimeout specifies how long to wait for Pact CLI to start + // Can be increased to reduce likelihood of intermittent failure + // Defaults to 10s + ClientTimeout time.Duration + + // Check if CLI tools are up to date + toolValidityCheck bool +} + +// AddMessage creates a new asynchronous consumer expectation +func (p *Pact) AddMessage() *Message { + log.Println("[DEBUG] pact add message") + + m := &Message{} + p.MessageInteractions = append(p.MessageInteractions, m) + return m +} + +// AddInteraction creates a new Pact interaction, initialising all +// required things. Will automatically start a Mock Service if none running. +func (p *Pact) AddInteraction() *Interaction { + p.Setup(true) + log.Println("[DEBUG] pact add interaction") + i := &Interaction{} + p.Interactions = append(p.Interactions, i) + return i +} + +// Setup starts the Pact Mock Server. This is usually called before each test +// suite begins. AddInteraction() will automatically call this if no Mock Server +// has been started. +func (p *Pact) Setup(startMockServer bool) *Pact { + p.setupLogging() + log.Println("[DEBUG] pact setup") + dir, _ := os.Getwd() + + if p.Network == "" { + p.Network = "tcp" + } + + if !p.toolValidityCheck && !(p.DisableToolValidityCheck || os.Getenv("PACT_DISABLE_TOOL_VALIDITY_CHECK") != "") { + checkCliCompatibility() + p.toolValidityCheck = true + } + + if p.Host == "" { + p.Host = "localhost" + } + + if p.LogDir == "" { + p.LogDir = fmt.Sprintf(filepath.Join(dir, "logs")) + } + + if p.PactDir == "" { + p.PactDir = fmt.Sprintf(filepath.Join(dir, "pacts")) + } + + if p.SpecificationVersion == 0 { + p.SpecificationVersion = 2 + } + + if p.ClientTimeout == 0 { + p.ClientTimeout = 10 * time.Second + } + + if p.pactClient == nil { + c := NewClient() + c.TimeoutDuration = p.ClientTimeout + p.pactClient = c + } + + if p.PactFileWriteMode == "" { + p.PactFileWriteMode = "overwrite" + } + + // Need to predefine due to scoping + var port int + var perr error + if p.AllowedMockServerPorts != "" { + port, perr = utils.FindPortInRange(p.AllowedMockServerPorts) + } else { + port, perr = utils.GetFreePort() + } + if perr != nil { + log.Println("[ERROR] unable to find free port, mockserver will fail to start") + } + + if p.Server == nil && startMockServer { + log.Println("[DEBUG] starting mock service on port:", port) + args := []string{ + "--pact-specification-version", + fmt.Sprintf("%d", p.SpecificationVersion), + "--pact-dir", + filepath.FromSlash(p.PactDir), + "--log", + filepath.FromSlash(p.LogDir + "/" + "pact.log"), + "--consumer", + p.Consumer, + "--provider", + p.Provider, + "--pact-file-write-mode", + p.PactFileWriteMode, + } + + p.Server = p.pactClient.StartServer(args, port) + } + + return p +} + +// Configure logging +func (p *Pact) setupLogging() { + if p.logFilter == nil { + if p.LogLevel == "" { + p.LogLevel = "INFO" + } + p.logFilter = &logutils.LevelFilter{ + Levels: []logutils.LogLevel{"DEBUG", "INFO", "WARN", "ERROR"}, + MinLevel: logutils.LogLevel(p.LogLevel), + Writer: os.Stderr, + } + log.SetOutput(p.logFilter) + } + log.Println("[DEBUG] pact setup logging") +} + +// Teardown stops the Pact Mock Server. This usually is called on completion +// of each test suite. +func (p *Pact) Teardown() *Pact { + log.Println("[DEBUG] teardown") + if p.Server != nil { + server, err := p.pactClient.StopServer(p.Server) + + if err != nil { + log.Println("error:", err) + } + p.Server = server + } + return p +} + +// Verify runs the current test case against a Mock Service. +// Will cleanup interactions between tests within a suite. +func (p *Pact) Verify(integrationTest func() error) error { + p.Setup(true) + log.Println("[DEBUG] pact verify") + + // Check if we are verifying messages or if we actually have interactions + if len(p.Interactions) == 0 { + return errors.New("there are no interactions to be verified") + } + + mockServer := &MockService{ + BaseURL: fmt.Sprintf("http://%s:%d", p.Host, p.Server.Port), + Consumer: p.Consumer, + Provider: p.Provider, + } + + for _, interaction := range p.Interactions { + err := mockServer.AddInteraction(interaction) + if err != nil { + return err + } + } + + // Run the integration test + err := integrationTest() + if err != nil { + return err + } + + // Run Verification Process + err = mockServer.Verify() + if err != nil { + return err + } + + // Clear out interations + p.Interactions = make([]*Interaction, 0) + + return mockServer.DeleteInteractions() +} + +// WritePact should be called writes when all tests have been performed for a +// given Consumer <-> Provider pair. It will write out the Pact to the +// configured file. +func (p *Pact) WritePact() error { + p.Setup(true) + log.Println("[DEBUG] pact write Pact file") + mockServer := MockService{ + BaseURL: fmt.Sprintf("http://%s:%d", p.Host, p.Server.Port), + Consumer: p.Consumer, + Provider: p.Provider, + PactFileWriteMode: p.PactFileWriteMode, + } + err := mockServer.WritePact() + if err != nil { + return err + } + + return nil +} + +// VerifyProviderRaw reads the provided pact files and runs verification against +// a running Provider API, providing raw response from the Verification process. +// +// Order of events: BeforeEach, stateHandlers, requestFilter(pre post), AfterEach +func (p *Pact) VerifyProviderRaw(request types.VerifyRequest) (types.ProviderVerifierResponse, error) { + p.Setup(false) + var res types.ProviderVerifierResponse + + u, err := url.Parse(request.ProviderBaseURL) + + if err != nil { + return res, err + } + + m := []proxy.Middleware{} + + if request.BeforeEach != nil { + m = append(m, BeforeEachMiddleware(request.BeforeEach)) + } + + if request.AfterEach != nil { + m = append(m, AfterEachMiddleware(request.AfterEach)) + } + + if len(request.StateHandlers) > 0 { + m = append(m, stateHandlerMiddleware(request.StateHandlers)) + } + + if request.RequestFilter != nil { + m = append(m, request.RequestFilter) + } + + // Configure HTTP Verification Proxy + opts := proxy.Options{ + TargetAddress: fmt.Sprintf("%s:%s", u.Hostname(), u.Port()), + TargetScheme: u.Scheme, + Middleware: m, + } + + // Starts the message wrapper API with hooks back to the state handlers + // This maps the 'description' field of a message pact, to a function handler + // that will implement the message producer. This function must return an object and optionally + // and error. The object will be marshalled to JSON for comparison. + port, err := proxy.HTTPReverseProxy(opts) + + // Backwards compatibility, setup old provider states URL if given + // Otherwise point to proxy + setupURL := request.ProviderStatesSetupURL + if request.ProviderStatesSetupURL == "" { + setupURL = fmt.Sprintf("%s://localhost:%d/__setup", u.Scheme, port) + } + + // Construct verifier request + verificationRequest := types.VerifyRequest{ + ProviderBaseURL: fmt.Sprintf("%s://localhost:%d", u.Scheme, port), // + PactURLs: request.PactURLs, + BrokerURL: request.BrokerURL, + Tags: request.Tags, + BrokerUsername: request.BrokerUsername, + BrokerPassword: request.BrokerPassword, + BrokerToken: request.BrokerToken, + PublishVerificationResults: request.PublishVerificationResults, + ProviderVersion: request.ProviderVersion, + ProviderStatesSetupURL: setupURL, + } + + if request.Provider == "" { + verificationRequest.Provider = p.Provider + } + + portErr := waitForPort(port, "tcp", "localhost", p.ClientTimeout, + fmt.Sprintf(`Timed out waiting for http verification proxy on port %d - check for errors`, port)) + + if portErr != nil { + log.Fatal("Error:", err) + return res, portErr + } + + log.Println("[DEBUG] pact provider verification") + + return p.pactClient.VerifyProvider(verificationRequest) +} + +// VerifyProvider accepts an instance of `*testing.T` +// running the provider verification with granular test reporting and +// automatic failure reporting for nice, simple tests. +func (p *Pact) VerifyProvider(t *testing.T, request types.VerifyRequest) (types.ProviderVerifierResponse, error) { + res, err := p.VerifyProviderRaw(request) + + for _, example := range res.Examples { + t.Run(example.Description, func(st *testing.T) { + st.Log(example.FullDescription) + if example.Status != "passed" { + t.Errorf("%s\n%s\n", example.FullDescription, example.Exception.Message) + } + }) + } + + return res, err +} + +var installer = install.NewInstaller() + +var checkCliCompatibility = func() { + log.Println("[DEBUG] checking CLI compatability") + err := installer.CheckInstallation() + + if err != nil { + log.Fatal("[ERROR] CLI tools are out of date, please upgrade before continuing") + } +} + +// BeforeEachMiddleware is invoked before any other, only on the __setup +// request (to avoid duplication) +func BeforeEachMiddleware(BeforeEach types.Hook) proxy.Middleware { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/__setup" { + + log.Println("[DEBUG] executing before hook") + err := BeforeEach() + + if err != nil { + log.Println("[ERROR] error executing before hook:", err) + w.WriteHeader(http.StatusInternalServerError) + } + } + next.ServeHTTP(w, r) + }) + } +} + +// AfterEachMiddleware is invoked after any other, and is the last +// function to be called prior to returning to the test suite. It is +// therefore not invoked on __setup +func AfterEachMiddleware(AfterEach types.Hook) proxy.Middleware { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + next.ServeHTTP(w, r) + + if r.URL.Path != "/__setup" { + log.Println("[DEBUG] executing after hook") + err := AfterEach() + + if err != nil { + log.Println("[ERROR] error executing after hook:", err) + w.WriteHeader(http.StatusInternalServerError) + } + } + }) + } +} + +// stateHandlerMiddleware responds to the various states that are +// given during provider verification +// +// statehandler accepts a state object from the verifier and executes +// any state handlers associated with the provider. +// It will not execute further middleware if it is the designted "state" request +func stateHandlerMiddleware(stateHandlers types.StateHandlers) proxy.Middleware { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/__setup" { + var s *types.ProviderState + decoder := json.NewDecoder(r.Body) + decoder.Decode(&s) + + // Setup any provider state + for _, state := range s.States { + sf, stateFound := stateHandlers[state] + + if !stateFound { + log.Printf("[WARN] state handler not found for state: %v", state) + } else { + // Execute state handler + if err := sf(); err != nil { + log.Printf("[ERROR] state handler for '%v' errored: %v", state, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + } + } + + w.WriteHeader(http.StatusOK) + return + } + + log.Println("[DEBUG] skipping state handler for request", r.RequestURI) + + // Pass through to application + next.ServeHTTP(w, r) + }) + } +} + +var messageVerificationHandler = func(messageHandlers MessageHandlers, stateHandlers StateHandlers) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + + // Extract message + var message Message + body, err := ioutil.ReadAll(r.Body) + r.Body.Close() + + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + err = json.Unmarshal(body, &message) + + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + + // Setup any provider state + for _, state := range message.States { + sf, stateFound := stateHandlers[state.Name] + + if !stateFound { + log.Printf("[WARN] state handler not found for state: %v", state.Name) + } else { + // Execute state handler + if err = sf(state); err != nil { + log.Printf("[WARN] state handler for '%v' return error: %v", state.Name, err) + w.WriteHeader(http.StatusInternalServerError) + return + } + } + } + + // Lookup key in function mapping + f, messageFound := messageHandlers[message.Description] + + if !messageFound { + log.Printf("[ERROR] message handler not found for message description: %v", message.Description) + w.WriteHeader(http.StatusNotFound) + return + } + + // Execute function handler + res, handlerErr := f(message) + + if handlerErr != nil { + w.WriteHeader(http.StatusServiceUnavailable) + return + } + + wrappedResponse := map[string]interface{}{ + "contents": res, + } + + // Write the body back + resBody, errM := json.Marshal(wrappedResponse) + if errM != nil { + w.WriteHeader(http.StatusServiceUnavailable) + fmt.Println("[ERROR] error marshalling objcet:", errM) + return + } + + w.WriteHeader(http.StatusOK) + w.Write(resBody) + } +} + +// VerifyMessageProvider accepts an instance of `*testing.T` +// running provider message verification with granular test reporting and +// automatic failure reporting for nice, simple tests. +// +// A Message Producer is analagous to Consumer in the HTTP Interaction model. +// It is the initiator of an interaction, and expects something on the other end +// of the interaction to respond - just in this case, not immediately. +func (p *Pact) VerifyMessageProvider(t *testing.T, request VerifyMessageRequest) (res types.ProviderVerifierResponse, err error) { + res, err = p.VerifyMessageProviderRaw(request) + + for _, example := range res.Examples { + t.Run(example.Description, func(st *testing.T) { + st.Log(example.FullDescription) + if example.Status != "passed" { + st.Errorf("%s\n", example.Exception.Message) + st.Error("Check to ensure that all message expectations have corresponding message handlers") + } + }) + } + + return +} + +// VerifyMessageProviderRaw runs provider message verification. +// +// A Message Producer is analagous to Consumer in the HTTP Interaction model. +// It is the initiator of an interaction, and expects something on the other end +// of the interaction to respond - just in this case, not immediately. +func (p *Pact) VerifyMessageProviderRaw(request VerifyMessageRequest) (types.ProviderVerifierResponse, error) { + p.Setup(false) + response := types.ProviderVerifierResponse{} + + // Starts the message wrapper API with hooks back to the message handlers + // This maps the 'description' field of a message pact, to a function handler + // that will implement the message producer. This function must return an object and optionally + // and error. The object will be marshalled to JSON for comparison. + mux := http.NewServeMux() + + port, err := utils.GetFreePort() + if err != nil { + return response, fmt.Errorf("unable to allocate a port for verification: %v", err) + } + + // Construct verifier request + verificationRequest := types.VerifyRequest{ + ProviderBaseURL: fmt.Sprintf("http://localhost:%d", port), + PactURLs: request.PactURLs, + BrokerURL: request.BrokerURL, + Tags: request.Tags, + BrokerUsername: request.BrokerUsername, + BrokerPassword: request.BrokerPassword, + BrokerToken: request.BrokerToken, + PublishVerificationResults: request.PublishVerificationResults, + ProviderVersion: request.ProviderVersion, + Provider: p.Provider, + } + + mux.HandleFunc("/", messageVerificationHandler(request.MessageHandlers, request.StateHandlers)) + + ln, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + log.Fatal(err) + } + defer ln.Close() + + log.Printf("[DEBUG] API handler starting: port %d (%s)", port, ln.Addr()) + go http.Serve(ln, mux) + + portErr := waitForPort(port, "tcp", "localhost", p.ClientTimeout, + fmt.Sprintf(`Timed out waiting for pact proxy on port %d - check for errors`, port)) + + if portErr != nil { + log.Fatal("Error:", err) + return response, portErr + } + + log.Println("[DEBUG] pact provider verification") + return p.pactClient.VerifyProvider(verificationRequest) +} + +// VerifyMessageConsumerRaw creates a new Pact _message_ interaction to build a testable +// interaction. +// +// +// A Message Consumer is analagous to a Provider in the HTTP Interaction model. +// It is the receiver of an interaction, and needs to be able to handle whatever +// request was provided. +func (p *Pact) VerifyMessageConsumerRaw(message *Message, handler MessageConsumer) error { + log.Printf("[DEBUG] verify message") + p.Setup(false) + + // Reify the message back to its "example/generated" form + reified, err := p.pactClient.ReifyMessage(&types.PactReificationRequest{ + Message: message.Content, + }) + + if err != nil { + return fmt.Errorf("unable to convert consumer test to a valid JSON representation: %v", err) + } + + t := reflect.TypeOf(message.Type) + if t != nil && t.Name() != "interface" { + log.Println("[DEBUG] narrowing type to", t.Name()) + err = json.Unmarshal(reified.ResponseRaw, &message.Type) + + if err != nil { + return fmt.Errorf("unable to narrow type to %v: %v", t.Name(), err) + } + } + + // Yield message, and send through handler function + generatedMessage := + Message{ + Content: message.Type, + States: message.States, + Description: message.Description, + Metadata: message.Metadata, + } + + err = handler(generatedMessage) + if err != nil { + return err + } + + // If no errors, update Message Pact + return p.pactClient.UpdateMessagePact(types.PactMessageRequest{ + Message: message, + Consumer: p.Consumer, + Provider: p.Provider, + PactDir: p.PactDir, + }) +} + +// VerifyMessageConsumer is a test convience function for VerifyMessageConsumerRaw, +// accepting an instance of `*testing.T` +func (p *Pact) VerifyMessageConsumer(t *testing.T, message *Message, handler MessageConsumer) error { + err := p.VerifyMessageConsumerRaw(message, handler) + + if err != nil { + t.Errorf("VerifyMessageConsumer failed: %v", err) + } + + return err +} diff --git a/vendor/github.com/pact-foundation/pact-go/dsl/publish.go b/vendor/github.com/pact-foundation/pact-go/dsl/publish.go new file mode 100644 index 000000000..8157befda --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/dsl/publish.go @@ -0,0 +1,45 @@ +package dsl + +import ( + "log" + + "github.com/pact-foundation/pact-go/types" +) + +// PactFile is a simple representation of a Pact file to be able to +// parse Consumer/Provider from the file. +type PactFile struct { + // The API Consumer name + Consumer PactName `json:"consumer"` + + // The API Provider name + Provider PactName `json:"provider"` +} + +// PactName represents the name fields in the PactFile. +type PactName struct { + Name string `json:"name"` +} + +// Publisher is the API to send Pact files to a Pact Broker. +type Publisher struct { + pactClient Client +} + +// Publish sends the Pacts to a broker, optionally tagging them +func (p *Publisher) Publish(request types.PublishRequest) error { + log.Println("[DEBUG] pact publisher: publish pact") + + if p.pactClient == nil { + c := NewClient() + p.pactClient = c + } + + err := request.Validate() + + if err != nil { + return err + } + + return p.pactClient.PublishPacts(request) +} diff --git a/vendor/github.com/pact-foundation/pact-go/dsl/request.go b/vendor/github.com/pact-foundation/pact-go/dsl/request.go new file mode 100644 index 000000000..7776ea760 --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/dsl/request.go @@ -0,0 +1,10 @@ +package dsl + +// Request is the default implementation of the Request interface. +type Request struct { + Method string `json:"method"` + Path Matcher `json:"path"` + Query MapMatcher `json:"query,omitempty"` + Headers MapMatcher `json:"headers,omitempty"` + Body interface{} `json:"body,omitempty"` +} diff --git a/vendor/github.com/pact-foundation/pact-go/dsl/response.go b/vendor/github.com/pact-foundation/pact-go/dsl/response.go new file mode 100644 index 000000000..b3036300b --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/dsl/response.go @@ -0,0 +1,8 @@ +package dsl + +// Response is the default implementation of the Response interface. +type Response struct { + Status int `json:"status"` + Headers MapMatcher `json:"headers,omitempty"` + Body interface{} `json:"body,omitempty"` +} diff --git a/vendor/github.com/pact-foundation/pact-go/dsl/service_mock.go b/vendor/github.com/pact-foundation/pact-go/dsl/service_mock.go new file mode 100644 index 000000000..978264c8d --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/dsl/service_mock.go @@ -0,0 +1,65 @@ +package dsl + +import ( + "os/exec" + + "github.com/pact-foundation/pact-go/client" +) + +// ServiceMock is the mock implementation of the Service interface. +type ServiceMock struct { + Cmd string + processes map[int]*exec.Cmd + Args []string + ServiceStopResult bool + ServiceStopError error + ServiceList map[int]*exec.Cmd + ServiceStartCmd *exec.Cmd + ServiceStartCount int + ServicePort int + ServiceStopCount int + ServicesSetupCalled bool + + // ExecFunc sets the function to run when starting commands + ExecFunc func() *exec.Cmd +} + +// Setup the Management services. +func (s *ServiceMock) Setup() { + s.ServicesSetupCalled = true +} + +// Stop a Service and returns the exit status. +func (s *ServiceMock) Stop(pid int) (bool, error) { + s.ServiceStopCount++ + return s.ServiceStopResult, s.ServiceStopError +} + +// List all Service PIDs. +func (s *ServiceMock) List() map[int]*exec.Cmd { + return s.ServiceList +} + +// Start a Service and log its output. +func (s *ServiceMock) Start() *exec.Cmd { + + s.ServiceStartCount++ + cmd := s.ExecFunc() + cmd.Start() + if s.processes == nil { + s.processes = make(map[int]*exec.Cmd) + } + s.processes[cmd.Process.Pid] = cmd + + return cmd +} + +// Command implements to Command operation +func (s *ServiceMock) Command() *exec.Cmd { + return s.ExecFunc() +} + +// NewService creates a new MockService with default settings. +func (s *ServiceMock) NewService(args []string) client.Service { + return s +} diff --git a/vendor/github.com/pact-foundation/pact-go/dsl/verify_mesage_request.go b/vendor/github.com/pact-foundation/pact-go/dsl/verify_mesage_request.go new file mode 100644 index 000000000..6bd4c7a46 --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/dsl/verify_mesage_request.go @@ -0,0 +1,80 @@ +package dsl + +import ( + "fmt" +) + +// VerifyMessageRequest contains the verification logic +// to send to the Pact Message verifier +type VerifyMessageRequest struct { + // Local/HTTP paths to Pact files. + PactURLs []string + + // Pact Broker URL for broker-based verification + BrokerURL string + + // Tags to find in Broker for matrix-based testing + Tags []string + + // Username when authenticating to a Pact Broker. + BrokerUsername string + + // Password when authenticating to a Pact Broker. + BrokerPassword string + + // BrokerToken is required when authenticating using the Bearer token mechanism + BrokerToken string + + // PublishVerificationResults to the Pact Broker. + PublishVerificationResults bool + + // ProviderVersion is the semantical version of the Provider API. + ProviderVersion string + + // MessageHandlers contains a mapped list of message handlers for a provider + // that will be rable to produce the correct message format for a given + // consumer interaction + MessageHandlers MessageHandlers + + // StateHandlers contain a mapped list of message states to functions + // that are used to setup a given provider state prior to the message + // verification step. + StateHandlers StateHandlers + + // Arguments to the VerificationProvider + // Deprecated: This will be deleted after the native library replaces Ruby deps. + Args []string +} + +// Validate checks that the minimum fields are provided. +// Deprecated: This map be deleted after the native library replaces Ruby deps, +// and should not be used outside of this library. +func (v *VerifyMessageRequest) Validate() error { + v.Args = []string{} + + if len(v.PactURLs) != 0 { + v.Args = append(v.Args, v.PactURLs...) + } else { + return fmt.Errorf("Pact URLs is mandatory") + } + + v.Args = append(v.Args, "--format", "json") + + if v.BrokerUsername != "" { + v.Args = append(v.Args, "--broker-username", v.BrokerUsername) + } + + if v.BrokerPassword != "" { + v.Args = append(v.Args, "--broker-password", v.BrokerPassword) + } + + if v.ProviderVersion != "" { + v.Args = append(v.Args, "--provider_app_version", v.ProviderVersion) + } + + if v.PublishVerificationResults { + v.Args = append(v.Args, "--publish_verification_results", "true") + } + + return nil +} diff --git a/vendor/github.com/pact-foundation/pact-go/install/installer.go b/vendor/github.com/pact-foundation/pact-go/install/installer.go new file mode 100644 index 000000000..e6ebaada9 --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/install/installer.go @@ -0,0 +1,101 @@ +// Package install contains functions necessary for installing and checking +// if the necessary underlying Ruby tools have been properly installed +package install + +import ( + "fmt" + "log" + "os/exec" + "strings" + + goversion "github.com/hashicorp/go-version" +) + +// Installer manages the underlying Ruby installation +type Installer struct { + commander commander +} + +const ( + mockServiceRange = ">= 3.1.0, < 4.0.0" + verifierRange = ">= 1.23.0, < 3.0.0" + brokerRange = ">= 1.18.0, < 2.0.0" +) + +var versionMap = map[string]string{ + "pact-mock-service": mockServiceRange, + "pact-provider-verifier": verifierRange, + "pact-broker": brokerRange, +} + +// NewInstaller creates a new initialised Installer +func NewInstaller() *Installer { + return &Installer{commander: realCommander{}} +} + +// CheckInstallation checks installation of all of the tools +func (i *Installer) CheckInstallation() error { + + for binary, versionRange := range versionMap { + log.Println("[INFO] checking", binary, "within range", versionRange) + + version, err := i.GetVersionForBinary(binary) + if err != nil { + return err + } + + if err = i.CheckVersion(binary, version); err != nil { + return err + } + } + + return nil +} + +// CheckVersion checks installation of a given binary using semver-compatible +// comparisions +func (i *Installer) CheckVersion(binary, version string) error { + log.Println("[DEBUG] checking version for binary", binary, "version", version) + v, err := goversion.NewVersion(version) + if err != nil { + log.Println("[DEBUG] err", err) + return err + } + + versionRange, ok := versionMap[binary] + if !ok { + return fmt.Errorf("unable to find version range for binary %s", binary) + } + + log.Println("[DEBUG] checking if version", v, "within semver range", versionRange) + constraints, err := goversion.NewConstraint(versionRange) + 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, binary, versionRange) +} + +// GetVersionForBinary gets the version of a given Ruby binary +func (i *Installer) GetVersionForBinary(binary string) (version string, err error) { + log.Println("[DEBUG] running binary", binary) + + content, err := i.commander.Output(binary, "version") + elements := strings.Split(strings.TrimSpace(string(content)), "\n") + version = strings.TrimSpace(elements[len(elements)-1]) + + return version, err +} + +// commander wraps the exec package, allowing us +// properly test the file system +type commander interface { + Output(command string, args ...string) ([]byte, error) +} + +type realCommander struct{} + +func (c realCommander) Output(command string, args ...string) ([]byte, error) { + return exec.Command(command, args...).CombinedOutput() +} diff --git a/vendor/github.com/pact-foundation/pact-go/proxy/http.go b/vendor/github.com/pact-foundation/pact-go/proxy/http.go new file mode 100644 index 000000000..66db32d0b --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/proxy/http.go @@ -0,0 +1,84 @@ +package proxy + +import ( + "fmt" + "log" + "net/http" + "net/http/httputil" + "net/url" + + "github.com/pact-foundation/pact-go/utils" +) + +// Middleware is a way to use composition to add functionality +// by intercepting the req/response cycle of the Reverse Proxy. +// Each handler must accept an http.Handler and also return an +// http.Handler, allowing a simple way to chain functionality together +type Middleware func(http.Handler) http.Handler + +// Options for the Reverse Proxy configuration +type Options struct { + + // TargetScheme is one of 'http' or 'https' + TargetScheme string + + // TargetAddress is the host:port component to proxy + TargetAddress string + + // ProxyPort is the port to make available for proxying + // Defaults to a random port + ProxyPort int + + // Middleware to apply to the Proxy + Middleware []Middleware +} + +// loggingMiddleware logs requests to the proxy +func loggingMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Printf("[DEBUG] http reverse proxy received connection from %s on path %s\n", r.RemoteAddr, r.RequestURI) + next.ServeHTTP(w, r) + }) +} + +// chainHandlers takes a set of middleware and joins them together +// into a single Middleware, making it much simpler to compose middleware +// together +func chainHandlers(mw ...Middleware) Middleware { + return func(final http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + last := final + for i := len(mw) - 1; i >= 0; i-- { + last = mw[i](last) + } + last.ServeHTTP(w, r) + }) + } +} + +// HTTPReverseProxy provides a default setup for proxying +// internal components within the framework +func HTTPReverseProxy(options Options) (int, error) { + port := options.ProxyPort + var err error + + proxy := httputil.NewSingleHostReverseProxy(&url.URL{ + Scheme: options.TargetScheme, + Host: options.TargetAddress, + }) + + if port == 0 { + port, err = utils.GetFreePort() + if err != nil { + log.Println("[ERROR] unable to start reverse proxy server:", err) + return 0, err + } + } + + wrapper := chainHandlers(append(options.Middleware, loggingMiddleware)...) + + log.Println("[DEBUG] starting reverse proxy on port", port) + go http.ListenAndServe(fmt.Sprintf(":%d", port), wrapper(proxy)) + + return port, nil +} diff --git a/vendor/github.com/pact-foundation/pact-go/types/command_response.go b/vendor/github.com/pact-foundation/pact-go/types/command_response.go new file mode 100644 index 000000000..aef5da245 --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/types/command_response.go @@ -0,0 +1,12 @@ +// Package types contains a number of structs common to the library. +package types + +// CommandResponse contains the exit status and any message from running +// an external command / service. +type CommandResponse struct { + // System exit code from the command. Note that this will only even be 0 or 1. + ExitCode int + + // Error message (if any) from the command. + Message string +} diff --git a/vendor/github.com/pact-foundation/pact-go/types/handler.go b/vendor/github.com/pact-foundation/pact-go/types/handler.go new file mode 100644 index 000000000..938165ce8 --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/types/handler.go @@ -0,0 +1,14 @@ +package types + +// StateHandler is a provider function that sets up a given state before +// the provider interaction is validated +type StateHandler func() error + +// StateHandlers is a list of StateHandler's +type StateHandlers map[string]StateHandler + +// State specifies how the system should be configured when +// verified. e.g. "user A exists" +type State struct { + Name string `json:"name"` +} diff --git a/vendor/github.com/pact-foundation/pact-go/types/mock_server.go b/vendor/github.com/pact-foundation/pact-go/types/mock_server.go new file mode 100644 index 000000000..3b746c7b8 --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/types/mock_server.go @@ -0,0 +1,9 @@ +package types + +// MockServer contains the RPC client interface to a Mock Server +type MockServer struct { + Pid int + Port int + Error error + Args []string +} diff --git a/vendor/github.com/pact-foundation/pact-go/types/pact_message_request.go b/vendor/github.com/pact-foundation/pact-go/types/pact_message_request.go new file mode 100644 index 000000000..231325e30 --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/types/pact_message_request.go @@ -0,0 +1,49 @@ +package types + +import "encoding/json" + +// PactMessageRequest contains the response from the Pact Message +// CLI execution. +type PactMessageRequest struct { + + // Message is the object to be marshalled to JSON + Message interface{} + + // Consumer is the name of the message consumer + Consumer string + + // Provider is the name of the message provider + Provider string + + // PactDir is the location of where pacts should be stored + PactDir string + + // Args are the arguments sent to to the message service + Args []string +} + +// Validate checks all things are well and constructs +// the CLI args to the message service +func (m *PactMessageRequest) Validate() error { + m.Args = []string{} + + body, err := json.Marshal(m.Message) + if err != nil { + return err + } + + m.Args = append(m.Args, []string{ + "update", + string(body), + "--consumer", + m.Consumer, + "--provider", + m.Provider, + "--pact-dir", + m.PactDir, + "--pact-specification-version", + "3", + }...) + + return nil +} diff --git a/vendor/github.com/pact-foundation/pact-go/types/pact_reification_request.go b/vendor/github.com/pact-foundation/pact-go/types/pact_reification_request.go new file mode 100644 index 000000000..15fc17577 --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/types/pact_reification_request.go @@ -0,0 +1,32 @@ +package types + +import "encoding/json" + +// PactReificationRequest contains the response from the Pact Message +// CLI execution. +type PactReificationRequest struct { + + // Message is the object to be marshalled to JSON + Message interface{} + + // Args are the arguments sent to to the message service + Args []string +} + +// Validate checks all things are well and constructs +// the CLI args to the message service +func (m *PactReificationRequest) Validate() error { + m.Args = []string{} + + body, err := json.Marshal(m.Message) + if err != nil { + return err + } + + m.Args = append(m.Args, []string{ + "reify", + string(body), + }...) + + return nil +} diff --git a/vendor/github.com/pact-foundation/pact-go/types/provider_state.go b/vendor/github.com/pact-foundation/pact-go/types/provider_state.go new file mode 100644 index 000000000..2d6551e5a --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/types/provider_state.go @@ -0,0 +1,15 @@ +package types + +// ProviderState Models a provider state coming over the Wire. +// This is generally provided as a request to an HTTP endpoint (e.g. PUT /state) +// to configure a state on a Provider. +type ProviderState struct { + Consumer string `json:"consumer"` + State string `json:"state"` + States []string `json:"states"` +} + +// ProviderStates is a mapping of consumers to all known states. This is usually +// a response from an HTTP endpoint (e.g. GET /states) to find all states a +// provider has. +type ProviderStates map[string][]string diff --git a/vendor/github.com/pact-foundation/pact-go/types/provider_verifier_response.go b/vendor/github.com/pact-foundation/pact-go/types/provider_verifier_response.go new file mode 100644 index 000000000..8f0f5e72a --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/types/provider_verifier_response.go @@ -0,0 +1,30 @@ +package types + +// ProviderVerifierResponse contains the ouput of the pact-provider-verifier +// command. +type ProviderVerifierResponse struct { + Version string `json:"version"` + Examples []struct { + ID string `json:"id"` + Description string `json:"description"` + FullDescription string `json:"full_description"` + Status string `json:"status"` + FilePath string `json:"file_path"` + LineNumber int `json:"line_number"` + RunTime float64 `json:"run_time"` + PendingMessage interface{} `json:"pending_message"` + Exception struct { + Class string `json:"class"` + Message string `json:"message"` + Backtrace []string `json:"backtrace"` + } `json:"exception,omitempty"` + } `json:"examples"` + Summary struct { + Duration float64 `json:"duration"` + ExampleCount int `json:"example_count"` + FailureCount int `json:"failure_count"` + PendingCount int `json:"pending_count"` + ErrorsOutsideOfExamplesCount int `json:"errors_outside_of_examples_count"` + } `json:"summary"` + SummaryLine string `json:"summary_line"` +} diff --git a/vendor/github.com/pact-foundation/pact-go/types/publish_request.go b/vendor/github.com/pact-foundation/pact-go/types/publish_request.go new file mode 100644 index 000000000..b5f41b12f --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/types/publish_request.go @@ -0,0 +1,90 @@ +package types + +import ( + "errors" + "fmt" +) + +// PublishRequest contains the details required to Publish Pacts to a broker. +type PublishRequest struct { + // Array of local Pact files or directories containing them. Required. + PactURLs []string + + // URL to fetch the provider states for the given provider API. Optional. + PactBroker string + + // Username for Pact Broker basic authentication. Optional + BrokerUsername string + + // Password for Pact Broker basic authentication. Optional + BrokerPassword string + + // BrokerToken is required when authenticating using the Bearer token mechanism + BrokerToken string + + // ConsumerVersion is the semantical version of the consumer API. + ConsumerVersion string + + // Tags help you organise your Pacts for different testing purposes. + // e.g. "production", "master" and "development" are some common examples. + Tags []string + + // Verbose increases verbosity of output + // Deprecated + Verbose bool + + // Arguments to the VerificationProvider + // Deprecated: This will be deleted after the native library replaces Ruby deps. + Args []string +} + +// Validate checks that the minimum fields are provided. +// Deprecated: This map be deleted after the native library replaces Ruby deps, +// and should not be used outside of this library. +func (p *PublishRequest) Validate() error { + p.Args = []string{} + + if len(p.PactURLs) != 0 { + p.Args = append(p.Args, p.PactURLs...) + } else { + return fmt.Errorf("'PactURLs' is mandatory") + } + + if p.BrokerUsername != "" { + p.Args = append(p.Args, "--broker-username", p.BrokerUsername) + } + + if p.BrokerPassword != "" { + p.Args = append(p.Args, "--broker-password", p.BrokerPassword) + } + + if p.PactBroker != "" && ((p.BrokerUsername == "" && p.BrokerPassword != "") || (p.BrokerUsername != "" && p.BrokerPassword == "")) { + return errors.New("both 'BrokerUsername' and 'BrokerPassword' must be supplied if one given") + } + + if p.PactBroker == "" { + return fmt.Errorf("'PactBroker' is mandatory") + } + p.Args = append(p.Args, "--broker-base-url", p.PactBroker) + + if p.BrokerToken != "" { + p.Args = append(p.Args, "--broker-token", p.BrokerToken) + } + + if p.ConsumerVersion == "" { + return fmt.Errorf("'ConsumerVersion' is mandatory") + } + p.Args = append(p.Args, "--consumer-app-version", p.ConsumerVersion) + + if len(p.Tags) > 0 { + for _, t := range p.Tags { + p.Args = append(p.Args, "--tag", t) + } + } + + if p.Verbose { + p.Args = append(p.Args, "--verbose") + } + + return nil +} diff --git a/vendor/github.com/pact-foundation/pact-go/types/reification_response.go b/vendor/github.com/pact-foundation/pact-go/types/reification_response.go new file mode 100644 index 000000000..4a0dc7dc1 --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/types/reification_response.go @@ -0,0 +1,10 @@ +package types + +// ReificationResponse contains the ouput of the reification request +type ReificationResponse struct { + // Interface wrapped object + Response interface{} + + // Raw response from reification + ResponseRaw []byte +} diff --git a/vendor/github.com/pact-foundation/pact-go/types/verify_request.go b/vendor/github.com/pact-foundation/pact-go/types/verify_request.go new file mode 100644 index 000000000..314ffe6c4 --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/types/verify_request.go @@ -0,0 +1,161 @@ +package types + +import ( + "errors" + "fmt" + "log" + + "github.com/pact-foundation/pact-go/proxy" +) + +// Hook functions are used to tap into the lifecycle of a Consumer or Provider test +type Hook func() error + +// VerifyRequest contains the verification params. +type VerifyRequest struct { + // URL to hit during provider verification. + ProviderBaseURL string + + // Local/HTTP paths to Pact files. + PactURLs []string + + // Pact Broker URL for broker-based verification + BrokerURL string + + // Tags to find in Broker for matrix-based testing + Tags []string + + // ProviderStatesSetupURL is the endpoint to post current provider state + // to on the Provider API. + // Deprecated: For backward compatibility ProviderStatesSetupURL is + // still supported. Use StateHandlers instead. + ProviderStatesSetupURL string + + // Provider is the name of the Providing service. + Provider string + + // Username when authenticating to a Pact Broker. + BrokerUsername string + + // Password when authenticating to a Pact Broker. + BrokerPassword string + + // BrokerToken is required when authenticating using the Bearer token mechanism + BrokerToken string + + // PublishVerificationResults to the Pact Broker. + PublishVerificationResults bool + + // ProviderVersion is the semantical version of the Provider API. + ProviderVersion string + + // CustomProviderHeaders are headers to add during pact verification `requests`. + // eg 'Authorization: Basic cGFjdDpwYWN0'. + // + // NOTE: Use this feature very carefully, as anything in here is not captured + // in the contract (e.g. time-bound tokens) + // + // NOTE: This should be used very carefully and deliberately, as anything you do here + // runs the risk of changing the contract and breaking the real system. + CustomProviderHeaders []string + + // StateHandlers contain a mapped list of message states to functions + // that are used to setup a given provider state prior to the message + // verification step. + StateHandlers StateHandlers + + // BeforeEach allows you to configure your provider prior to the individual test execution + // e.g. setup temporary tokens, prepare data + BeforeEach Hook + + // AfterEach allows you to configure your provider prior to the test execution + // e.g. reset the database state + AfterEach Hook + + // RequestFilter is a piece of middleware that will intercept requests/responses + // from the provider in order to modify it. This is useful in situations where + // you need to override a value due to time sensitivity - such as a OAuth Bearer + // token. + // NOTE: This should be used very carefully and deliberately, as anything you do here + // runs the risk of changing the contract and breaking the real system. + RequestFilter proxy.Middleware + + // Verbose increases verbosity of output + // Deprecated + Verbose bool + + // Arguments to the VerificationProvider + // Deprecated: This will be deleted after the native library replaces Ruby deps. + Args []string +} + +// Validate checks that the minimum fields are provided. +// Deprecated: This map be deleted after the native library replaces Ruby deps, +// and should not be used outside of this library. +func (v *VerifyRequest) Validate() error { + v.Args = []string{} + + if len(v.PactURLs) != 0 { + v.Args = append(v.Args, v.PactURLs...) + } + + if len(v.PactURLs) == 0 && v.BrokerURL == "" { + return fmt.Errorf("One of 'PactURLs' or 'BrokerURL' must be specified") + } + + if len(v.CustomProviderHeaders) != 0 { + for _, header := range v.CustomProviderHeaders { + v.Args = append(v.Args, "--custom-provider-header", header) + } + } + + v.Args = append(v.Args, "--format", "json") + + if v.ProviderBaseURL != "" { + v.Args = append(v.Args, "--provider-base-url", v.ProviderBaseURL) + } else { + return fmt.Errorf("Provider base URL is mandatory") + } + + if v.ProviderStatesSetupURL != "" { + v.Args = append(v.Args, "--provider-states-setup-url", v.ProviderStatesSetupURL) + } + + if v.BrokerUsername != "" { + v.Args = append(v.Args, "--broker-username", v.BrokerUsername) + } + + if v.BrokerPassword != "" { + v.Args = append(v.Args, "--broker-password", v.BrokerPassword) + } + + if v.BrokerURL != "" && ((v.BrokerUsername == "" && v.BrokerPassword != "") || (v.BrokerUsername != "" && v.BrokerPassword == "")) { + return errors.New("both 'BrokerUsername' and 'BrokerPassword' must be supplied if one given") + } + + if v.BrokerURL != "" { + v.Args = append(v.Args, "--pact-broker-base-url", v.BrokerURL) + } + + if v.BrokerToken != "" { + v.Args = append(v.Args, "--broker-token", v.BrokerToken) + } + + if v.ProviderVersion != "" { + v.Args = append(v.Args, "--provider_app_version", v.ProviderVersion) + } + + if v.Provider != "" { + v.Args = append(v.Args, "--provider", v.Provider) + } + + if v.PublishVerificationResults { + v.Args = append(v.Args, "--publish_verification_results", "true") + } + + if v.Verbose { + log.Println("[DEBUG] verifier: ignoring deprecated Verbose flag") + } + + return nil +} diff --git a/vendor/github.com/pact-foundation/pact-go/utils/port.go b/vendor/github.com/pact-foundation/pact-go/utils/port.go new file mode 100644 index 000000000..605fcfa10 --- /dev/null +++ b/vendor/github.com/pact-foundation/pact-go/utils/port.go @@ -0,0 +1,87 @@ +// Package utils contains a number of helper / utility functions. +package utils + +import ( + "errors" + "fmt" + "net" + "strconv" + "strings" +) + +// GetFreePort Gets an available port by asking the kernal for a random port +// ready and available for use. +func GetFreePort() (int, error) { + addr, err := net.ResolveTCPAddr("tcp", "localhost:0") + if err != nil { + return 0, err + } + + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return 0, err + } + defer l.Close() + return l.Addr().(*net.TCPAddr).Port, nil +} + +// FindPortInRange Iterate through CSV or Range of ports to find open port +// Valid inputs are "8081", "8081,8085", "8081-8085". Do not combine +// list and range +func FindPortInRange(s string) (int, error) { + // Take care of csv and single value + if !strings.Contains(s, "-") { + ports := strings.Split(strings.TrimSpace(s), ",") + for _, p := range ports { + i, err := strconv.Atoi(p) + if err != nil { + return 0, err + } + err = checkPort(i) + if err != nil { + continue + } + return i, nil + } + return 0, errors.New("all passed ports are unusable") + } + // Now take care of ranges + ports := strings.Split(strings.TrimSpace(s), "-") + if len(ports) != 2 { + return 0, errors.New("invalid range passed") + } + lower, err := strconv.Atoi(ports[0]) + if err != nil { + return 0, err + } + upper, err := strconv.Atoi(ports[1]) + if err != nil { + return 0, err + } + if upper < lower { + return 0, errors.New("invalid range passed") + } + for i := lower; i <= upper; i++ { + err = checkPort(i) + if err != nil { + continue + } + return i, nil + } + return 0, errors.New("all passed ports are unusable") +} + +func checkPort(p int) error { + s := fmt.Sprintf("localhost:%d", p) + addr, err := net.ResolveTCPAddr("tcp", s) + if err != nil { + return err + } + + l, err := net.ListenTCP("tcp", addr) + if err != nil { + return err + } + defer l.Close() + return nil +}