Skip to content
This repository has been archived by the owner on Dec 21, 2023. It is now read-only.

Commit

Permalink
feat(webhook-service): Added unmarshalling of curl responses (#8782)
Browse files Browse the repository at this point in the history
  • Loading branch information
RealAnna committed Aug 31, 2022
1 parent f8e89d4 commit db8778e
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 4 deletions.
1 change: 1 addition & 0 deletions webhook-service/deploy/service.yaml
Expand Up @@ -26,6 +26,7 @@ spec:
app.kubernetes.io/version: develop
helm.sh/chart: keptn-0.1.0
spec:
serviceAccountName: keptn-webhook-service
containers:
- name: webhook-service
image: 'docker.io/keptndev/webhook-service'
Expand Down
28 changes: 24 additions & 4 deletions webhook-service/handler/handler.go
@@ -1,6 +1,7 @@
package handler

import (
"encoding/json"
"errors"
"fmt"
"net/url"
Expand Down Expand Up @@ -55,8 +56,6 @@ func (th *TaskHandler) Execute(keptnHandler sdk.IKeptn, event sdk.KeptnEvent) (i
return nil, sdkError(err.Error(), err)
}

responses := []string{}

if sdkErr := th.onStartedWebhookExecution(keptnHandler, event, webhook); sdkErr != nil {
return nil, sdkErr
}
Expand All @@ -69,6 +68,8 @@ func (th *TaskHandler) Execute(keptnHandler sdk.IKeptn, event sdk.KeptnEvent) (i
return nil, sdkError(removeSecretsFromMessage(err.Error(), secretEnvVars), err)
}
eventAdapter.Add("env", secretEnvVars)

responses := []interface{}{}
responses, err = th.performWebhookRequests(*webhook, eventAdapter, responses)
if err != nil {
onError(err, secretEnvVars)
Expand Down Expand Up @@ -200,7 +201,8 @@ func (th *TaskHandler) onStartedWebhookExecution(keptnHandler sdk.IKeptn, event
return nil
}

func (th *TaskHandler) performWebhookRequests(webhook lib.Webhook, eventAdapter *lib.EventDataAdapter, responses []string) ([]string, error) {
func (th *TaskHandler) performWebhookRequests(webhook lib.Webhook, eventAdapter *lib.EventDataAdapter, responses []interface{}) ([]interface{}, error) {

executedRequests := 0
logger.Infof("executing webhooks for subscriptionID %s", webhook.SubscriptionID)
for _, req := range webhook.Requests {
Expand All @@ -220,11 +222,29 @@ func (th *TaskHandler) performWebhookRequests(webhook lib.Webhook, eventAdapter
return nil, lib.NewWebhookExecutionError(true, fmt.Errorf("could not execute request '%s': %s", request, err.Error()), lib.WithNrOfExecutedRequests(executedRequests))
}
executedRequests = executedRequests + 1
responses = append(responses, response)

data := UnmarshalResponse(response)
responses = append(responses, data)
}
return responses, nil
}

//UnmarshalResponse attempts to create a json object out of the response as requested in https://github.com/keptn/keptn/issues/8256
func UnmarshalResponse(response string) interface{} {
dat := map[string]interface{}{}

resp := []byte(response)

err := json.Unmarshal(resp, &dat)
if err != nil {
logger.Debugf("Webhook response is unmarshallable : %s, appending response as string", err.Error())
return response
}
logger.Debugf("Webhook response unmarshalled! %+v", dat)
return dat

}

func (th *TaskHandler) gatherSecretEnvVars(webhook lib.Webhook) (map[string]string, error) {
secretEnvVars := map[string]string{}
for _, secretRef := range webhook.EnvFrom {
Expand Down
122 changes: 122 additions & 0 deletions webhook-service/handler/handler_test.go
Expand Up @@ -698,6 +698,97 @@ func Test_HandleIncomingFinishedEventWithResultingError(t *testing.T) {

}

func Test_HandleIncomingFinishedEventWithJSON(t *testing.T) {

t.Run("Test_HandleIncomingFinishedEventWithJSON - BETA", func(t *testing.T) {

templateEngineMock := &fake.ITemplateEngineMock{ParseTemplateFunc: func(data interface{}, templateStr string) (string, error) {
tplE := &lib.TemplateEngine{}
return tplE.ParseTemplate(data, templateStr)
}}

secretReaderMock := &fake.ISecretReaderMock{}
secretReaderMock.ReadSecretFunc = func(name string, key string) (string, error) {
return "my-secret-value", nil
}

validJSON := true

curlExecutorMock := &fake.ICurlExecutorMock{}
curlExecutorMock.CurlFunc = func(curlCmd string) (string, error) {
if validJSON {
return "{\"page\": \"1\", \"fruits\": [\"apple\", \"peach\"]}", nil
}
return "some strange output", nil
}

requestValidatorMock := &fake.RequestValidatorMock{ValidateFunc: func(request lib.Request) error {
return nil
}}

taskHandler := handler.NewTaskHandler(templateEngineMock, curlExecutorMock, requestValidatorMock, secretReaderMock)

fakeKeptn := sdk.NewFakeKeptn("test-webhook-svc")
fakeKeptn.SetResourceHandler(sdk.StringResourceHandler{ResourceContent: webHookContent1_BETA})
fakeKeptn.AddTaskHandlerWithSubscriptionID("sh.keptn.event.webhook.triggered", taskHandler, "my-subscription-id")
fakeKeptn.SetAutomaticResponse(false)

fakeKeptn.NewEvent(newWebhookTriggeredEvent("test/events/test-webhook.triggered-0.json"))
require.Eventually(t, func() bool { return len(curlExecutorMock.CurlCalls()) == 1 }, 30*time.Second, time.Millisecond*10)
require.Eventually(t, func() bool {
return curlExecutorMock.CurlCalls()[0].CurlCmd == "curl --request GET http://local:8080 myproject my-secret-value"
}, 30*time.Second, time.Millisecond*10)

//verify sent events
fakeKeptn.AssertNumberOfEventSent(t, 2)
fakeKeptn.AssertSentEventType(t, 0, keptnv2.GetStartedEventType("webhook"))
fakeKeptn.AssertSentEventType(t, 1, keptnv2.GetFinishedEventType("webhook"))
fakeKeptn.AssertSentEventStatus(t, 1, keptnv2.StatusSucceeded)
fakeKeptn.AssertSentEventResult(t, 1, keptnv2.ResultPass)

result := map[string]interface{}{
"labels": interface{}(nil), "project": "myproject", "result": "pass", "service": "myservice", "stage": "mystage", "status": "succeeded",
"webhook": map[string]interface{}{
"responses": []interface{}{
map[string]interface{}{
"page": "1",
"fruits": []interface{}{"apple", "peach"},
},
},
},
}
require.Equal(t, result, fakeKeptn.SentEvents[1].Data)

// now set wrong json format
validJSON = false
fakeKeptn.NewEvent(newWebhookTriggeredEvent("test/events/test-webhook.triggered-0.json"))
require.Eventually(t, func() bool { return len(curlExecutorMock.CurlCalls()) == 2 }, 30*time.Second, time.Millisecond*10)
require.Eventually(t, func() bool {
return curlExecutorMock.CurlCalls()[0].CurlCmd == "curl --request GET http://local:8080 myproject my-secret-value"
}, 30*time.Second, time.Millisecond*10)

//verify sent events
fakeKeptn.AssertNumberOfEventSent(t, 4)
fakeKeptn.AssertSentEventType(t, 2, keptnv2.GetStartedEventType("webhook"))
fakeKeptn.AssertSentEventType(t, 3, keptnv2.GetFinishedEventType("webhook"))
fakeKeptn.AssertSentEventStatus(t, 3, keptnv2.StatusSucceeded)
fakeKeptn.AssertSentEventResult(t, 3, keptnv2.ResultPass)

result = map[string]interface{}{
"labels": interface{}(nil), "project": "myproject", "result": "pass", "service": "myservice", "stage": "mystage", "status": "succeeded",
"webhook": map[string]interface{}{
"responses": []interface{}{
"some strange output",
},
},
}
t.Logf("%+v vs %+v", result, fakeKeptn.SentEvents[3].Data)
require.Equal(t, result, fakeKeptn.SentEvents[3].Data)

})

}

func Test_HandleIncomingTriggeredEvent_SendMultipleRequests(t *testing.T) {

t.Run("Test_HandleIncomingTriggeredEvent_SendMultipleRequests - ALPHA", func(t *testing.T) {
Expand Down Expand Up @@ -2068,3 +2159,34 @@ func Test_createRequest(t *testing.T) {
})
}
}

func TestUnmarshalResponse(t *testing.T) {

tests := []struct {
name string
response string
want interface{}
}{
{
name: "invalid JSON",
response: "not a JSON",
want: "not a JSON",
},
{
name: "valid JSON",
response: "{\n \"contenttype\": \"application/json\",\n \"data\": {\n \"project\": \"keptn-webhooks-subscription-overlap\",\n \"service\": \"myservice\",\n \"stage\": \"dev\"\n }\n}",
want: map[string]interface{}{
"contenttype": "application/json",
"data": map[string]interface{}{
"project": "keptn-webhooks-subscription-overlap",
"service": "myservice",
"stage": "dev"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.want, handler.UnmarshalResponse(tt.response), "UnmarshalResponse(%v)", tt.response)
})
}
}

0 comments on commit db8778e

Please sign in to comment.