From aa460b9f17d155d1a90bda50fe21b992668a4622 Mon Sep 17 00:00:00 2001 From: kapishmalik Date: Thu, 4 Apr 2024 00:53:17 +0530 Subject: [PATCH 1/5] add support for remote post serve action --- core/action/action.go | 53 ++++++++- core/action/action_test.go | 110 ++++++++++++++++-- core/action/postserveactiondetails_test.go | 4 +- core/cmd/hoverfly/main.go | 15 ++- ...hoverfly_postserveactiondetails_handler.go | 10 +- ...fly_postserveactiondetails_handler_test.go | 6 +- .../v2/postserveactiondetails_views.go | 5 +- core/hoverfly.go | 2 +- core/hoverfly_service.go | 17 ++- core/hoverfly_service_test.go | 52 +++++++-- .../core/ft_postserveaction_test.go | 93 +++++++++++++-- functional-tests/functional_tests.go | 17 ++- .../hoverctl/postserveaction_test.go | 30 ++++- hoverctl/cmd/postserveaction.go | 38 +++--- hoverctl/wrapper/postserveaction.go | 30 ++++- 15 files changed, 425 insertions(+), 57 deletions(-) diff --git a/core/action/action.go b/core/action/action.go index 7a13a74af..68d671756 100644 --- a/core/action/action.go +++ b/core/action/action.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "io/ioutil" + "net/http" "os" "os/exec" "path" @@ -19,10 +20,11 @@ import ( type Action struct { Binary string Script *os.File + Remote string DelayInMs int } -func NewAction(actionName, binary, scriptContent string, delayInMs int) (*Action, error) { +func NewLocalAction(actionName, binary, scriptContent string, delayInMs int) (*Action, error) { scriptInfo := &Action{} if strings.TrimSpace(actionName) == "" { @@ -41,6 +43,19 @@ func NewAction(actionName, binary, scriptContent string, delayInMs int) (*Action return scriptInfo, nil } +func NewRemoteAction(actionName, host string, delayInMs int) (*Action, error) { + + if strings.TrimSpace(actionName) == "" { + return nil, errors.New("empty action name passed") + } + + if !hasHttpPrefix(host) { + return nil, errors.New("remote host is invalid") + } + + return &Action{Remote: host, DelayInMs: delayInMs}, nil +} + func setBinary(action *Action, binary string) error { action.Binary = binary return nil @@ -96,11 +111,12 @@ func (action *Action) GetActionView(actionName string) v2.ActionView { ActionName: actionName, Binary: action.Binary, ScriptContent: scriptContent, + Remote: action.Remote, DelayInMs: action.DelayInMs, } } -func (action *Action) ExecuteLocally(pair *models.RequestResponsePair) error { +func (action *Action) Execute(pair *models.RequestResponsePair) error { pairViewBytes, err := json.Marshal(pair.ConvertToRequestResponsePairView()) if err != nil { @@ -114,6 +130,35 @@ func (action *Action) ExecuteLocally(pair *models.RequestResponsePair) error { //adding 200 ms to include some buffer for it to return response time.Sleep(time.Duration(200+action.DelayInMs) * time.Millisecond) + //if it is remote callback + if action.Remote != "" { + + req, err := http.NewRequest("POST", action.Remote, bytes.NewBuffer(pairViewBytes)) + if err != nil { + log.WithFields(log.Fields{ + "error": err.Error(), + }).Error("Error when building request to remote post serve action") + return err + } + + req.Header.Add("Content-Type", "application/json") + + resp, err := http.DefaultClient.Do(req) + if err != nil { + log.WithFields(log.Fields{ + "error": err.Error(), + }).Error("Error when communicating with remote post serve action") + return err + } + + if resp.StatusCode != 200 { + log.Error("Remote post serve action did not process payload") + return nil + } + log.Info("Remote post serve action invoked successfully") + return nil + } + actionCommand := exec.Command(action.Binary, action.Script.Name()) actionCommand.Stdin = bytes.NewReader(pairViewBytes) var stdout bytes.Buffer @@ -137,3 +182,7 @@ func (action *Action) ExecuteLocally(pair *models.RequestResponsePair) error { } return nil } + +func hasHttpPrefix(host string) bool { + return strings.HasPrefix(host, "http") || strings.HasPrefix(host, "https") +} diff --git a/core/action/action_test.go b/core/action/action_test.go index 5742ef470..4d48d1383 100644 --- a/core/action/action_test.go +++ b/core/action/action_test.go @@ -1,6 +1,9 @@ package action_test import ( + "github.com/gorilla/mux" + "net/http" + "net/http/httptest" "testing" "github.com/SpectoLabs/hoverfly/core/action" @@ -10,10 +13,10 @@ import ( const pythonBasicScript = "import sys\nprint(sys.stdin.readlines()[0])" -func Test_NewActionMethod(t *testing.T) { +func Test_NewLocalActionMethod(t *testing.T) { RegisterTestingT(t) - newAction, err := action.NewAction("test-callback", "python3", "dummy-script", 1800) + newAction, err := action.NewLocalAction("test-callback", "python3", "dummy-script", 1800) Expect(err).To(BeNil()) Expect(newAction).NotTo(BeNil()) @@ -26,10 +29,51 @@ func Test_NewActionMethod(t *testing.T) { Expect(scriptContent).To(Equal("dummy-script")) } -func Test_GetActionViewMethod(t *testing.T) { +func Test_NewRemoteActionMethodWithEmptyHost(t *testing.T) { RegisterTestingT(t) - newAction, err := action.NewAction("test-callback", "python3", "dummy-script", 1800) + newAction, err := action.NewRemoteAction("test-callback", "", 1800) + + Expect(err).NotTo(BeNil()) + Expect(newAction).To(BeNil()) +} + +func Test_NewRemoteActionMethodWithInvalidHost(t *testing.T) { + RegisterTestingT(t) + + newAction, err := action.NewRemoteAction("test-callback", "testing", 1800) + + Expect(err).NotTo(BeNil()) + Expect(err.Error()).To(Equal("remote host is invalid")) + Expect(newAction).To(BeNil()) +} + +func Test_NewRemoteActionMethodWithHttpHost(t *testing.T) { + RegisterTestingT(t) + + newAction, err := action.NewRemoteAction("test-callback", "http://localhost", 1800) + + Expect(err).To(BeNil()) + Expect(newAction).NotTo(BeNil()) + Expect(newAction.Remote).To(Equal("http://localhost")) + Expect(newAction.DelayInMs).To(Equal(1800)) +} + +func Test_NewRemoteActionMethodWithHttpsHost(t *testing.T) { + RegisterTestingT(t) + + newAction, err := action.NewRemoteAction("test-callback", "https://test.com", 1800) + + Expect(err).To(BeNil()) + Expect(newAction).NotTo(BeNil()) + Expect(newAction.Remote).To(Equal("https://test.com")) + Expect(newAction.DelayInMs).To(Equal(1800)) +} + +func Test_GetLocalActionViewMethod(t *testing.T) { + RegisterTestingT(t) + + newAction, err := action.NewLocalAction("test-callback", "python3", "dummy-script", 1800) Expect(err).To(BeNil()) actionView := newAction.GetActionView("test-callback") @@ -40,9 +84,24 @@ func Test_GetActionViewMethod(t *testing.T) { Expect(actionView.DelayInMs).To(Equal(1800)) } -func Test_ExecuteLocallyPostServeAction(t *testing.T) { +func Test_GetRemoteActionViewMethod(t *testing.T) { + RegisterTestingT(t) + + newAction, err := action.NewRemoteAction("test-callback", "http://localhost:8000", 1800) + + Expect(err).To(BeNil()) + actionView := newAction.GetActionView("test-callback") + + Expect(actionView.ActionName).To(Equal("test-callback")) + Expect(actionView.Binary).To(Equal("")) + Expect(actionView.ScriptContent).To(Equal("")) + Expect(actionView.Remote).To(Equal("http://localhost:8000")) + Expect(actionView.DelayInMs).To(Equal(1800)) +} + +func Test_ExecuteLocalPostServeAction(t *testing.T) { RegisterTestingT(t) - newAction, err := action.NewAction("test-callback", "python3", pythonBasicScript, 0) + newAction, err := action.NewLocalAction("test-callback", "python3", pythonBasicScript, 0) Expect(err).To(BeNil()) @@ -51,6 +110,43 @@ func Test_ExecuteLocallyPostServeAction(t *testing.T) { originalPair := models.RequestResponsePair{Response: resp, Request: req} - err = newAction.ExecuteLocally(&originalPair) + err = newAction.Execute(&originalPair) Expect(err).To(BeNil()) } + +func Test_ExecuteRemotePostServeAction(t *testing.T) { + RegisterTestingT(t) + muxRouter := mux.NewRouter() + muxRouter.HandleFunc("/process", processHandlerOkay).Methods("POST") + server := httptest.NewServer(muxRouter) + defer server.Close() + + originalPair := models.RequestResponsePair{ + Response: models.ResponseDetails{ + Body: "Normal body", + }, + } + + newAction, err := action.NewRemoteAction("test-callback", server.URL+"/process", 0) + Expect(err).To(BeNil()) + err = newAction.Execute(&originalPair) + Expect(err).To(BeNil()) +} + +func Test_ExecuteRemotePostServeAction_WithUnReachableHost(t *testing.T) { + originalPair := models.RequestResponsePair{ + Response: models.ResponseDetails{ + Body: "Normal body", + }, + } + + newAction, err := action.NewRemoteAction("test-callback", "http://test", 0) + Expect(err).To(BeNil()) + + err = newAction.Execute(&originalPair) + Expect(err).NotTo(BeNil()) +} + +func processHandlerOkay(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) +} diff --git a/core/action/postserveactiondetails_test.go b/core/action/postserveactiondetails_test.go index 40d8109c4..2daaf36a0 100644 --- a/core/action/postserveactiondetails_test.go +++ b/core/action/postserveactiondetails_test.go @@ -10,7 +10,7 @@ import ( func Test_SetPostServeActionMethod(t *testing.T) { RegisterTestingT(t) - newAction, err := action.NewAction("test-callback", "python3", "dummy script", 1800) + newAction, err := action.NewLocalAction("test-callback", "python3", "dummy script", 1800) Expect(err).To(BeNil()) unit := action.NewPostServeActionDetails() @@ -25,7 +25,7 @@ func Test_SetPostServeActionMethod(t *testing.T) { func Test_DeletePostServeActionMethod(t *testing.T) { RegisterTestingT(t) - newAction, err := action.NewAction("test-callback", "python3", "dummy script", 1800) + newAction, err := action.NewLocalAction("test-callback", "python3", "dummy script", 1800) Expect(err).To(BeNil()) unit := action.NewPostServeActionDetails() diff --git a/core/cmd/hoverfly/main.go b/core/cmd/hoverfly/main.go index 8280522b9..9536d3aa4 100644 --- a/core/cmd/hoverfly/main.go +++ b/core/cmd/hoverfly/main.go @@ -559,7 +559,7 @@ func main() { } if fileContents, err := ioutil.ReadFile(splitPostServeAction[2]); err == nil { - err = hoverfly.SetPostServeAction(splitPostServeAction[0], splitPostServeAction[1], string(fileContents), delayInMs) + err = hoverfly.SetLocalPostServeAction(splitPostServeAction[0], splitPostServeAction[1], string(fileContents), delayInMs) if err != nil { log.WithFields(log.Fields{ "error": err.Error(), @@ -567,6 +567,19 @@ func main() { }).Fatal("Failed to import post serve action") } } + } else if len(splitPostServeAction) == 3 { + delayInMs, err := strconv.Atoi(splitPostServeAction[2]) + if err != nil { + //default to 1000 incase of error + delayInMs = 1000 + } + err = hoverfly.SetRemotePostServeAction(splitPostServeAction[0], splitPostServeAction[1], delayInMs) + if err != nil { + log.WithFields(log.Fields{ + "error": err.Error(), + "import": v, + }).Fatal("Failed to import post serve action") + } } else { log.WithFields(log.Fields{ "import": v, diff --git a/core/handlers/v2/hoverfly_postserveactiondetails_handler.go b/core/handlers/v2/hoverfly_postserveactiondetails_handler.go index 1ba19dab1..cade3dd72 100644 --- a/core/handlers/v2/hoverfly_postserveactiondetails_handler.go +++ b/core/handlers/v2/hoverfly_postserveactiondetails_handler.go @@ -11,7 +11,8 @@ import ( type HoverflyPostServeActionDetails interface { GetAllPostServeActions() PostServeActionDetailsView - SetPostServeAction(string, string, string, int) error + SetLocalPostServeAction(string, string, string, int) error + SetRemotePostServeAction(string, string, int) error DeletePostServeAction(string) error } @@ -51,12 +52,15 @@ func (postServeActionDetailsHandler *HoverflyPostServeActionDetailsHandler) Put( return } - err = postServeActionDetailsHandler.Hoverfly.SetPostServeAction(actionRequest.ActionName, actionRequest.Binary, actionRequest.ScriptContent, actionRequest.DelayInMs) + if actionRequest.Remote != "" { + err = postServeActionDetailsHandler.Hoverfly.SetRemotePostServeAction(actionRequest.ActionName, actionRequest.Remote, actionRequest.DelayInMs) + } else { + err = postServeActionDetailsHandler.Hoverfly.SetLocalPostServeAction(actionRequest.ActionName, actionRequest.Binary, actionRequest.ScriptContent, actionRequest.DelayInMs) + } if err != nil { handlers.WriteErrorResponse(w, err.Error(), 400) return } - postServeActionDetailsHandler.Get(w, req, next) } diff --git a/core/handlers/v2/hoverfly_postserveactiondetails_handler_test.go b/core/handlers/v2/hoverfly_postserveactiondetails_handler_test.go index 73c3d6ec8..2ecd45aa6 100644 --- a/core/handlers/v2/hoverfly_postserveactiondetails_handler_test.go +++ b/core/handlers/v2/hoverfly_postserveactiondetails_handler_test.go @@ -20,7 +20,11 @@ func (HoverflyPostServeActionDetailsStub) GetAllPostServeActions() PostServeActi return PostServeActionDetailsView{Actions: actions} } -func (HoverflyPostServeActionDetailsStub) SetPostServeAction(string, string, string, int) error { +func (HoverflyPostServeActionDetailsStub) SetLocalPostServeAction(string, string, string, int) error { + return nil +} + +func (HoverflyPostServeActionDetailsStub) SetRemotePostServeAction(string, string, int) error { return nil } diff --git a/core/handlers/v2/postserveactiondetails_views.go b/core/handlers/v2/postserveactiondetails_views.go index 101a82999..565fde5a5 100644 --- a/core/handlers/v2/postserveactiondetails_views.go +++ b/core/handlers/v2/postserveactiondetails_views.go @@ -6,7 +6,8 @@ type PostServeActionDetailsView struct { type ActionView struct { ActionName string `json:"actionName"` - Binary string `json:"binary"` - ScriptContent string `json:"script"` + Binary string `json:"binary,omitempty"` + ScriptContent string `json:"script,omitempty"` + Remote string `json:"remote,omitempty"` DelayInMs int `json:"delayInMs,omitempty"` } diff --git a/core/hoverfly.go b/core/hoverfly.go index 9ddc4855f..bb7ade90b 100644 --- a/core/hoverfly.go +++ b/core/hoverfly.go @@ -221,7 +221,7 @@ func (hf *Hoverfly) processRequest(req *http.Request) *http.Response { if result.PostServeActionInputDetails != nil { if postServeAction, ok := hf.PostServeActionDetails.Actions[result.PostServeActionInputDetails.PostServeAction]; ok { - go postServeAction.ExecuteLocally(result.PostServeActionInputDetails.Pair) + go postServeAction.Execute(result.PostServeActionInputDetails.Pair) } } diff --git a/core/hoverfly_service.go b/core/hoverfly_service.go index 95dba6419..0ebe60124 100644 --- a/core/hoverfly_service.go +++ b/core/hoverfly_service.go @@ -471,9 +471,22 @@ func (hf *Hoverfly) GetAllPostServeActions() v2.PostServeActionDetailsView { } } -func (hf *Hoverfly) SetPostServeAction(actionName string, binary string, scriptContent string, delayInMs int) error { +func (hf *Hoverfly) SetLocalPostServeAction(actionName string, binary string, scriptContent string, delayInMs int) error { - action, err := action.NewAction(actionName, binary, scriptContent, delayInMs) + action, err := action.NewLocalAction(actionName, binary, scriptContent, delayInMs) + if err != nil { + return err + } + err = hf.PostServeActionDetails.SetAction(actionName, action) + if err != nil { + return err + } + return nil +} + +func (hf *Hoverfly) SetRemotePostServeAction(actionName, remote string, delayInMs int) error { + + action, err := action.NewRemoteAction(actionName, remote, delayInMs) if err != nil { return err } diff --git a/core/hoverfly_service_test.go b/core/hoverfly_service_test.go index fe3959626..a422b7506 100644 --- a/core/hoverfly_service_test.go +++ b/core/hoverfly_service_test.go @@ -1359,28 +1359,33 @@ func TestHoverfly_GetPostServeActions(t *testing.T) { RegisterTestingT(t) unit := NewHoverflyWithConfiguration(&Configuration{}) - actionDetails := action.Action{Binary: "python3", DelayInMs: 1900} + localActionDetails := action.Action{Binary: "python3", DelayInMs: 1900} + remoteActionDetails := action.Action{Remote: "http://localhost", DelayInMs: 1800} actionMap := map[string]action.Action{ - "test-callback": actionDetails, + "test-local-callback": localActionDetails, + "test-remote-callback": remoteActionDetails, } unit.PostServeActionDetails.Actions = actionMap postServeActions := unit.GetAllPostServeActions() Expect(postServeActions).NotTo(BeNil()) - Expect(postServeActions.Actions).To(HaveLen(1)) - Expect(postServeActions.Actions[0].ActionName).To(Equal("test-callback")) + Expect(postServeActions.Actions).To(HaveLen(2)) + Expect(postServeActions.Actions[0].ActionName).To(Equal("test-local-callback")) Expect(postServeActions.Actions[0].Binary).To(Equal("python3")) Expect(postServeActions.Actions[0].DelayInMs).To(Equal(1900)) + Expect(postServeActions.Actions[1].ActionName).To(Equal("test-remote-callback")) + Expect(postServeActions.Actions[1].Remote).To(Equal("http://localhost")) + Expect(postServeActions.Actions[1].DelayInMs).To(Equal(1800)) } -func TestHoverfly_SetPostServeAction(t *testing.T) { +func TestHoverfly_SetLocalPostServeAction(t *testing.T) { RegisterTestingT(t) unit := NewHoverflyWithConfiguration(&Configuration{}) - err := unit.SetPostServeAction("test-callback", "script", "dummy script", 1800) + err := unit.SetLocalPostServeAction("test-callback", "script", "dummy script", 1800) Expect(err).To(BeNil()) Expect(unit.PostServeActionDetails.Actions).NotTo(BeNil()) @@ -1389,13 +1394,44 @@ func TestHoverfly_SetPostServeAction(t *testing.T) { Expect(unit.PostServeActionDetails.Actions["test-callback"].DelayInMs).To(Equal(1800)) } -func TestHoverfly_DeletePostServeAction(t *testing.T) { +func TestHoverfly_SetRemotePostServeAction(t *testing.T) { RegisterTestingT(t) unit := NewHoverflyWithConfiguration(&Configuration{}) - err := unit.SetPostServeAction("test-callback", "script", "dummy script", 1800) + err := unit.SetRemotePostServeAction("test-callback", "http://localhost:8080", 1800) + + Expect(err).To(BeNil()) + Expect(unit.PostServeActionDetails.Actions).NotTo(BeNil()) + Expect(unit.PostServeActionDetails.Actions).To(HaveLen(1)) + Expect(unit.PostServeActionDetails.Actions["test-callback"].Remote).To(Equal("http://localhost:8080")) + Expect(unit.PostServeActionDetails.Actions["test-callback"].DelayInMs).To(Equal(1800)) +} + +func TestHoverfly_DeleteLocalPostServeAction(t *testing.T) { + + RegisterTestingT(t) + + unit := NewHoverflyWithConfiguration(&Configuration{}) + + err := unit.SetLocalPostServeAction("test-callback", "script", "dummy script", 1800) + + Expect(err).To(BeNil()) + + err = unit.DeletePostServeAction("test-callback") + + Expect(err).To(BeNil()) + Expect(unit.PostServeActionDetails.Actions).To(HaveLen(0)) +} + +func TestHoverfly_DeleteRemotePostServeAction(t *testing.T) { + + RegisterTestingT(t) + + unit := NewHoverflyWithConfiguration(&Configuration{}) + + err := unit.SetRemotePostServeAction("test-callback", "http://localhost", 1800) Expect(err).To(BeNil()) diff --git a/functional-tests/core/ft_postserveaction_test.go b/functional-tests/core/ft_postserveaction_test.go index ab4594c9e..d0d11c7ed 100644 --- a/functional-tests/core/ft_postserveaction_test.go +++ b/functional-tests/core/ft_postserveaction_test.go @@ -2,14 +2,12 @@ package hoverfly_test import ( "encoding/json" - "fmt" - "io/ioutil" - v2 "github.com/SpectoLabs/hoverfly/core/handlers/v2" functional_tests "github.com/SpectoLabs/hoverfly/functional-tests" "github.com/SpectoLabs/hoverfly/functional-tests/testdata" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "io/ioutil" ) var _ = Describe("Manage post serve actions in hoverfly", func() { @@ -18,6 +16,8 @@ var _ = Describe("Manage post serve actions in hoverfly", func() { hoverfly *functional_tests.Hoverfly ) + //var server *httptest.Server + BeforeEach(func() { hoverfly = functional_tests.NewHoverfly() }) @@ -28,7 +28,7 @@ var _ = Describe("Manage post serve actions in hoverfly", func() { Context("get post serve action", func() { - Context("hoverfly with post-serve-action", func() { + Context("hoverfly with local post-serve-action", func() { BeforeEach(func() { hoverfly.Start("-post-serve-action", "test-callback python testdata/middleware.py 1300") @@ -43,9 +43,25 @@ var _ = Describe("Manage post serve actions in hoverfly", func() { Expect(postServeActionDetails.Actions[0].DelayInMs).To(Equal(1300)) }) }) + + Context("hoverfly with remote post-serve-action", func() { + + BeforeEach(func() { + hoverfly.Start("-post-serve-action", "test-callback http://localhost:8080 1300") + }) + + It("Should return post serve action details", func() { + postServeActionDetails := hoverfly.GetAllPostServeAction() + Expect(postServeActionDetails).NotTo(BeNil()) + Expect(postServeActionDetails.Actions).To(HaveLen(1)) + Expect(postServeActionDetails.Actions[0].ActionName).To(Equal("test-callback")) + Expect(postServeActionDetails.Actions[0].Remote).To(Equal("http://localhost:8080")) + Expect(postServeActionDetails.Actions[0].DelayInMs).To(Equal(1300)) + }) + }) }) - Context("set post serve action", func() { + Context("set local post serve action", func() { Context("start hoverfly and set post serve action", func() { @@ -58,7 +74,7 @@ var _ = Describe("Manage post serve actions in hoverfly", func() { }) It("Should set post serve action", func() { - postServeActionDetails := hoverfly.SetPostServeAction("testing", "python3", "dummy-script", 1400) + postServeActionDetails := hoverfly.SetLocalPostServeAction("testing", "python3", "dummy-script", 1400) Expect(postServeActionDetails).NotTo(BeNil()) Expect(postServeActionDetails.Actions).To(HaveLen(1)) Expect(postServeActionDetails.Actions[0].ActionName).To(Equal("testing")) @@ -67,11 +83,31 @@ var _ = Describe("Manage post serve actions in hoverfly", func() { Expect(postServeActionDetails.Actions[0].DelayInMs).To(Equal(1400)) }) }) + + Context("start hoverfly and set remote post serve action", func() { + + BeforeEach(func() { + hoverfly.Start() + }) + + AfterEach(func() { + hoverfly.Stop() + }) + + It("Should set post serve action", func() { + postServeActionDetails := hoverfly.SetRemotePostServeAction("testing", "http://localhost", 1400) + Expect(postServeActionDetails).NotTo(BeNil()) + Expect(postServeActionDetails.Actions).To(HaveLen(1)) + Expect(postServeActionDetails.Actions[0].ActionName).To(Equal("testing")) + Expect(postServeActionDetails.Actions[0].Remote).To(Equal("http://localhost")) + Expect(postServeActionDetails.Actions[0].DelayInMs).To(Equal(1400)) + }) + }) }) Context("delete post serve action", func() { - Context("start post serve acton and delete it", func() { + Context("start local post serve acton and delete it", func() { BeforeEach(func() { hoverfly.Start("-post-serve-action", "test-callback python testdata/middleware.py 1300") @@ -83,11 +119,24 @@ var _ = Describe("Manage post serve actions in hoverfly", func() { Expect(postServeActionDetails.Actions).To(HaveLen(0)) }) }) + + Context("start remote post serve acton and delete it", func() { + + BeforeEach(func() { + hoverfly.Start("-post-serve-action", "test-callback http://localhost:8080 1300") + }) + + It("Should return empty post serve action details on deletion", func() { + postServeActionDetails := hoverfly.DeletePostServeAction("test-callback") + Expect(postServeActionDetails).NotTo(BeNil()) + Expect(postServeActionDetails.Actions).To(HaveLen(0)) + }) + }) }) Context("set post serve action in simulation", func() { - Context("start hoverfly with post serve action and set in simulation", func() { + Context("start hoverfly with local post serve action and set in simulation", func() { BeforeEach(func() { hoverfly.Start("-post-serve-action", "test-callback python testdata/middleware.py 1300") @@ -106,7 +155,33 @@ var _ = Describe("Manage post serve actions in hoverfly", func() { simulationView := &v2.SimulationViewV5{} err = json.Unmarshal(simulationBytes, simulationView) - fmt.Println(simulationView) + Expect(err).To(BeNil()) + Expect(simulationView).NotTo(BeNil()) + Expect(simulationView.DataViewV5.RequestResponsePairs).To(HaveLen(1)) + Expect(simulationView.DataViewV5.RequestResponsePairs[0].Response.PostServeAction).To(Equal("test-callback")) + + }) + }) + + Context("start hoverfly with remote post serve action and set in simulation", func() { + + BeforeEach(func() { + hoverfly.Start("-post-serve-action", "test-callback http://localhost:8080 1300") + }) + + AfterEach(func() { + hoverfly.Stop() + }) + + It("Should be able to set post-serve-action in simulation", func() { + hoverfly.ImportSimulation(testdata.SimulationWithPostServeAction) + body := hoverfly.GetSimulation() + + simulationBytes, err := ioutil.ReadAll(body) + Expect(err).To(BeNil()) + + simulationView := &v2.SimulationViewV5{} + err = json.Unmarshal(simulationBytes, simulationView) Expect(err).To(BeNil()) Expect(simulationView).NotTo(BeNil()) Expect(simulationView.DataViewV5.RequestResponsePairs).To(HaveLen(1)) diff --git a/functional-tests/functional_tests.go b/functional-tests/functional_tests.go index 442b0a86c..4b6386f3f 100644 --- a/functional-tests/functional_tests.go +++ b/functional-tests/functional_tests.go @@ -136,7 +136,7 @@ func (this Hoverfly) GetAllPostServeAction() *v2.PostServeActionDetailsView { return PostServeActionDetailsView } -func (this Hoverfly) SetPostServeAction(actionName, binary, scriptContent string, delayInMs int) *v2.PostServeActionDetailsView { +func (this Hoverfly) SetLocalPostServeAction(actionName, binary, scriptContent string, delayInMs int) *v2.PostServeActionDetailsView { actionView := v2.ActionView{ActionName: actionName, Binary: binary, DelayInMs: delayInMs, ScriptContent: scriptContent} resp := DoRequest(sling.New().Put(fmt.Sprintf("http://localhost:%v/api/v2/hoverfly/post-serve-action", this.adminPort)).BodyJSON(actionView)) @@ -151,6 +151,21 @@ func (this Hoverfly) SetPostServeAction(actionName, binary, scriptContent string return PostServeActionDetailsView } +func (this Hoverfly) SetRemotePostServeAction(actionName, remote string, delayInMs int) *v2.PostServeActionDetailsView { + actionView := v2.ActionView{ActionName: actionName, DelayInMs: delayInMs, Remote: remote} + + resp := DoRequest(sling.New().Put(fmt.Sprintf("http://localhost:%v/api/v2/hoverfly/post-serve-action", this.adminPort)).BodyJSON(actionView)) + + PostServeActionDetailsView := &v2.PostServeActionDetailsView{} + body, err := ioutil.ReadAll(resp.Body) + Expect(err).To(BeNil()) + + err = json.Unmarshal(body, PostServeActionDetailsView) + Expect(err).To(BeNil()) + + return PostServeActionDetailsView +} + func (this Hoverfly) DeletePostServeAction(actionName string) *v2.PostServeActionDetailsView { resp := DoRequest(sling.New().Delete(fmt.Sprintf("http://localhost:%v/api/v2/hoverfly/post-serve-action/%v", this.adminPort, actionName))) diff --git a/functional-tests/hoverctl/postserveaction_test.go b/functional-tests/hoverctl/postserveaction_test.go index f6a3fd809..188bcdef7 100644 --- a/functional-tests/hoverctl/postserveaction_test.go +++ b/functional-tests/hoverctl/postserveaction_test.go @@ -24,12 +24,18 @@ var _ = Describe("When I use hoverctl", func() { hoverfly.Stop() }) - It("should return success on setting post-serve-action", func() { + It("should return success on setting local post-serve-action", func() { output := functional_tests.Run(hoverctlBinary, "post-serve-action", "set", "--binary", "python", "--script", "testdata/add_random_delay.py", "--delay", "1500", "--name", "test-callback") Expect(output).To(ContainSubstring("Success")) }) + It("should return success on setting remote post-serve-action", func() { + output := functional_tests.Run(hoverctlBinary, "post-serve-action", "set", "--remote", "http://localhost", "--delay", "1500", "--name", "test-callback") + + Expect(output).To(ContainSubstring("Success")) + }) + }) Describe("delete post-serve-action", func() { @@ -50,13 +56,20 @@ var _ = Describe("When I use hoverctl", func() { Expect(output).To(ContainSubstring("invalid action name passed")) }) - It("should return success on deleting post-serve-action after setting it", func() { + It("should return success on deleting local post-serve-action after setting it", func() { output := functional_tests.Run(hoverctlBinary, "post-serve-action", "set", "--binary", "python", "--script", "testdata/add_random_delay.py", "--delay", "1500", "--name", "test-callback") Expect(output).To(ContainSubstring("Success")) output = functional_tests.Run(hoverctlBinary, "post-serve-action", "delete", "--name", "test-callback") Expect(output).To(ContainSubstring("Success")) }) + It("should return success on deleting remote post-serve-action after setting it", func() { + output := functional_tests.Run(hoverctlBinary, "post-serve-action", "set", "--remote", "http://localhost:8080", "--delay", "1500", "--name", "test-callback") + Expect(output).To(ContainSubstring("Success")) + output = functional_tests.Run(hoverctlBinary, "post-serve-action", "delete", "--name", "test-callback") + Expect(output).To(ContainSubstring("Success")) + }) + }) Describe("get post-serve-action", func() { @@ -71,7 +84,7 @@ var _ = Describe("When I use hoverctl", func() { hoverfly.Stop() }) - It("should return post-serve-action", func() { + It("should return local post-serve-action", func() { output := functional_tests.Run(hoverctlBinary, "post-serve-action", "set", "--binary", "python", "--script", "testdata/add_random_delay.py", "--delay", "1300", "--name", "test-callback") Expect(output).To(ContainSubstring("Success")) @@ -82,5 +95,16 @@ var _ = Describe("When I use hoverctl", func() { Expect(output).To(ContainSubstring("1300")) }) + It("should return remote post-serve-action", func() { + output := functional_tests.Run(hoverctlBinary, "post-serve-action", "set", "--remote", "http://localhost", "--delay", "1700", "--name", "test-callback") + + Expect(output).To(ContainSubstring("Success")) + + output = functional_tests.Run(hoverctlBinary, "post-serve-action", "get-all") + Expect(output).To(ContainSubstring("test-callback")) + Expect(output).To(ContainSubstring("http://localhost")) + Expect(output).To(ContainSubstring("1700")) + }) + }) }) diff --git a/hoverctl/cmd/postserveaction.go b/hoverctl/cmd/postserveaction.go index d243bec71..a66d8dfd0 100644 --- a/hoverctl/cmd/postserveaction.go +++ b/hoverctl/cmd/postserveaction.go @@ -9,7 +9,7 @@ import ( "github.com/spf13/cobra" ) -var binary, scriptPath, actionNameToBeSet, actionNameToBeDeleted string +var binary, scriptPath, actionNameToBeSet, actionNameToBeDeleted, remote string var delayInMs int var postServeActionCommand = &cobra.Command{ @@ -30,7 +30,9 @@ var postServeActionGetCommand = &cobra.Command{ if len(args) == 0 { postServeActions, err := wrapper.GetAllPostServeActions(*target) handleIfError(err) - drawTable(getPostServeActionsTabularData(postServeActions), true) + localPostServeActionData, remotePostServeActionData := getPostServeActionsTabularData(postServeActions) + drawTable(localPostServeActionData, true) + drawTable(remotePostServeActionData, true) } }, } @@ -40,18 +42,22 @@ var postServeActionSetCommand = &cobra.Command{ Short: "Set postServeAction for Hoverfly", Long: ` Hoverfly PostServeAction can be set using the following flags: - --name --binary --script --delay + --name --binary --script --delay --remote `, Run: func(cmd *cobra.Command, args []string) { checkTargetAndExit(target) - if binary == "" || scriptPath == "" || actionNameToBeSet == "" { - fmt.Println("Binary, script path and action name are compulsory to set post serve action") - } else { + if remote != "" && actionNameToBeSet != "" { + err := wrapper.SetRemotePostServeAction(actionNameToBeSet, remote, delayInMs, *target) + handleIfError(err) + fmt.Println("Success") + } else if binary != "" && scriptPath != "" && actionNameToBeSet != "" { script, err := configuration.ReadFile(scriptPath) handleIfError(err) - err = wrapper.SetPostServeAction(actionNameToBeSet, binary, string(script), delayInMs, *target) + err = wrapper.SetLocalPostServeAction(actionNameToBeSet, binary, string(script), delayInMs, *target) handleIfError(err) fmt.Println("Success") + } else { + fmt.Println("(Binary and script path/remote) and action name are compulsory to set post serve action") } }, } @@ -87,17 +93,23 @@ func init() { postServeActionSetCommand.PersistentFlags().StringVar(&scriptPath, "script", "", "An absolute or relative path to a script that will be executed by the binary") postServeActionSetCommand.PersistentFlags().IntVar(&delayInMs, "delay", 0, "Delay in milli seconds after which action needs to be executed") + postServeActionSetCommand.PersistentFlags().StringVar(&remote, "remote", "", "Remote host to be set for triggering post serve action") postServeActionDeleteCommand.PersistentFlags().StringVar(&actionNameToBeDeleted, "name", "", "Action Name to be deleted") } -func getPostServeActionsTabularData(postServeActions v2.PostServeActionDetailsView) [][]string { - - postServeActionsData := [][]string{{"Action Name", "Binary", "Script", "Delay(Ms)"}} +func getPostServeActionsTabularData(postServeActions v2.PostServeActionDetailsView) ([][]string, [][]string) { + localPostServeActionsData := [][]string{{"Action Name", "Binary", "Script", "Delay(Ms)"}} + remotePostServeActionData := [][]string{{"Action Name", "Remote", "Delay(Ms)"}} for _, action := range postServeActions.Actions { - actionData := []string{action.ActionName, action.Binary, getContentShorthand(action.ScriptContent), fmt.Sprint(action.DelayInMs)} - postServeActionsData = append(postServeActionsData, actionData) + if action.Remote == "" { + actionData := []string{action.ActionName, action.Binary, getContentShorthand(action.ScriptContent), fmt.Sprint(action.DelayInMs)} + localPostServeActionsData = append(localPostServeActionsData, actionData) + } else { + actionData := []string{action.ActionName, action.Remote, fmt.Sprint(action.DelayInMs)} + remotePostServeActionData = append(remotePostServeActionData, actionData) + } } - return postServeActionsData + return localPostServeActionsData, remotePostServeActionData } diff --git a/hoverctl/wrapper/postserveaction.go b/hoverctl/wrapper/postserveaction.go index ffce1405b..75e8d9d0b 100644 --- a/hoverctl/wrapper/postserveaction.go +++ b/hoverctl/wrapper/postserveaction.go @@ -31,7 +31,7 @@ func GetAllPostServeActions(target configuration.Target) (v2.PostServeActionDeta return postServeActionDetailsView, nil } -func SetPostServeAction(actionName, binary, scriptContent string, delayInMs int, target configuration.Target) error { +func SetLocalPostServeAction(actionName, binary, scriptContent string, delayInMs int, target configuration.Target) error { actionRequest := v2.ActionView{ ActionName: actionName, @@ -51,7 +51,33 @@ func SetPostServeAction(actionName, binary, scriptContent string, delayInMs int, defer response.Body.Close() - err = handleResponseError(response, "Could not set post serve action") + err = handleResponseError(response, "Could not set local post serve action") + if err != nil { + return err + } + return nil +} + +func SetRemotePostServeAction(actionName, remote string, delayInMs int, target configuration.Target) error { + + actionRequest := v2.ActionView{ + ActionName: actionName, + Remote: remote, + DelayInMs: delayInMs, + } + marshalledAction, err := json.Marshal(actionRequest) + if err != nil { + return err + } + + response, err := doRequest(target, "PUT", v2ApiPostServeAction, string(marshalledAction), nil) + if err != nil { + return err + } + + defer response.Body.Close() + + err = handleResponseError(response, "Could not set remote post serve action") if err != nil { return err } From 40e9252404e0305fd2063d71de7d6e230a5f7a3b Mon Sep 17 00:00:00 2001 From: kapishmalik Date: Thu, 4 Apr 2024 02:41:36 +0530 Subject: [PATCH 2/5] run codeql on latest version --- .github/workflows/codeql.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 17a53af07..db19c9cee 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -42,7 +42,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -53,7 +53,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # ℹī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -67,4 +67,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 From 3cbdfeb04701097b40216db98691ca94604548b0 Mon Sep 17 00:00:00 2001 From: kapishmalik Date: Thu, 4 Apr 2024 02:45:31 +0530 Subject: [PATCH 3/5] installing go as per version in go.mod --- .github/workflows/codeql.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index db19c9cee..4ee3e3747 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -40,9 +40,14 @@ jobs: - name: Checkout repository uses: actions/checkout@v2 + - name: Install Go + uses: actions/setup-go@v4 + with: + go-version-file: go.mod + # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -53,7 +58,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v2 # ℹī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -67,4 +72,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v2 From a63fb91c92a845f07a454170e6292769ca670738 Mon Sep 17 00:00:00 2001 From: kapishmalik Date: Fri, 5 Apr 2024 12:23:21 +0530 Subject: [PATCH 4/5] documentation for remote postserve action --- docs/pages/keyconcepts/postserveaction.rst | 8 +++++--- docs/pages/reference/hoverfly/hoverfly.output | 4 +++- examples/postserveaction/README.md | 18 +++++++++++++++++- hoverctl/cmd/postserveaction.go | 6 ++++-- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/docs/pages/keyconcepts/postserveaction.rst b/docs/pages/keyconcepts/postserveaction.rst index 69449888f..7b0c8acff 100644 --- a/docs/pages/keyconcepts/postserveaction.rst +++ b/docs/pages/keyconcepts/postserveaction.rst @@ -6,13 +6,15 @@ Post Serve Action Overview -------- -- PostServeAction allows you to execute custom code after a response has been served in simulate or spy mode. +- PostServeAction allows you to execute custom code or invoke endpoint with request-response pair after a response has been served in simulate or spy mode. -- It is custom script that can be written in any language. Hoverfly has the ability to invoke a script or binary file on a host operating system. Custom code is execute after a provided delay(in ms) once simulated response is served. +- It is custom script that can be written in any language. Hoverfly has the ability to invoke a script or binary file on a host operating system or on the remote host. Custom code is executed/remote host is invoked after a provided delay(in ms) once simulated response is served. - We can register multiple post serve actions. -- In order to register post serve action, it takes mainly four parameters - binary to invoke script, script content/location, delay(in ms) post which it will be executed and name of that action. +- In order to register local post serve action, it takes mainly four parameters - binary to invoke script, script content/location, delay(in ms) post which it will be executed and name of that action. + +- In order to register remote post serve action, it takes mainly three parameters - remote host to be invoked with request-response pair, delay(in ms) post which it will be executed and name of that action. Ways to register a Post Serve Action ------------------------------------ diff --git a/docs/pages/reference/hoverfly/hoverfly.output b/docs/pages/reference/hoverfly/hoverfly.output index 2080237cd..92c7dc1dc 100644 --- a/docs/pages/reference/hoverfly/hoverfly.output +++ b/docs/pages/reference/hoverfly/hoverfly.output @@ -73,8 +73,10 @@ Usage of hoverfly: -metrics Enable metrics logging to stdout -post-serve-action string - Set post serve action by passing the action name, binary and the path of the action script and delay in Ms separated by space. + Set local post serve action by passing the action name, binary and the path of the action script and delay in Ms separated by space. (i.e. -post-serve-action " python3