diff --git a/agent/workers/trigger/graphql.go b/agent/workers/trigger/graphql.go new file mode 100644 index 0000000000..80c9170fbe --- /dev/null +++ b/agent/workers/trigger/graphql.go @@ -0,0 +1,86 @@ +package trigger + +import ( + "context" + "fmt" +) + +func GRAPHQL(httpTriggerer Triggerer) Triggerer { + return &graphqlTriggerer{httpTriggerer} +} + +type graphqlTriggerer struct { + httpTriggerer Triggerer +} + +func (te *graphqlTriggerer) Trigger(ctx context.Context, triggerConfig Trigger, opts *Options) (Response, error) { + response := Response{ + Result: TriggerResult{ + Type: te.Type(), + }, + } + + if triggerConfig.Type != TriggerTypeGraphql { + return response, fmt.Errorf(`trigger type "%s" not supported by HTTP triggerer`, triggerConfig.Type) + } + + triggerConfig = mapGraphqlToHttp(triggerConfig) + + response, err := te.httpTriggerer.Trigger(ctx, triggerConfig, opts) + if err != nil { + return response, fmt.Errorf("error triggering Graphql trigger: %w", err) + } + + return mapHttpToGraphql(response), nil +} + +func (t *graphqlTriggerer) Type() TriggerType { + return TriggerTypeGraphql +} + +const TriggerTypeGraphql TriggerType = "graphql" + +func mapGraphqlToHttp(triggerConfig Trigger) Trigger { + return Trigger{ + Type: TriggerTypeHTTP, + HTTP: &HTTPRequest{ + URL: triggerConfig.Graphql.URL, + Method: HTTPMethodPOST, + Body: triggerConfig.Graphql.Body, + Headers: triggerConfig.Graphql.Headers, + SSLVerification: triggerConfig.Graphql.SSLVerification, + }, + } +} + +func mapHttpToGraphql(response Response) Response { + return Response{ + TraceID: response.TraceID, + SpanID: response.SpanID, + SpanAttributes: response.SpanAttributes, + Result: TriggerResult{ + Type: TriggerTypeGraphql, + Graphql: &GraphqlResponse{ + Status: response.Result.HTTP.Status, + StatusCode: response.Result.HTTP.StatusCode, + Headers: response.Result.HTTP.Headers, + Body: response.Result.HTTP.Body, + }, + }, + } +} + +type GraphqlRequest struct { + URL string `expr_enabled:"true" json:"url,omitempty"` + Body string `expr_enabled:"true" json:"body,omitempty"` + Headers []HTTPHeader `json:"headers,omitempty"` + Auth *HTTPAuthenticator `json:"auth,omitempty"` + SSLVerification bool `json:"sslVerification,omitempty"` +} + +type GraphqlResponse struct { + Status string + StatusCode int + Headers []HTTPHeader + Body string +} diff --git a/agent/workers/trigger/graphql_test.go b/agent/workers/trigger/graphql_test.go new file mode 100644 index 0000000000..3e1421f3d1 --- /dev/null +++ b/agent/workers/trigger/graphql_test.go @@ -0,0 +1,58 @@ +package trigger_test + +import ( + "io" + "net/http" + "net/http/httptest" + "testing" + + "github.com/kubeshop/tracetest/agent/workers/trigger" + triggerer "github.com/kubeshop/tracetest/agent/workers/trigger" + "github.com/stretchr/testify/assert" +) + +func TestGraphqlTrigger(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + t.Log(req.Header) + + tp, ok := req.Header["Traceparent"] + if !ok { + t.Fatalf("missing Traceparent header %#v", req.Header) + } + assert.Len(t, tp, 1) + + assert.Equal(t, "POST", req.Method) + assert.Equal(t, "Value1", req.Header.Get("Key1")) + + b, err := io.ReadAll(req.Body) + assert.NoError(t, err) + assert.Equal(t, `query { films { name } }`, string(b)) + + rw.Header().Add("Content-Type", "application/json") + rw.WriteHeader(200) + _, err = rw.Write([]byte(`{ "data": { "films": [{ "name": "A New Hope" }] } }`)) + assert.NoError(t, err) + })) + defer server.Close() + + triggerConfig := trigger.Trigger{ + Type: trigger.TriggerTypeGraphql, + Graphql: &trigger.GraphqlRequest{ + URL: server.URL, + Headers: []trigger.HTTPHeader{ + {Key: "Key1", Value: "Value1"}, + }, + Body: `query { films { name } }`, + }, + } + + httpTriggerer := triggerer.HTTP() + + ex := triggerer.GRAPHQL(httpTriggerer) + + resp, err := ex.Trigger(createContext(), triggerConfig, nil) + assert.NoError(t, err) + + assert.Equal(t, 200, resp.Result.Graphql.StatusCode) + assert.Equal(t, `{ "data": { "films": [{ "name": "A New Hope" }] } }`, resp.Result.Graphql.Body) +} diff --git a/api/graphql.yaml b/api/graphql.yaml new file mode 100644 index 0000000000..c8e9d8513f --- /dev/null +++ b/api/graphql.yaml @@ -0,0 +1,35 @@ +openapi: 3.0.0 +components: + schemas: + GraphqlRequest: + type: object + properties: + url: + type: string + headers: + type: array + items: + $ref: "./http.yaml#/components/schemas/HTTPHeader" + auth: + $ref: "./http.yaml#/components/schemas/HTTPAuth" + body: + type: string + sslVerification: + type: boolean + default: false + schema: + type: string + + GraphqlResponse: + type: object + properties: + status: + type: string + statusCode: + type: integer + headers: + type: array + items: + $ref: "#/components/schemas/HTTPHeader" + body: + type: string