diff --git a/api/event-source.html b/api/event-source.html
index bcbccbb5ee..d7bf5586bf 100644
--- a/api/event-source.html
+++ b/api/event-source.html
@@ -776,7 +776,7 @@
Gateway
type
-github.com/argoproj/argo-events/pkg/apis/common.EventSourceType
+Argo Events common.EventSourceType
|
@@ -292,7 +292,7 @@ GatewaySpec
type
-github.com/argoproj/argo-events/pkg/apis/common.EventSourceType
+Argo Events common.EventSourceType
|
@@ -647,5 +647,5 @@ Subscribers
Generated with gen-crd-api-reference-docs
-on git commit 9f73feb .
+on git commit 2bedd9d .
diff --git a/api/gateway.md b/api/gateway.md
index 77bf840d51..e8ce0ba13c 100644
--- a/api/gateway.md
+++ b/api/gateway.md
@@ -270,8 +270,7 @@ configurations for the gateway
|
-type
-github.com/argoproj/argo-events/pkg/apis/common.EventSourceType
+type Argo Events common.EventSourceType
|
@@ -578,8 +577,7 @@ configurations for the gateway
-type
-github.com/argoproj/argo-events/pkg/apis/common.EventSourceType
+type Argo Events common.EventSourceType
|
@@ -1277,6 +1275,6 @@ NATS refers to the subscribers over NATS protocol.
Generated with gen-crd-api-reference-docs on git
-commit 9f73feb .
+commit 2bedd9d .
diff --git a/api/sensor.html b/api/sensor.html
index 5580932119..c34ef13c46 100644
--- a/api/sensor.html
+++ b/api/sensor.html
@@ -224,7 +224,7 @@ ArtifactLocation
s3
-github.com/argoproj/argo-events/pkg/apis/common.S3Artifact
+Argo Events common.S3Artifact
|
@@ -550,13 +550,17 @@ CustomTrigger
-triggerBody
+spec
-string
+map[string]string
|
- TriggerBody is the custom trigger resource specification that custom trigger gRPC server knows how to interpret.
+Spec is the custom trigger resource specification that custom trigger gRPC server knows how to interpret.
+
+
+
|
@@ -3371,5 +3375,5 @@ URLArtifact
Generated with gen-crd-api-reference-docs
-on git commit 9f73feb .
+on git commit 2bedd9d .
diff --git a/api/sensor.md b/api/sensor.md
index d82977af9f..7dddc330a3 100644
--- a/api/sensor.md
+++ b/api/sensor.md
@@ -470,8 +470,7 @@ Description
-s3
-github.com/argoproj/argo-events/pkg/apis/common.S3Artifact
+s3 Argo Events common.S3Artifact
|
@@ -1132,7 +1131,7 @@ trigger gRPC server.
-triggerBody string
+spec map\[string\]string
|
@@ -1140,11 +1139,17 @@ trigger gRPC server.
-TriggerBody is the custom trigger resource specification that custom
-trigger gRPC server knows how to interpret.
+Spec is the custom trigger resource specification that custom trigger
+gRPC server knows how to interpret.
+
+
+
+
@@ -6699,6 +6704,6 @@ VerifyCert decides whether the connection is secure or not
Generated with gen-crd-api-reference-docs on git
-commit 9f73feb .
+commit 2bedd9d .
diff --git a/controllers/sensor/validate.go b/controllers/sensor/validate.go
index 04c8739819..54ebe139a4 100644
--- a/controllers/sensor/validate.go
+++ b/controllers/sensor/validate.go
@@ -392,7 +392,7 @@ func validateCustomTrigger(trigger *v1alpha1.CustomTrigger) error {
if trigger.ServerURL == "" {
return errors.New("custom trigger gRPC server url is not defined")
}
- if trigger.TriggerBody == "" {
+ if trigger.Spec == nil {
return errors.New("trigger body can't be empty")
}
if trigger.Secure {
diff --git a/docs/triggers/build-your-own-trigger.md b/docs/triggers/build-your-own-trigger.md
new file mode 100644
index 0000000000..772d5737b6
--- /dev/null
+++ b/docs/triggers/build-your-own-trigger.md
@@ -0,0 +1,112 @@
+# Build Your Own Trigger
+
+Argo Events supports a variety of triggers out of box like Argo Workflow, K8s Objects, AWS Lambda, HTTP Requests etc., but you may want to write your own logic to trigger a pipeline or create an object in K8s cluster. An example would be to trigger
+TektonCD or AirFlow pipelines on GitHub events.
+
+## Custom Trigger
+
+In order to plug your own implementation for a trigger with Argo Events Sensor, you need to
+run a gRPC server that implements the interface that the sensor expects.
+
+### Interface
+
+The interface exposed via proto file,
+
+ // Trigger offers services to build a custom trigger
+ service Trigger {
+ // FetchResource fetches the resource to be triggered.
+ rpc FetchResource(FetchResourceRequest) returns (FetchResourceResponse);
+ // Execute executes the requested trigger resource.
+ rpc Execute(ExecuteRequest) returns (ExecuteResponse);
+ // ApplyPolicy applies policies on the trigger execution result.
+ rpc ApplyPolicy(ApplyPolicyRequest) returns (ApplyPolicyResponse);
+ }
+
+The complete proto file is available [here](https://github.com/argoproj/argo-events/blob/master/sensors/triggers/trigger.proto).
+
+Let's walk through the contract,
+
+1. `FetchResource`: If the trigger server needs to fetch a resource from external sources like S3, Git or a URL, this is the
+ place to do so. e.g. if the trigger server aims to invoke a TektonCD pipeline and the `PipelineRun` resource lives on Git, then
+ trigger server can first fetch it from Git and return it back to sensor.
+
+2. `Execute`: In this method, the trigger server executes/invokes the trigger. e.g. TektonCD pipeline resource being
+ created in K8s cluster.
+
+3. `ApplyPolicy`: This is where your trigger implementation can check whether the triggered resource transitioned into the success state.
+ Depending upon the response from the trigger server, the sensor will either stop processing subsequent triggers, or it will continue to
+ process them.
+
+
+### How to define the Custom Trigger in a sensor?
+
+Let's look at the following sensor,
+
+ apiVersion: argoproj.io/v1alpha1
+ kind: Sensor
+ metadata:
+ name: webhook-sensor
+ labels:
+ sensors.argoproj.io/sensor-controller-instanceid: argo-events
+ spec:
+ template:
+ spec:
+ containers:
+ - name: sensor
+ image: metalgearsolid/sensor:v0.15.0
+ imagePullPolicy: Always
+ serviceAccountName: argo-events-sa
+ dependencies:
+ - name: test-dep
+ gatewayName: webhook-gateway
+ eventName: example
+ subscription:
+ http:
+ port: 9300
+ triggers:
+ - template:
+ name: webhook-workflow-trigger
+ custom:
+ # the url of the trigger server.
+ serverURL: tekton-trigger.argo-events.svc:9000
+ # spec is map of string->string and it is sent over to trigger server.
+ # the spec can be anything you want as per your use-case, just make sure the trigger server understands the spec map.
+ spec:
+ url: "https://raw.githubusercontent.com/VaibhavPage/tekton-cd-trigger/master/example.yaml"
+ # These parameters are applied on resource fetched and returned by the trigger server.
+ # e.g. consider a trigger server which invokes TektonCD pipeline runs, then
+ # the trigger server can return a TektonCD PipelineRun resource.
+ # The parameters are then applied on that PipelineRun resource.
+ parameters:
+ - src:
+ dependencyName: test-dep
+ dataKey: body.namespace
+ dest: metadata.namespace
+ # These parameters are applied on entire template body.
+ # So that you can parameterize anything under `custom` key such as `serverURL`, `spec` etc.
+ parameters:
+ - src:
+ dependencyName: test-dep
+ dataKey: body.url
+ dest: custom.spec.url
+
+The sensor definition should look familiar to you. The only difference is the `custom` key under `triggers -> template`.
+The specification under `custom` key defines the custom trigger.
+
+The most important fields are,
+
+1. `serverURL`: This is the URL of the trigger gRPC server.
+
+1. `spec`: It is a map of string -> string. The spec can be anything you want as per your use-case. The sensor sends
+ the spec to trigger server, and it is upto the trigger gRPC server to interpret the spec.
+
+1. `parameters`: The parameters override the resource that is fetched by the trigger server.
+ Read more info on parameters [here](https://argoproj.github.io/argo-events/tutorials/02-parameterization/).
+
+1. `payload`: Payload to send to the trigger server. Read more on payload [here](https://argoproj.github.io/argo-events/triggers/http-trigger/#request-payload).
+
+The complete spec for the custom trigger is available [here](https://github.com/argoproj/argo-events/blob/master/api/sensor.md#customtrigger).
+
+## Custom Trigger in Action
+
+Refer to a sample [trigger server](https://github.com/VaibhavPage/tekton-cd-trigger) that invokes TektonCD pipeline on events.
diff --git a/examples/sensors/custom-trigger.yaml b/examples/sensors/custom-trigger.yaml
new file mode 100644
index 0000000000..135367eb11
--- /dev/null
+++ b/examples/sensors/custom-trigger.yaml
@@ -0,0 +1,47 @@
+apiVersion: argoproj.io/v1alpha1
+kind: Sensor
+metadata:
+ name: webhook-sensor
+ labels:
+ sensors.argoproj.io/sensor-controller-instanceid: argo-events
+spec:
+ template:
+ spec:
+ containers:
+ - name: sensor
+ image: metalgearsolid/sensor:v0.15.0
+ imagePullPolicy: Always
+ serviceAccountName: argo-events-sa
+ dependencies:
+ - name: test-dep
+ gatewayName: webhook-gateway
+ eventName: example
+ subscription:
+ http:
+ port: 9300
+ triggers:
+ - template:
+ name: webhook-workflow-trigger
+ custom:
+ # the url of the trigger server.
+ serverURL: tekton-trigger.argo-events.svc:9000
+ # spec is map of string->string and it is sent over to trigger server.
+ # the spec can be anything you want as per your use-case, just make sure the trigger server understands the spec map.
+ spec:
+ url: "https://raw.githubusercontent.com/VaibhavPage/tekton-cd-trigger/master/example.yaml"
+ # These parameters are applied on resource fetched and returned by the trigger server.
+ # e.g. consider a trigger server which invokes TektonCD pipeline runs, then
+ # the trigger server can return a TektonCD PipelineRun resource.
+ # The parameters are then applied on that PipelineRun resource.
+ parameters:
+ - src:
+ dependencyName: test-dep
+ dataKey: body.namespace
+ dest: metadata.namespace
+ # These parameters are applied on entire template body.
+ # So that you can parameterize anything under `custom` key such as `serverURL`, `spec` etc.
+ parameters:
+ - src:
+ dependencyName: test-dep
+ dataKey: body.url
+ dest: custom.spec.url
diff --git a/pkg/apis/sensor/v1alpha1/types.go b/pkg/apis/sensor/v1alpha1/types.go
index 99ae8b6b6a..32d869e09c 100644
--- a/pkg/apis/sensor/v1alpha1/types.go
+++ b/pkg/apis/sensor/v1alpha1/types.go
@@ -486,8 +486,8 @@ type CustomTrigger struct {
CertFilePath string `json:"certFilePath,omitempty" protobuf:"bytes,3,opt,name=certFilePath"`
// ServerNameOverride for the secure connection between sensor and custom trigger gRPC server.
ServerNameOverride string `json:"serverNameOverride,omitempty" protobuf:"bytes,4,opt,name=serverNameOverride"`
- // TriggerBody is the custom trigger resource specification that custom trigger gRPC server knows how to interpret.
- TriggerBody string `json:"triggerBody" protobuf:"bytes,5,name=triggerBody"`
+ // Spec is the custom trigger resource specification that custom trigger gRPC server knows how to interpret.
+ Spec map[string]string `json:"spec" protobuf:"bytes,5,name=spec"`
// Parameters is the list of parameters that is applied to resolved custom trigger trigger object.
// +listType=triggerParameters
Parameters []TriggerParameter `json:"parameters,omitempty" protobuf:"bytes,6,rep,name=parameters"`
diff --git a/pkg/apis/sensor/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/sensor/v1alpha1/zz_generated.deepcopy.go
index c7b360e20e..38ca1f6ac8 100644
--- a/pkg/apis/sensor/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/sensor/v1alpha1/zz_generated.deepcopy.go
@@ -211,6 +211,13 @@ func (in *ConfigmapArtifact) DeepCopy() *ConfigmapArtifact {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CustomTrigger) DeepCopyInto(out *CustomTrigger) {
*out = *in
+ if in.Spec != nil {
+ in, out := &in.Spec, &out.Spec
+ *out = make(map[string]string, len(*in))
+ for key, val := range *in {
+ (*out)[key] = val
+ }
+ }
if in.Parameters != nil {
in, out := &in.Parameters, &out.Parameters
*out = make([]TriggerParameter, len(*in))
diff --git a/sensors/triggers/custom-trigger/custom-trigger.go b/sensors/triggers/custom-trigger/custom-trigger.go
index d58ba00546..aac7f3b1ba 100644
--- a/sensors/triggers/custom-trigger/custom-trigger.go
+++ b/sensors/triggers/custom-trigger/custom-trigger.go
@@ -17,10 +17,12 @@ package customtrigger
import (
"context"
+ "encoding/json"
"github.com/argoproj/argo-events/common"
"github.com/argoproj/argo-events/pkg/apis/sensor/v1alpha1"
"github.com/argoproj/argo-events/sensors/triggers"
+ "github.com/ghodss/yaml"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"google.golang.org/grpc"
@@ -36,7 +38,7 @@ type CustomTrigger struct {
// Trigger definition
Trigger *v1alpha1.Trigger
// logger to log stuff
- Logger *logrus.Logger
+ Logger *logrus.Entry
// triggerClient is the gRPC client for the custom trigger server
triggerClient triggers.TriggerClient
}
@@ -46,19 +48,24 @@ func NewCustomTrigger(sensor *v1alpha1.Sensor, trigger *v1alpha1.Trigger, logger
customTrigger := &CustomTrigger{
Sensor: sensor,
Trigger: trigger,
- Logger: logger,
+ Logger: logger.WithField("trigger-name", trigger.Template.Name),
}
ct := trigger.Template.CustomTrigger
if conn, ok := customTriggerClients[trigger.Template.Name]; ok {
if conn.GetState() == connectivity.Ready {
+ logger.Infoln("trigger client connection is ready...")
customTrigger.triggerClient = triggers.NewTriggerClient(conn)
return customTrigger, nil
}
+
+ logger.Infoln("trigger client connection is closed, creating new one...")
delete(customTriggerClients, trigger.Template.Name)
}
+ logger.WithField("server-url", ct.ServerURL).Infoln("instantiating trigger client...")
+
opt := []grpc.DialOption{
grpc.WithBlock(),
grpc.WithInsecure(),
@@ -93,18 +100,29 @@ func NewCustomTrigger(sensor *v1alpha1.Sensor, trigger *v1alpha1.Trigger, logger
customTrigger.triggerClient = triggers.NewTriggerClient(conn)
customTriggerClients[trigger.Template.Name] = conn
+
+ logger.Infoln("successfully setup the trigger client...")
return customTrigger, nil
}
// FetchResource fetches the trigger resource from external source
func (ct *CustomTrigger) FetchResource() (interface{}, error) {
+ specBody, err := yaml.Marshal(ct.Trigger.Template.CustomTrigger.Spec)
+ if err != nil {
+ return nil, errors.Wrap(err, "failed to parse the custom trigger spec body")
+ }
+
+ ct.Logger.WithField("spec", string(specBody)).Debugln("trigger spec body")
+
resource, err := ct.triggerClient.FetchResource(context.Background(), &triggers.FetchResourceRequest{
- Resource: []byte(ct.Trigger.Template.CustomTrigger.TriggerBody),
+ Resource: specBody,
})
if err != nil {
return nil, errors.Wrapf(err, "failed to fetch the custom trigger resource for %s", ct.Trigger.Template.Name)
}
- return resource, nil
+
+ ct.Logger.WithField("resource", string(resource.Resource)).Debugln("fetched resource")
+ return resource.Resource, nil
}
// ApplyResourceParameters applies parameters to the trigger resource
@@ -116,11 +134,19 @@ func (ct *CustomTrigger) ApplyResourceParameters(sensor *v1alpha1.Sensor, resour
parameters := ct.Trigger.Template.CustomTrigger.Parameters
if parameters != nil && len(parameters) > 0 {
- resource, err := triggers.ApplyParams(obj, ct.Trigger.Template.CustomTrigger.Parameters, triggers.ExtractEvents(sensor, parameters))
+ // only JSON formatted resource body is eligible for parameters
+ var temp map[string]interface{}
+ if err := json.Unmarshal(obj, &temp); err != nil {
+ return nil, errors.Wrapf(err, "fetched resource body is not valid JSON for trigger %s", ct.Trigger.Template.Name)
+ }
+
+ result, err := triggers.ApplyParams(obj, ct.Trigger.Template.CustomTrigger.Parameters, triggers.ExtractEvents(sensor, parameters))
if err != nil {
return nil, errors.Wrapf(err, "failed to apply the parameters to the custom trigger resource for %s", ct.Trigger.Template.Name)
}
- return resource, nil
+
+ ct.Logger.WithField("resource", string(result)).Debugln("resource after parameterization")
+ return result, nil
}
return resource, nil
@@ -132,14 +158,23 @@ func (ct *CustomTrigger) Execute(resource interface{}) (interface{}, error) {
if !ok {
return nil, errors.New("failed to interpret the trigger resource for the execution")
}
+
+ ct.Logger.WithField("resource", string(obj)).Debugln("resource to execute")
+
trigger := ct.Trigger.Template.CustomTrigger
- if trigger.Payload == nil {
- return nil, errors.New("payload parameters are not specified")
- }
- payload, err := triggers.ConstructPayload(ct.Sensor, trigger.Payload)
- if err != nil {
- return nil, err
+
+ var payload []byte
+ var err error
+
+ if trigger.Payload != nil {
+ payload, err = triggers.ConstructPayload(ct.Sensor, trigger.Payload)
+ if err != nil {
+ return nil, err
+ }
+
+ ct.Logger.WithField("payload", string(payload)).Debugln("payload for the trigger execution")
}
+
result, err := ct.triggerClient.Execute(context.Background(), &triggers.ExecuteRequest{
Resource: obj,
Payload: payload,
@@ -147,7 +182,9 @@ func (ct *CustomTrigger) Execute(resource interface{}) (interface{}, error) {
if err != nil {
return nil, errors.Wrapf(err, "failed to execute the custom trigger resource for %s", ct.Trigger.Template.Name)
}
- return result, nil
+
+ ct.Logger.WithField("response", string(result.Response)).Debugln("trigger execution response")
+ return result.Response, nil
}
// ApplyPolicy applies the policy on the trigger
@@ -156,6 +193,9 @@ func (ct *CustomTrigger) ApplyPolicy(resource interface{}) error {
if !ok {
return errors.New("failed to interpret the trigger resource for the policy application")
}
+
+ ct.Logger.WithField("resource", string(obj)).Debugln("resource to apply policy on")
+
result, err := ct.triggerClient.ApplyPolicy(context.Background(), &triggers.ApplyPolicyRequest{
Request: obj,
})
| |