diff --git a/app/controlplane/api/controlplane/v1/integrations.pb.go b/app/controlplane/api/controlplane/v1/integrations.pb.go index 917e048f2..1b79661fe 100644 --- a/app/controlplane/api/controlplane/v1/integrations.pb.go +++ b/app/controlplane/api/controlplane/v1/integrations.pb.go @@ -454,8 +454,6 @@ type PluginFanout struct { AttachmentSchema []byte `protobuf:"bytes,5,opt,name=attachment_schema,json=attachmentSchema,proto3" json:"attachment_schema,omitempty"` // List of materials that the integration is subscribed to SubscribedMaterials []string `protobuf:"bytes,6,rep,name=subscribed_materials,json=subscribedMaterials,proto3" json:"subscribed_materials,omitempty"` - // Whether the integration is subscribed to attestations - SubscribedAttestation bool `protobuf:"varint,7,opt,name=subscribed_attestation,json=subscribedAttestation,proto3" json:"subscribed_attestation,omitempty"` } func (x *PluginFanout) Reset() { @@ -511,13 +509,6 @@ func (x *PluginFanout) GetSubscribedMaterials() []string { return nil } -func (x *PluginFanout) GetSubscribedAttestation() bool { - if x != nil { - return x.SubscribedAttestation - } - return false -} - type IntegrationsServiceListRegistrationsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1193,7 +1184,7 @@ var file_controlplane_v1_integrations_proto_rawDesc = []byte{ 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x46, 0x61, 0x6e, 0x6f, 0x75, 0x74, 0x48, 0x00, 0x52, 0x06, 0x66, 0x61, 0x6e, 0x6f, 0x75, 0x74, 0x42, 0x06, 0x0a, 0x04, - 0x74, 0x79, 0x70, 0x65, 0x22, 0xd6, 0x01, 0x0a, 0x0c, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x46, + 0x74, 0x79, 0x70, 0x65, 0x22, 0x9f, 0x01, 0x0a, 0x0c, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x46, 0x61, 0x6e, 0x6f, 0x75, 0x74, 0x12, 0x2f, 0x0a, 0x13, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x12, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, @@ -1203,153 +1194,150 @@ var file_controlplane_v1_integrations_proto_rawDesc = []byte{ 0x65, 0x6d, 0x61, 0x12, 0x31, 0x0a, 0x14, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x5f, 0x6d, 0x61, 0x74, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x64, 0x4d, 0x61, 0x74, - 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, 0x12, 0x35, 0x0a, 0x16, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, - 0x69, 0x62, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, - 0x65, 0x64, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x2d, 0x0a, - 0x2b, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x72, 0x0a, 0x2c, + 0x65, 0x72, 0x69, 0x61, 0x6c, 0x73, 0x22, 0x2d, 0x0a, 0x2b, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x72, 0x0a, 0x2c, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, + 0x65, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x74, 0x65, + 0x6d, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x4a, 0x0a, 0x2e, 0x49, 0x6e, 0x74, + 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x02, 0x69, + 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0xb0, 0x01, + 0x01, 0x52, 0x02, 0x69, 0x64, 0x22, 0x75, 0x0a, 0x2f, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x73, 0x63, + 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, + 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x65, 0x72, 0x65, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x49, 0x74, 0x65, 0x6d, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0x3c, 0x0a, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x06, - 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, - 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x22, 0x4a, 0x0a, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x69, 0x63, 0x65, 0x44, 0x65, 0x74, 0x61, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x18, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, + 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x02, 0x69, 0x64, 0x22, 0x23, 0x0a, 0x21, 0x49, 0x6e, + 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x44, 0x65, 0x74, 0x61, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x39, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, + 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x6f, 0x72, + 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, + 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x49, 0x64, 0x22, 0x5d, 0x0a, 0x17, 0x4c, 0x69, + 0x73, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, + 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x74, 0x65, + 0x6d, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x22, 0xb4, 0x01, 0x0a, 0x19, 0x52, 0x65, + 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, + 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x22, 0x87, 0x02, 0x0a, 0x19, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x0e, + 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x39, + 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, + 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x12, 0x4c, 0x0a, 0x0b, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, + 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, + 0x72, 0x65, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x74, + 0x65, 0x6d, 0x52, 0x0b, 0x69, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x39, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x49, 0x74, 0x65, 0x6d, + 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x22, 0x40, 0x0a, 0x24, 0x49, 0x6e, + 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, - 0xfa, 0x42, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x02, 0x69, 0x64, 0x22, 0x75, 0x0a, 0x2f, + 0xfa, 0x42, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x02, 0x69, 0x64, 0x22, 0x27, 0x0a, 0x25, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x63, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x42, 0x0a, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x2a, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, - 0x31, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x49, 0x6e, 0x74, 0x65, - 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x06, 0x72, 0x65, 0x73, - 0x75, 0x6c, 0x74, 0x22, 0x3c, 0x0a, 0x20, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x74, 0x61, 0x63, 0x68, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, 0x02, 0x69, - 0x64, 0x22, 0x23, 0x0a, 0x21, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x74, 0x61, 0x63, 0x68, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x39, 0x0a, 0x16, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x74, - 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x49, - 0x64, 0x22, 0x5d, 0x0a, 0x17, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, - 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, 0x06, - 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x63, - 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, - 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, - 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x06, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x22, 0xb4, 0x01, 0x0a, 0x19, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x49, - 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x0e, - 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, - 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, - 0x6e, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x5f, - 0x61, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, - 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x87, 0x02, 0x0a, 0x19, 0x49, 0x6e, 0x74, 0x65, - 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, - 0x74, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x39, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, - 0x5f, 0x61, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, - 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x4c, 0x0a, 0x0b, 0x69, 0x6e, 0x74, 0x65, - 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, - 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, - 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x65, 0x64, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x0b, 0x69, 0x6e, 0x74, 0x65, 0x67, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, - 0x6f, 0x77, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, - 0x6c, 0x6f, 0x77, 0x49, 0x74, 0x65, 0x6d, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, - 0x77, 0x22, 0x40, 0x0a, 0x24, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, - 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x02, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x08, 0xfa, 0x42, 0x05, 0x72, 0x03, 0xb0, 0x01, 0x01, 0x52, - 0x02, 0x69, 0x64, 0x22, 0x27, 0x0a, 0x25, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x72, 0x65, 0x67, 0x69, - 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x87, 0x08, 0x0a, - 0x13, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x12, 0x84, 0x01, 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, - 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x38, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, - 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, - 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x39, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, - 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, - 0x62, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x75, 0x0a, 0x08, 0x52, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x33, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x63, - 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, - 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x7b, 0x0a, 0x0a, 0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, - 0x12, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x69, 0x63, 0x65, 0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x87, 0x08, 0x0a, 0x13, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x84, 0x01, + 0x0a, 0x0d, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x12, + 0x38, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, + 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, + 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x39, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, + 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4c, + 0x69, 0x73, 0x74, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x75, 0x0a, 0x08, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, + 0x12, 0x33, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x36, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, - 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x72, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x90, 0x01, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3c, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x34, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x3d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, - 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, - 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x99, 0x01, 0x0a, 0x14, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, - 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3f, 0x2e, 0x63, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, - 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x40, 0x2e, 0x63, - 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, - 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, - 0x0a, 0x06, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x12, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, - 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x74, - 0x74, 0x61, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x63, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, - 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x6f, 0x0a, 0x06, 0x44, 0x65, 0x74, 0x61, 0x63, 0x68, 0x12, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x65, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x7b, 0x0a, 0x0a, 0x44, + 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x12, 0x35, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, - 0x65, 0x74, 0x61, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x63, + 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x36, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, + 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x90, 0x01, 0x0a, 0x11, 0x4c, 0x69, 0x73, + 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x3c, + 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, + 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x3d, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x44, 0x65, 0x74, 0x61, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x64, 0x0a, 0x0f, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, - 0x6e, 0x74, 0x73, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, - 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x63, + 0x63, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x99, 0x01, 0x0a, 0x14, + 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3f, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, + 0x62, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x40, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x62, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x06, 0x41, 0x74, 0x74, 0x61, 0x63, + 0x68, 0x12, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, + 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, + 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6f, 0x0a, 0x06, 0x44, 0x65, 0x74, 0x61, + 0x63, 0x68, 0x12, 0x31, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x74, 0x61, 0x63, 0x68, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x32, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x44, 0x65, 0x74, 0x61, 0x63, + 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x64, 0x0a, 0x0f, 0x4c, 0x69, 0x73, + 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x27, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2d, 0x64, - 0x65, 0x76, 0x2f, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2f, 0x61, 0x70, 0x70, - 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x61, 0x70, - 0x69, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, - 0x31, 0x3b, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x28, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x70, + 0x6c, 0x61, 0x6e, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x41, 0x74, 0x74, 0x61, + 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, + 0x4c, 0x5a, 0x4a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x68, + 0x61, 0x69, 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2d, 0x64, 0x65, 0x76, 0x2f, 0x63, 0x68, 0x61, 0x69, + 0x6e, 0x6c, 0x6f, 0x6f, 0x70, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, + 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x72, + 0x6f, 0x6c, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x76, 0x31, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/app/controlplane/api/controlplane/v1/integrations.pb.validate.go b/app/controlplane/api/controlplane/v1/integrations.pb.validate.go index 17f183009..cb4077637 100644 --- a/app/controlplane/api/controlplane/v1/integrations.pb.validate.go +++ b/app/controlplane/api/controlplane/v1/integrations.pb.validate.go @@ -1067,8 +1067,6 @@ func (m *PluginFanout) validate(all bool) error { // no validation rules for AttachmentSchema - // no validation rules for SubscribedAttestation - if len(errors) > 0 { return PluginFanoutMultiError(errors) } diff --git a/app/controlplane/api/controlplane/v1/integrations.proto b/app/controlplane/api/controlplane/v1/integrations.proto index 985cc4dd4..3a9957378 100644 --- a/app/controlplane/api/controlplane/v1/integrations.proto +++ b/app/controlplane/api/controlplane/v1/integrations.proto @@ -97,8 +97,6 @@ message PluginFanout { bytes attachment_schema = 5; // List of materials that the integration is subscribed to repeated string subscribed_materials = 6; - // Whether the integration is subscribed to attestations - bool subscribed_attestation = 7; } message IntegrationsServiceListRegistrationsRequest{} diff --git a/app/controlplane/api/gen/frontend/controlplane/v1/integrations.ts b/app/controlplane/api/gen/frontend/controlplane/v1/integrations.ts index 891ed90bc..d70f67855 100644 --- a/app/controlplane/api/gen/frontend/controlplane/v1/integrations.ts +++ b/app/controlplane/api/gen/frontend/controlplane/v1/integrations.ts @@ -60,8 +60,6 @@ export interface PluginFanout { attachmentSchema: Uint8Array; /** List of materials that the integration is subscribed to */ subscribedMaterials: string[]; - /** Whether the integration is subscribed to attestations */ - subscribedAttestation: boolean; } export interface IntegrationsServiceListRegistrationsRequest { @@ -637,12 +635,7 @@ export const IntegrationAvailableItem = { }; function createBasePluginFanout(): PluginFanout { - return { - registrationSchema: new Uint8Array(), - attachmentSchema: new Uint8Array(), - subscribedMaterials: [], - subscribedAttestation: false, - }; + return { registrationSchema: new Uint8Array(), attachmentSchema: new Uint8Array(), subscribedMaterials: [] }; } export const PluginFanout = { @@ -656,9 +649,6 @@ export const PluginFanout = { for (const v of message.subscribedMaterials) { writer.uint32(50).string(v!); } - if (message.subscribedAttestation === true) { - writer.uint32(56).bool(message.subscribedAttestation); - } return writer; }, @@ -690,13 +680,6 @@ export const PluginFanout = { message.subscribedMaterials.push(reader.string()); continue; - case 7: - if (tag != 56) { - break; - } - - message.subscribedAttestation = reader.bool(); - continue; } if ((tag & 7) == 4 || tag == 0) { break; @@ -715,7 +698,6 @@ export const PluginFanout = { subscribedMaterials: Array.isArray(object?.subscribedMaterials) ? object.subscribedMaterials.map((e: any) => String(e)) : [], - subscribedAttestation: isSet(object.subscribedAttestation) ? Boolean(object.subscribedAttestation) : false, }; }, @@ -734,7 +716,6 @@ export const PluginFanout = { } else { obj.subscribedMaterials = []; } - message.subscribedAttestation !== undefined && (obj.subscribedAttestation = message.subscribedAttestation); return obj; }, @@ -747,7 +728,6 @@ export const PluginFanout = { message.registrationSchema = object.registrationSchema ?? new Uint8Array(); message.attachmentSchema = object.attachmentSchema ?? new Uint8Array(); message.subscribedMaterials = object.subscribedMaterials?.map((e) => e) || []; - message.subscribedAttestation = object.subscribedAttestation ?? false; return message; }, }; diff --git a/app/controlplane/internal/dispatcher/dispatcher.go b/app/controlplane/internal/dispatcher/dispatcher.go index 3a4f2c703..7b531a601 100644 --- a/app/controlplane/internal/dispatcher/dispatcher.go +++ b/app/controlplane/internal/dispatcher/dispatcher.go @@ -24,7 +24,6 @@ import ( "github.com/cenkalti/backoff/v4" - schemaapi "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" "github.com/chainloop-dev/chainloop/app/controlplane/internal/biz" "github.com/chainloop-dev/chainloop/app/controlplane/plugins/sdk/v1" "github.com/chainloop-dev/chainloop/internal/attestation/renderer/chainloop" @@ -48,44 +47,87 @@ func New(integrationUC *biz.IntegrationUseCase, wfUC *biz.WorkflowUseCase, creds return &FanOutDispatcher{integrationUC, wfUC, creds, c, servicelogger.ScopedHelper(l, "fanout-dispatcher"), l, registered} } -type integrationInfo struct { +// Dispatch item is a plugin instance + resolved inputs that gets hydrated +// during the dispatch process with information from both the DB and the attestation +type dispatchItem struct { + // Configuration registrationConfig, attachmentConfig []byte credentials *sdk.Credentials - workflowID string - backend sdk.FanOut + // Actual plugin instance + plugin sdk.FanOut + + // Fully resolved inputs + materials []*sdk.ExecuteMaterial + attestation *sdk.ExecuteAttestation } -// List of integrations that expect a material as input grouped by material type -// CDX_SBOM => [DEPTRACK INSTANCE 1, OCI INSTANCE 1, DEPTRACK INSTANCE 2] -type materialsDispatch map[schemaapi.CraftingSchema_Material_MaterialType][]*integrationInfo +type dispatchQueue []*dispatchItem + +type RunOpts struct { + Envelope *dsse.Envelope + OrgID string + WorkflowID string + WorkflowRunID string + DownloadSecretName string +} -// List of integrations that expect an attestation as input -type attestationDispatch []*integrationInfo +func (d *FanOutDispatcher) Run(ctx context.Context, opts *RunOpts) error { + // Hydration process for the dispatch queue + // 1. Load all the integrations that are attached to this workflow + queue, err := d.initDispatchQueue(ctx, opts.OrgID, opts.WorkflowID) + if err != nil { + return fmt.Errorf("loading integration info: %w", err) + } -type dispatchQueue struct { - // map of integrations that are subscribed to a material type event - materials materialsDispatch - // List of integrations that are subscribed to an attestation event - attestations attestationDispatch + d.log.Infow("msg", fmt.Sprintf("found %d attached integrations", len(queue)), "workflowID", opts.WorkflowID) + + if len(queue) == 0 { + return nil + } + + // 2. Hydrate the dispatch queue with the actual inputs + if err := d.loadInputs(ctx, queue, opts.Envelope, opts.DownloadSecretName); err != nil { + return fmt.Errorf("loading materials: %w", err) + } + + // 3 - Calculate workflow / run information + wf, err := d.wfUC.FindByID(ctx, opts.WorkflowID) + if err != nil { + return fmt.Errorf("finding workflow: %w", err) + } else if wf == nil { + return fmt.Errorf("workflow not found") + } + + workflowMetadata := &sdk.ChainloopMetadata{ + WorkflowID: opts.WorkflowID, + WorkflowRunID: opts.WorkflowRunID, + WorkflowName: wf.Name, + WorkflowProject: wf.Project, + } + + // Dispatch the integrations + for _, item := range queue { + req := generateRequest(item, workflowMetadata) + go func(p sdk.FanOut, r *sdk.ExecutionRequest) { + _ = dispatch(ctx, p, req, d.log) + }(item.plugin, req) + } + + return nil } -// Calculate the list of integrations that need to be called for this workflow -// This is done by looking at the list of attachments for this workflow and -// extracting the list of integrations that are subscribed to the materials -// and attestation that are part of the workflow -// The result is a fully populated dispatchQueue that contains the backend instance, and the configuration that will be required -// to be run during dispatch.Run -func (d *FanOutDispatcher) calculateDispatchQueue(ctx context.Context, orgID, workflowID string) (*dispatchQueue, error) { +// Initialize the dispatchQueue with information about all the attached integrations +func (d *FanOutDispatcher) initDispatchQueue(ctx context.Context, orgID, workflowID string) (dispatchQueue, error) { d.log.Infow("msg", "looking for attached integration", "workflowID", workflowID) + queue := dispatchQueue{} + // List enabled integrations with this workflow attachments, err := d.integrationUC.ListAttachments(ctx, orgID, workflowID) if err != nil { return nil, fmt.Errorf("listing attachments: %w", err) } - materialDispatch := make(materialsDispatch) - attestationDispatch := make(attestationDispatch, 0) for _, attachment := range attachments { // Get the integration DB object dbIntegration, err := d.integrationUC.FindByIDInOrg(ctx, orgID, attachment.IntegrationID.String()) @@ -106,9 +148,6 @@ func (d *FanOutDispatcher) calculateDispatchQueue(ctx context.Context, orgID, wo d.log.Infow("msg", "found attached integration", "workflowID", workflowID, "integration", backend.String()) // Craft required configuration - // Retrieve credentials - // TODO: remove from here since it's possible that this integration in fact is not being used in the end - // so we'll be retrieving credentials for nothing creds := &sdk.Credentials{} if dbIntegration.SecretName != "" { if err := d.credentialsProvider.ReadCredentials(ctx, dbIntegration.SecretName, creds); err != nil { @@ -117,194 +156,117 @@ func (d *FanOutDispatcher) calculateDispatchQueue(ctx context.Context, orgID, wo } // All the required configuration needed to run the integration - executionConfig := &integrationInfo{ + queue = append(queue, &dispatchItem{ registrationConfig: dbIntegration.Config, attachmentConfig: attachment.Config, credentials: creds, - workflowID: workflowID, - backend: backend, - } - - // Extract the list of materials this kind of backend is subscribed to - inputs := backend.Describe().SubscribedInputs - if inputs == nil { - d.log.Warnw("msg", "integration does not subscribe to any material", "workflowID", workflowID, "backendID", backend.Describe().ID) - continue - } - - // If the integration is subscribed to the envelope, add it to the list of integrations that need to be called - if inputs.DSSEnvelope { - attestationDispatch = append(attestationDispatch, executionConfig) - } - - // if the integration is subscribed to any material, add it to the list of integrations that need to be called - if inputs.Materials != nil { - for _, material := range inputs.Materials { - // Add the integration to the list of integrations that need to be called for this material type - if _, ok := materialDispatch[material.Type]; !ok { - materialDispatch[material.Type] = []*integrationInfo{executionConfig} - } else { - materialDispatch[material.Type] = append(materialDispatch[material.Type], executionConfig) - } - } - } + plugin: backend, + }) } - return &dispatchQueue{materials: materialDispatch, attestations: attestationDispatch}, nil + return queue, nil } -type RunOpts struct { - Envelope *dsse.Envelope - OrgID string - WorkflowID string - WorkflowRunID string - DownloadSecretName string -} - -// Run attestation and materials to the attached integrations -func (d *FanOutDispatcher) Run(ctx context.Context, opts *RunOpts) error { - // Calculate metadata - wf, err := d.wfUC.FindByID(ctx, opts.WorkflowID) - if err != nil { - return fmt.Errorf("finding workflow: %w", err) - } else if wf == nil { - return fmt.Errorf("workflow not found") +// Load the inputs for the dispatchItem, both materials and attestation +func (d *FanOutDispatcher) loadInputs(ctx context.Context, queue dispatchQueue, att *dsse.Envelope, secretName string) error { + if att == nil { + return fmt.Errorf("attestation is nil") } - workflowMetadata := &sdk.ChainloopMetadata{ - WorkflowID: opts.WorkflowID, - WorkflowRunID: opts.WorkflowRunID, - WorkflowName: wf.Name, - WorkflowProject: wf.Project, - } - - queue, err := d.calculateDispatchQueue(ctx, opts.OrgID, opts.WorkflowID) - if err != nil { - return fmt.Errorf("calculating dispatch queue: %w", err) - } - - // get the in_toto statement from the envelope if present - statement, err := chainloop.ExtractStatement(opts.Envelope) + // Calculate the attestation information from the envelope + statement, err := chainloop.ExtractStatement(att) if err != nil { return fmt.Errorf("extracting statement: %w", err) } - // Iterate over the materials in the attestation and dispatch them to the integrations that are subscribed to them - predicate, err := chainloop.ExtractPredicate(opts.Envelope) + predicate, err := chainloop.ExtractPredicate(att) if err != nil { return fmt.Errorf("extracting predicate: %w", err) } var attestationInput = &sdk.ExecuteAttestation{ - Envelope: opts.Envelope, + Envelope: att, Statement: statement, Predicate: predicate, } - // Send the envelope to the integrations that are subscribed to it - for _, integration := range queue.attestations { - req := generateRequest(integration, workflowMetadata) - req.Input = &sdk.ExecuteInput{ - Attestation: attestationInput, - } - - go func(backend sdk.FanOut) { - _ = dispatch(ctx, backend, req, d.log) - }(integration.backend) + // 1 - Attach the attestation to all the dispatchItems + for _, item := range queue { + item.attestation = attestationInput } + // 2 - Attach the materials to only the plugins that is subscribed to that material type for _, material := range predicate.GetMaterials() { - // Find the backends that are subscribed to this material type, this includes - // 1) Any integration backend that is listening to all material types - // 2) Any integration backend that is listening to this specific material type - var backends []*integrationInfo - if b := queue.materials[schemaapi.CraftingSchema_Material_MATERIAL_TYPE_UNSPECIFIED]; b != nil { - backends = append(backends, b...) - } - - if b := queue.materials[schemaapi.CraftingSchema_Material_MaterialType(schemaapi.CraftingSchema_Material_MaterialType_value[material.Type])]; b != nil { - backends = append(backends, b...) - } - - // There are no backends that are subscribed to this material type - if len(backends) == 0 { - continue - } - - d.log.Infow("msg", fmt.Sprintf("%d integrations found for this material type", len(backends)), "workflowID", opts.WorkflowID, "materialType", material.Type, "name", material.Name) - - // Retrieve material content + // By default is the inline material content content := []byte(material.Value) - // It's a downloadable so we retrieve and override the content variable - if material.Hash != nil && material.UploadedToCAS { - digest := material.Hash.String() - d.log.Infow("msg", "downloading material", "workflowID", opts.WorkflowID, "materialType", material.Type, "name", material.Name) - buf := bytes.NewBuffer(nil) - if err := d.casClient.Download(ctx, opts.DownloadSecretName, buf, digest); err != nil { - return fmt.Errorf("downloading from CAS: %w", err) - } - - content = buf.Bytes() - } + // Flag to make sure we download it only once + var downloaded bool + + // Find the plugins that are subscribed to this material type + for _, item := range queue { + if item.plugin.IsSubscribedTo(material.Type) { + // It's a downloadable and has not been downloaded yet + if !downloaded && material.Hash != nil && material.UploadedToCAS { + buf := bytes.NewBuffer(nil) + if err := d.casClient.Download(ctx, secretName, buf, material.Hash.String()); err != nil { + return fmt.Errorf("downloading from CAS: %w", err) + } + + content = buf.Bytes() + downloaded = true + } - // Material information to be sent to the integration - var materialInput = &sdk.ExecuteMaterial{ - NormalizedMaterial: material, - Content: content, - } + item.materials = append(item.materials, &sdk.ExecuteMaterial{ + NormalizedMaterial: material, + Content: content, + }) - // Execute the integration backends - for _, b := range backends { - req := generateRequest(b, workflowMetadata) - req.Input = &sdk.ExecuteInput{ - // They receive both the attestation information and the specific material information - Material: materialInput, - Attestation: attestationInput, + d.log.Infow("msg", "adding material to integration", "material", material.Type, "integration", item.plugin.String()) } - - go func() { - _ = dispatch(ctx, b.backend, req, d.log) - }() - - d.log.Infow("msg", "integration executed!", "workflowID", opts.WorkflowID, "materialType", material.Type, "integration", b.backend.Describe().ID) } } return nil } -func dispatch(ctx context.Context, backend sdk.FanOut, opts *sdk.ExecutionRequest, logger *log.Helper) error { +func dispatch(ctx context.Context, plugin sdk.FanOut, opts *sdk.ExecutionRequest, logger *log.Helper) error { b := backoff.NewExponentialBackOff() - b.MaxElapsedTime = 1 * time.Minute + b.MaxElapsedTime = 10 * time.Second var inputType string switch { case opts.Input.Attestation != nil: inputType = "DSSEnvelope" - case opts.Input.Material != nil: - inputType = fmt.Sprintf("Material:%s", opts.Input.Material.Type) + case len(opts.Input.Materials) > 0: + var materialTypes []string + for _, m := range opts.Input.Materials { + materialTypes = append(materialTypes, m.Type) + } + + inputType = fmt.Sprintf("Materials: %q", materialTypes) default: return errors.New("no input provided") } return backoff.RetryNotify( func() error { - logger.Infow("msg", "executing integration", "integration", backend.String(), "input", inputType) - err := backend.Execute(ctx, opts) + logger.Infow("msg", "executing integration", "integration", plugin.String(), "input", inputType) + + err := plugin.Execute(ctx, opts) if err == nil { - logger.Infow("msg", "finished OK!", "integration", backend.String(), "input", inputType) + logger.Infow("msg", "execution OK!", "integration", plugin.String(), "input", inputType) } return err }, b, func(err error, delay time.Duration) { - logger.Warnf("error executing integration %s, will retry in %s - %s", backend.String(), delay, err) + logger.Warnf("error executing integration %s, will retry in %s - %s", plugin.String(), delay, err) }, ) } -func generateRequest(in *integrationInfo, metadata *sdk.ChainloopMetadata) *sdk.ExecutionRequest { +func generateRequest(in *dispatchItem, metadata *sdk.ChainloopMetadata) *sdk.ExecutionRequest { return &sdk.ExecutionRequest{ ChainloopMetadata: metadata, RegistrationInfo: &sdk.RegistrationResponse{ @@ -314,5 +276,9 @@ func generateRequest(in *integrationInfo, metadata *sdk.ChainloopMetadata) *sdk. AttachmentInfo: &sdk.AttachmentResponse{ Configuration: in.attachmentConfig, }, + Input: &sdk.ExecuteInput{ + Materials: in.materials, + Attestation: in.attestation, + }, } } diff --git a/app/controlplane/internal/dispatcher/dispatcher_test.go b/app/controlplane/internal/dispatcher/dispatcher_test.go index ff7f8f152..81c300265 100644 --- a/app/controlplane/internal/dispatcher/dispatcher_test.go +++ b/app/controlplane/internal/dispatcher/dispatcher_test.go @@ -16,7 +16,11 @@ package dispatcher import ( + "bytes" "context" + "encoding/json" + "io" + "os" "testing" v1 "github.com/chainloop-dev/chainloop/app/controlplane/api/workflowcontract/v1" @@ -24,6 +28,8 @@ import ( "github.com/chainloop-dev/chainloop/app/controlplane/internal/biz/mocks" "github.com/chainloop-dev/chainloop/app/controlplane/plugins/sdk/v1" mockedSDK "github.com/chainloop-dev/chainloop/app/controlplane/plugins/sdk/v1/mocks" + "github.com/go-kratos/kratos/v2/log" + "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/chainloop-dev/chainloop/app/controlplane/internal/biz/testhelpers" "github.com/stretchr/testify/assert" @@ -33,16 +39,77 @@ import ( "google.golang.org/protobuf/types/known/structpb" ) -func (s *dispatcherTestSuite) TestCalculateDispatchQueue() { - integrationInfoBuilder := func(b sdk.FanOut) *integrationInfo { - return &integrationInfo{ - backend: b, - registrationConfig: []byte("deadbeef"), - attachmentConfig: []byte("deadbeef"), - credentials: nil, - } +var integrationInfoBuilder = func(b sdk.FanOut) *dispatchItem { + return &dispatchItem{ + plugin: b, + registrationConfig: []byte("deadbeef"), + attachmentConfig: []byte("deadbeef"), + credentials: nil, + } +} + +func (s *dispatcherTestSuite) TestLoadInputs() { + queue := dispatchQueue{ + integrationInfoBuilder(s.ociIntegrationBackend), + integrationInfoBuilder(s.containerIntegrationBackend), + integrationInfoBuilder(s.cdxIntegrationBackend), } + envelope, err := testEnvelope("testdata/attestation.json") + require.NoError(s.T(), err) + + // Simulate SBOM download + s.casClient.On("Download", mock.Anything, "secret-name", mock.Anything, mock.Anything). + Return(nil).Run(func(args mock.Arguments) { + buf := bytes.NewBuffer([]byte("SBOM Content")) + _, err := io.Copy(args.Get(2).(io.Writer), buf) + s.NoError(err) + }) + + err = s.dispatcher.loadInputs(context.TODO(), queue, envelope, "secret-name") + assert.NoError(s.T(), err) + require.Len(s.T(), queue, 3) + + // OCI integration has no materials + assert.Equal(s.T(), "OCI_INTEGRATION", queue[0].plugin.Describe().ID) + assert.Len(s.T(), queue[0].materials, 0) + + // Container integration has container image information + dispathItem, materials := queue[1], queue[1].materials + assert.Equal(s.T(), "CONTAINER_INTEGRATION", dispathItem.plugin.Describe().ID) + assert.Len(s.T(), materials, 1) + assert.Equal(s.T(), "image", materials[0].Name) + assert.Equal(s.T(), "sha256:264f55a6ff9cec2f4742a9faacc033b29f65c04dd4480e71e23579d484288d61", materials[0].Hash.String()) + assert.Equal(s.T(), "index.docker.io/bitnami/nginx", string(materials[0].Content)) + + // Dependency-Track integration has two sboms with the content already downloaded + dispathItem, materials = queue[2], queue[2].materials + assert.Equal(s.T(), "SBOM_INTEGRATION", dispathItem.plugin.Describe().ID) + + require.Len(s.T(), materials, 2) + assert.Equal(s.T(), "skynet-sbom", materials[0].Name) + assert.Equal(s.T(), "SBOM Content", string(materials[0].Content)) + + assert.Equal(s.T(), "skynet2-sbom", materials[1].Name) + assert.Equal(s.T(), "SBOM Content", string(materials[1].Content)) +} + +func testEnvelope(filePath string) (*dsse.Envelope, error) { + var envelope dsse.Envelope + content, err := os.ReadFile(filePath) + if err != nil { + return nil, err + } + + err = json.Unmarshal(content, &envelope) + if err != nil { + return nil, err + } + + return &envelope, nil +} + +func (s *dispatcherTestSuite) TestInitDispatchQueue() { testCasesWithError := []struct { name string orgID string @@ -63,59 +130,45 @@ func (s *dispatcherTestSuite) TestCalculateDispatchQueue() { for _, tc := range testCasesWithError { s.Run(tc.name, func() { - q, err := s.dispatcher.calculateDispatchQueue(context.TODO(), tc.orgID, tc.workflowID) + q, err := s.dispatcher.initDispatchQueue(context.TODO(), tc.orgID, tc.workflowID) assert.Error(s.T(), err) assert.Nil(s.T(), q) }) } s.T().Run("integration does NOT have integrations", func(t *testing.T) { - q, err := s.dispatcher.calculateDispatchQueue(context.TODO(), s.org.ID, s.emptyWorkflow.ID.String()) + q, err := s.dispatcher.initDispatchQueue(context.TODO(), s.org.ID, s.emptyWorkflow.ID.String()) require.NoError(t, err) require.NotNil(t, q) - assert.Equal(t, make(materialsDispatch), q.materials) - assert.Equal(t, make(attestationDispatch, 0), q.attestations) + assert.Len(t, q, 0) }) - s.T().Run("integration does have attestation-based integrations", func(t *testing.T) { - wantAttestations := attestationDispatch{integrationInfoBuilder(s.ociIntegrationBackend)} + s.T().Run("integration does have integrations", func(t *testing.T) { + wantAttestations := dispatchQueue{ + integrationInfoBuilder(s.ociIntegrationBackend), integrationInfoBuilder(s.containerIntegrationBackend), + integrationInfoBuilder(s.cdxIntegrationBackend), integrationInfoBuilder(s.cdxIntegrationBackend), + } - q, err := s.dispatcher.calculateDispatchQueue(context.TODO(), s.org.ID, s.workflow.ID.String()) + q, err := s.dispatcher.initDispatchQueue(context.TODO(), s.org.ID, s.workflow.ID.String()) require.NoError(t, err) - // Attestation integrations - assert.Len(t, q.attestations, 1) - assert.Equal(t, wantAttestations[0].backend, q.attestations[0].backend) - assert.Equal(t, q.attestations[0].backend.Describe().ID, "OCI_INTEGRATION") - assert.Equal(t, wantAttestations[0].attachmentConfig, q.attestations[0].attachmentConfig) - }) - - s.T().Run("integration does have material-based integrations", func(t *testing.T) { - wantMaterials := make(materialsDispatch) - wantMaterials[v1.CraftingSchema_Material_SBOM_CYCLONEDX_JSON] = []*integrationInfo{ - integrationInfoBuilder(s.cdxIntegrationBackend), + // There are 4 integrations attached + require.Len(t, q, 4) + + for i, tc := range []struct{ id, subscribedMaterial string }{ + {"OCI_INTEGRATION", ""}, + {"CONTAINER_INTEGRATION", "CONTAINER_IMAGE"}, + {"SBOM_INTEGRATION", "SBOM_CYCLONEDX_JSON"}, + {"SBOM_INTEGRATION", "SBOM_CYCLONEDX_JSON"}, + } { + assert.Equal(t, tc.id, q[i].plugin.Describe().ID) + assert.Equal(t, wantAttestations[i].plugin, q[i].plugin) + assert.Equal(t, wantAttestations[i].attachmentConfig, q[i].attachmentConfig) + + if tc.subscribedMaterial != "" { + assert.True(t, q[i].plugin.IsSubscribedTo(tc.subscribedMaterial)) + } } - q, err := s.dispatcher.calculateDispatchQueue(context.TODO(), s.org.ID, s.workflow.ID.String()) - require.NoError(t, err) - - // the map has two keys - require.Len(t, q.materials, 2) - sbomQueue := q.materials[v1.CraftingSchema_Material_SBOM_CYCLONEDX_JSON] - // There are two integrations for SBOM material attached - require.Len(t, sbomQueue, 2) - assert.Equal(t, s.cdxIntegrationBackend, sbomQueue[0].backend) - assert.Equal(t, "SBOM_INTEGRATION", sbomQueue[0].backend.Describe().ID) - assert.Equal(t, s.cdxIntegrationBackend, sbomQueue[1].backend) - assert.Equal(t, "SBOM_INTEGRATION", sbomQueue[1].backend.Describe().ID) - assert.Equal(t, []byte("deadbeef"), sbomQueue[0].attachmentConfig) - assert.Equal(t, []byte("deadbeef"), sbomQueue[1].attachmentConfig) - - // and one for any material type - anyQueue := q.materials[v1.CraftingSchema_Material_MATERIAL_TYPE_UNSPECIFIED] - require.Len(t, anyQueue, 1) - assert.Equal(t, s.anyIntegrationBackend, anyQueue[0].backend) - assert.Equal(t, "ANY_INTEGRATION", anyQueue[0].backend.Describe().ID) - assert.Equal(t, []byte("deadbeef"), anyQueue[0].attachmentConfig) }) } @@ -173,19 +226,18 @@ func (s *dispatcherTestSuite) SetupTest() { s.cdxIntegration, err = s.Integration.RegisterAndSave(ctx, s.org.ID, "", s.cdxIntegrationBackend, config) require.NoError(s.T(), err) - // Any material integration b, err = sdk.NewFanOut( &sdk.NewParams{ - ID: "ANY_INTEGRATION", + ID: "CONTAINER_INTEGRATION", Version: "1.0", InputSchema: fanOutSchemas, }, - sdk.WithInputMaterial(v1.CraftingSchema_Material_MATERIAL_TYPE_UNSPECIFIED), + sdk.WithInputMaterial(v1.CraftingSchema_Material_CONTAINER_IMAGE), ) require.NoError(s.T(), err) - s.anyIntegrationBackend = &mockedIntegration{FanOutPlugin: customImplementation, FanOutIntegration: b} - s.anyIntegration, err = s.Integration.RegisterAndSave(ctx, s.org.ID, "", s.anyIntegrationBackend, config) + s.containerIntegrationBackend = &mockedIntegration{FanOutPlugin: customImplementation, FanOutIntegration: b} + s.containerIntegration, err = s.Integration.RegisterAndSave(ctx, s.org.ID, "", s.containerIntegrationBackend, config) require.NoError(s.T(), err) // Attestation integration @@ -195,7 +247,6 @@ func (s *dispatcherTestSuite) SetupTest() { Version: "1.0", InputSchema: fanOutSchemas, }, - sdk.WithEnvelope(), ) require.NoError(s.T(), err) @@ -206,19 +257,19 @@ func (s *dispatcherTestSuite) SetupTest() { // Attach all the integrations to the workflow for _, i := range []struct { integrationID string - fanout sdk.FanOut + fanOut sdk.FanOut }{ // We attach the CDX integration twice {s.cdxIntegration.ID.String(), s.cdxIntegrationBackend}, {s.cdxIntegration.ID.String(), s.cdxIntegrationBackend}, - {s.anyIntegration.ID.String(), s.anyIntegrationBackend}, + {s.containerIntegration.ID.String(), s.containerIntegrationBackend}, {s.ociIntegration.ID.String(), s.ociIntegrationBackend}, } { _, err = s.Integration.AttachToWorkflow(ctx, &biz.AttachOpts{ OrgID: s.org.ID, IntegrationID: i.integrationID, WorkflowID: s.workflow.ID.String(), - FanOutIntegration: i.fanout, + FanOutIntegration: i.fanOut, AttachmentConfig: config, }) @@ -226,8 +277,11 @@ func (s *dispatcherTestSuite) SetupTest() { } // Register the integrations in the dispatcher - registeredIntegrations := sdk.AvailablePlugins{s.cdxIntegrationBackend, s.anyIntegrationBackend, s.ociIntegrationBackend} - s.dispatcher = New(s.Integration, nil, nil, mocks.NewCASClient(s.T()), registeredIntegrations, s.L) + registeredIntegrations := sdk.AvailablePlugins{s.cdxIntegrationBackend, s.containerIntegrationBackend, s.ociIntegrationBackend} + l := log.NewStdLogger(os.Stdout) + + s.casClient = mocks.NewCASClient(s.T()) + s.dispatcher = New(s.Integration, nil, nil, s.casClient, registeredIntegrations, l) } type mockedIntegration struct { @@ -239,9 +293,10 @@ type mockedIntegration struct { type dispatcherTestSuite struct { suite.Suite testhelpers.UseCasesEachTestSuite - cdxIntegration, ociIntegration, anyIntegration *biz.Integration - cdxIntegrationBackend, ociIntegrationBackend, anyIntegrationBackend sdk.FanOut - org, emptyOrg *biz.Organization - workflow, emptyWorkflow *biz.Workflow - dispatcher *FanOutDispatcher + cdxIntegration, ociIntegration, containerIntegration *biz.Integration + cdxIntegrationBackend, ociIntegrationBackend, containerIntegrationBackend sdk.FanOut + org, emptyOrg *biz.Organization + workflow, emptyWorkflow *biz.Workflow + dispatcher *FanOutDispatcher + casClient *mocks.CASClient } diff --git a/app/controlplane/internal/dispatcher/testdata/attestation.json b/app/controlplane/internal/dispatcher/testdata/attestation.json new file mode 100644 index 000000000..1fe43e112 --- /dev/null +++ b/app/controlplane/internal/dispatcher/testdata/attestation.json @@ -0,0 +1,10 @@ +{ + "payloadType": "application/vnd.in-toto+json", + "payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjAuMSIsInByZWRpY2F0ZVR5cGUiOiJjaGFpbmxvb3AuZGV2L2F0dGVzdGF0aW9uL3YwLjIiLCJzdWJqZWN0IjpbeyJuYW1lIjoiY2hhaW5sb29wLmRldi93b3JrZmxvdy9vbmx5LXNib20iLCJkaWdlc3QiOnsic2hhMjU2IjoiMzAzNmYyZTVkNzA5YTIzODA4ZWFhMDU1NzBmMTcxOGFmZmY5MmQ0YWQzNWUyMzJjMTM4NzM4MzcyYWM5M2RmYiJ9fV0sInByZWRpY2F0ZSI6eyJtZXRhZGF0YSI6eyJuYW1lIjoib25seS1zYm9tIiwicHJvamVjdCI6ImZvbyIsInRlYW0iOiIiLCJpbml0aWFsaXplZEF0IjoiMjAyMy0wNi0yM1QxMzowMDoyOS4xODM2MTg5NjZaIiwiZmluaXNoZWRBdCI6IjIwMjMtMDYtMjNUMTU6MDA6NDMuNzA5OTcwNjcrMDI6MDAiLCJ3b3JrZmxvd1J1bklEIjoiYTE3Y2EyMTYtMjliNi00MTA3LWIzZmMtOTI1NmY3YWYyZmU2Iiwid29ya2Zsb3dJRCI6IjVlMzRiMzRmLTg4MmMtNDhiOC04NGMwLTRmMjM4ZTE1YTVmZCJ9LCJidWlsZGVyIjp7ImlkIjoiY2hhaW5sb29wLmRldi9jbGkvZGV2QHNoYTI1NjphOGRkZDczODEzMDAyZjYyZDJkZTExYTQyMDU0YzA2ZmFlMGQwYTg4YjZjODkwNGU3ZGU0NDc1MTk4YzBiMGQzIn0sImJ1aWxkVHlwZSI6ImNoYWlubG9vcC5kZXYvd29ya2Zsb3dydW4vdjAuMSIsInJ1bm5lclR5cGUiOiJSVU5ORVJfVFlQRV9VTlNQRUNJRklFRCIsIm1hdGVyaWFscyI6W3siZGlnZXN0Ijp7InNoYTI1NiI6IjI2NGY1NWE2ZmY5Y2VjMmY0NzQyYTlmYWFjYzAzM2IyOWY2NWMwNGRkNDQ4MGU3MWUyMzU3OWQ0ODQyODhkNjEifSwibmFtZSI6ImluZGV4LmRvY2tlci5pby9iaXRuYW1pL25naW54IiwiYW5ub3RhdGlvbnMiOnsiY2hhaW5sb29wLm1hdGVyaWFsLm5hbWUiOiJpbWFnZSIsImNoYWlubG9vcC5tYXRlcmlhbC50eXBlIjoiQ09OVEFJTkVSX0lNQUdFIn19LHsiZGlnZXN0Ijp7InNoYTI1NiI6IjE2MTU5YmI4ODFlYjRhYjdlYjVkOGFmYzUzNTBiMGZlZWVkMWUzMWMwYTI2OGUzNTVlNzRmOWNjYmU4ODVlMGMifSwibmFtZSI6InNib20uY3ljbG9uZWR4Lmpzb24iLCJhbm5vdGF0aW9ucyI6eyJjaGFpbmxvb3AubWF0ZXJpYWwuY2FzIjp0cnVlLCJjaGFpbmxvb3AubWF0ZXJpYWwubmFtZSI6InNreW5ldC1zYm9tIiwiY2hhaW5sb29wLm1hdGVyaWFsLnR5cGUiOiJTQk9NX0NZQ0xPTkVEWF9KU09OIn19LHsiZGlnZXN0Ijp7InNoYTI1NiI6IjE2MTU5YmI4ODFlYjRhYjdlYjVkOGFmYzUzNTBiMGZlZWVkMWUzMWMwYTI2OGUzNTVlNzRmOWNjYmU4ODVlMGMifSwibmFtZSI6InNib20uY3ljbG9uZWR4Lmpzb24iLCJhbm5vdGF0aW9ucyI6eyJjaGFpbmxvb3AubWF0ZXJpYWwuY2FzIjp0cnVlLCJjaGFpbmxvb3AubWF0ZXJpYWwubmFtZSI6InNreW5ldDItc2JvbSIsImNoYWlubG9vcC5tYXRlcmlhbC50eXBlIjoiU0JPTV9DWUNMT05FRFhfSlNPTiJ9fV19fQ==", + "signatures": [ + { + "keyid": "", + "sig": "MEQCIAFytdWto+Bi5Tht+7haXnjiHBfwLwgf6ks0mxeeSadEAiBoLKhy+UKsdDH3ukHJPuiHvWOGhEP19kZ9aipmaT4TlQ==" + } + ] +} diff --git a/app/controlplane/internal/service/integration.go b/app/controlplane/internal/service/integration.go index 675202f62..46e13bb01 100644 --- a/app/controlplane/internal/service/integration.go +++ b/app/controlplane/internal/service/integration.go @@ -52,7 +52,7 @@ func (s *IntegrationsService) ListAvailable(_ context.Context, _ *pb.Integration d := i.Describe() var subscribedMaterials = make([]string, 0) - for _, m := range d.SubscribedInputs.Materials { + for _, m := range d.SubscribedMaterials { subscribedMaterials = append(subscribedMaterials, m.Type.String()) } @@ -62,10 +62,9 @@ func (s *IntegrationsService) ListAvailable(_ context.Context, _ *pb.Integration Description: d.Description, Type: &pb.IntegrationAvailableItem_Fanout{ Fanout: &pb.PluginFanout{ - AttachmentSchema: d.AttachmentJSONSchema, - RegistrationSchema: d.RegistrationJSONSchema, - SubscribedAttestation: d.SubscribedInputs.DSSEnvelope, - SubscribedMaterials: subscribedMaterials, + AttachmentSchema: d.AttachmentJSONSchema, + RegistrationSchema: d.RegistrationJSONSchema, + SubscribedMaterials: subscribedMaterials, }, }, } diff --git a/app/controlplane/plugins/README.md b/app/controlplane/plugins/README.md index fbe8d44cd..4030d8d66 100644 --- a/app/controlplane/plugins/README.md +++ b/app/controlplane/plugins/README.md @@ -20,6 +20,23 @@ The loading stage is when the plugin gets enabled in the Chainloop Control Plane - What kind of input you want your plugin to receive, materials, attestations or both. - Available input properties for the registration and attachment phases. These schemas will be shown to the user and will be used to validate the input. +Example, excerpt from the [Dependency-Track plugin](https://github.com/chainloop-dev/chainloop/tree/main/app/controlplane/plugins/core/dependency-track/v1): + +```go +base, err := sdk.NewFanOut( + &sdk.NewParams{ + ID: "dependency-track", + Version: "1.2", + Description: "Send CycloneDX SBOMs to your Dependency-Track instance", + // The input schema for both registration and attachment phases + InputSchema: &sdk.InputSchema{ + Registration: registrationRequest{}, + Attachment: attachmentRequest{}, + }, + // Subscribed to receive CycloneDX SBOMs in json format + }, sdk.WithInputMaterial(schemaapi.CraftingSchema_Material_SBOM_CYCLONEDX_JSON)) +``` + Once loaded, the plugin will be available to be registered on any organization and will be shown in the list of available plugins. ```console @@ -95,9 +112,22 @@ Examples: This is the actual execution of the plugin. This is where the plugin will do its work. i.e forward the attestation/material data to a third-party API, send a notification and so on. +When an attestation is received, the attestation and any materials that match the list of material types the plugin supports will be sent to the execute handler. For example, if your plugin does not specify any supported material types, it will receive only the attestation information. + +![execution](../../../docs/img/fanout-execute.png) + +On another hand, if the plugin is subscribed to for example support SBOM_CYCLONEDX_JSON, and JUNIX_XML it will receive the attestation information and both materials **on a single execution** + +![execution](../../../docs/img/fanout-execute-materials.png) + In addition to the attestation and material data, this handler **will also have access to the outputs from the registration and attachment phases**. -Examples: +Some important notes about the execution handler: + +- If your plugin is subscribed to a specific material type, it will get executed on every attestation, even if such attestation does not contain any material of the supported type. +- It's up to the plugin developer to make sure the desired material is present and handle the case when it's not. + +Some example use-cases: A Dependency-Track SBOM plugin will diff --git a/app/controlplane/plugins/core/dependency-track/v1/extension.go b/app/controlplane/plugins/core/dependency-track/v1/extension.go index 54609c765..46e541a3c 100644 --- a/app/controlplane/plugins/core/dependency-track/v1/extension.go +++ b/app/controlplane/plugins/core/dependency-track/v1/extension.go @@ -147,51 +147,59 @@ func (i *DependencyTrack) Attach(ctx context.Context, req *sdk.AttachmentRequest func (i *DependencyTrack) Execute(ctx context.Context, req *sdk.ExecutionRequest) error { i.Logger.Info("execution requested") - if err := validateExecuteOpts(req); err != nil { - return fmt.Errorf("running validation: %w", err) + // Iterate over all SBOMs + for _, sbom := range req.Input.Materials { + // Make sure it's an SBOM and all the required configuration has been received + if err := validateExecuteOpts(sbom, req.RegistrationInfo, req.AttachmentInfo); err != nil { + return fmt.Errorf("running validation: %w", err) + } + + // Extract registration configuration + var registrationConfig *registrationConfig + if err := sdk.FromConfig(req.RegistrationInfo.Configuration, ®istrationConfig); err != nil { + return errors.New("invalid registration configuration") + } + + // Extract attachment configuration + var attachmentConfig *attachmentConfig + if err := sdk.FromConfig(req.AttachmentInfo.Configuration, &attachmentConfig); err != nil { + return errors.New("invalid attachment configuration") + } + + i.Logger.Infow("msg", "Uploading SBOM", + "materialName", sbom.Name, + "host", registrationConfig.Domain, + "projectID", attachmentConfig.ProjectID, "projectName", attachmentConfig.ProjectName, + "workflowID", req.WorkflowID, + ) + + // Create an SBOM client and perform validation and upload + d, err := client.NewSBOMUploader(registrationConfig.Domain, + req.RegistrationInfo.Credentials.Password, + bytes.NewReader(sbom.Content), + attachmentConfig.ProjectID, + attachmentConfig.ProjectName) + if err != nil { + return fmt.Errorf("creating uploader: %w", err) + } + + if err := d.Validate(ctx); err != nil { + return fmt.Errorf("validating uploader: %w", err) + } + + if err := d.Do(ctx); err != nil { + return fmt.Errorf("uploading SBOM: %w", err) + } + + i.Logger.Infow("msg", "SBOM Uploaded", + "materialName", sbom.Name, + "host", registrationConfig.Domain, + "projectID", attachmentConfig.ProjectID, "projectName", attachmentConfig.ProjectName, + "workflowID", req.WorkflowID, + ) } - // Extract registration configuration - var registrationConfig *registrationConfig - if err := sdk.FromConfig(req.RegistrationInfo.Configuration, ®istrationConfig); err != nil { - return errors.New("invalid registration configuration") - } - - // Extract attachment configuration - var attachmentConfig *attachmentConfig - if err := sdk.FromConfig(req.AttachmentInfo.Configuration, &attachmentConfig); err != nil { - return errors.New("invalid attachment configuration") - } - - i.Logger.Infow("msg", "Uploading SBOM", - "host", registrationConfig.Domain, - "projectID", attachmentConfig.ProjectID, "projectName", attachmentConfig.ProjectName, - "workflowID", req.WorkflowID, - ) - - // Create an SBOM client and perform validation and upload - d, err := client.NewSBOMUploader(registrationConfig.Domain, - req.RegistrationInfo.Credentials.Password, - bytes.NewReader(req.Input.Material.Content), - attachmentConfig.ProjectID, - attachmentConfig.ProjectName) - if err != nil { - return fmt.Errorf("creating uploader: %w", err) - } - - if err := d.Validate(ctx); err != nil { - return fmt.Errorf("validating uploader: %w", err) - } - - if err := d.Do(ctx); err != nil { - return fmt.Errorf("uploading SBOM: %w", err) - } - - i.Logger.Infow("msg", "SBOM Uploaded", - "host", registrationConfig.Domain, - "projectID", attachmentConfig.ProjectID, "projectName", attachmentConfig.ProjectName, - "workflowID", req.WorkflowID, - ) + i.Logger.Info("execution finished") return nil } @@ -232,24 +240,24 @@ func validateAttachmentConfiguration(rc *registrationConfig, ac *attachmentReque return nil } -func validateExecuteOpts(opts *sdk.ExecutionRequest) error { - if opts == nil || opts.Input == nil || opts.Input.Material == nil || opts.Input.Material.Content == nil { +func validateExecuteOpts(m *sdk.ExecuteMaterial, regConfig *sdk.RegistrationResponse, attConfig *sdk.AttachmentResponse) error { + if m == nil || m.Content == nil { return errors.New("invalid input") } - if opts.Input.Material.Type != schemaapi.CraftingSchema_Material_SBOM_CYCLONEDX_JSON.String() { - return fmt.Errorf("invalid input type: %s", opts.Input.Material.Type) + if m.Type != schemaapi.CraftingSchema_Material_SBOM_CYCLONEDX_JSON.String() { + return fmt.Errorf("invalid input type: %s", m.Type) } - if opts.RegistrationInfo == nil || opts.RegistrationInfo.Configuration == nil { + if regConfig == nil || regConfig.Configuration == nil { return errors.New("missing registration configuration") } - if opts.RegistrationInfo.Credentials == nil { + if regConfig.Credentials == nil { return errors.New("missing credentials") } - if opts.AttachmentInfo == nil || opts.AttachmentInfo.Configuration == nil { + if attConfig == nil || attConfig.Configuration == nil { return errors.New("missing attachment configuration") } diff --git a/app/controlplane/plugins/core/dependency-track/v1/extension_test.go b/app/controlplane/plugins/core/dependency-track/v1/extension_test.go index 9042e4d48..fde02005c 100644 --- a/app/controlplane/plugins/core/dependency-track/v1/extension_test.go +++ b/app/controlplane/plugins/core/dependency-track/v1/extension_test.go @@ -133,33 +133,31 @@ func TestValidateExecuteOpts(t *testing.T) { opts *sdk.ExecutionRequest errMsg string }{ - {name: "invalid - missing input", errMsg: "invalid input"}, - {name: "invalid - missing input", opts: &sdk.ExecutionRequest{Input: &sdk.ExecuteInput{}}, errMsg: "invalid input"}, { name: "invalid - missing material", opts: &sdk.ExecutionRequest{ - Input: &sdk.ExecuteInput{Material: &sdk.ExecuteMaterial{}}, + Input: &sdk.ExecuteInput{Materials: []*sdk.ExecuteMaterial{{}}}, }, errMsg: "invalid input", }, { name: "invalid - invalid material", opts: &sdk.ExecutionRequest{ - Input: &sdk.ExecuteInput{Material: &sdk.ExecuteMaterial{NormalizedMaterial: &chainloop.NormalizedMaterial{Type: "invalid"}, Content: []byte("content")}}, + Input: &sdk.ExecuteInput{Materials: []*sdk.ExecuteMaterial{{NormalizedMaterial: &chainloop.NormalizedMaterial{Type: "invalid"}, Content: []byte("content")}}}, }, errMsg: "invalid input type", }, { name: "invalid - missing configuration", opts: &sdk.ExecutionRequest{ - Input: &sdk.ExecuteInput{Material: validMaterial}, + Input: &sdk.ExecuteInput{Materials: []*sdk.ExecuteMaterial{validMaterial}}, }, errMsg: "missing registration configuration", }, { name: "invalid - missing attachment configuration", opts: &sdk.ExecutionRequest{ - Input: &sdk.ExecuteInput{Material: validMaterial}, + Input: &sdk.ExecuteInput{Materials: []*sdk.ExecuteMaterial{validMaterial}}, RegistrationInfo: &sdk.RegistrationResponse{Configuration: []byte("config"), Credentials: &sdk.Credentials{Password: "password"}}, }, errMsg: "missing attachment configuration", @@ -167,7 +165,7 @@ func TestValidateExecuteOpts(t *testing.T) { { name: "invalid - missing registration configuration", opts: &sdk.ExecutionRequest{ - Input: &sdk.ExecuteInput{Material: validMaterial}, + Input: &sdk.ExecuteInput{Materials: []*sdk.ExecuteMaterial{validMaterial}}, AttachmentInfo: &sdk.AttachmentResponse{}, }, errMsg: "missing registration configuration", @@ -175,7 +173,7 @@ func TestValidateExecuteOpts(t *testing.T) { { name: "invalid - missing credentials", opts: &sdk.ExecutionRequest{ - Input: &sdk.ExecuteInput{Material: validMaterial}, + Input: &sdk.ExecuteInput{Materials: []*sdk.ExecuteMaterial{validMaterial}}, RegistrationInfo: &sdk.RegistrationResponse{Configuration: []byte("config")}, AttachmentInfo: &sdk.AttachmentResponse{Configuration: []byte("config")}, }, @@ -184,7 +182,7 @@ func TestValidateExecuteOpts(t *testing.T) { { name: "ok - all good", opts: &sdk.ExecutionRequest{ - Input: &sdk.ExecuteInput{Material: validMaterial}, + Input: &sdk.ExecuteInput{Materials: []*sdk.ExecuteMaterial{validMaterial}}, RegistrationInfo: &sdk.RegistrationResponse{Configuration: []byte("config"), Credentials: &sdk.Credentials{Password: "password"}}, AttachmentInfo: &sdk.AttachmentResponse{Configuration: []byte("config")}, }, @@ -193,7 +191,8 @@ func TestValidateExecuteOpts(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - err := validateExecuteOpts(tc.opts) + m := tc.opts.Input.Materials[0] + err := validateExecuteOpts(m, tc.opts.RegistrationInfo, tc.opts.AttachmentInfo) if tc.errMsg != "" { assert.ErrorContains(t, err, tc.errMsg) } else { diff --git a/app/controlplane/plugins/core/discord-webhook/v1/discord.go b/app/controlplane/plugins/core/discord-webhook/v1/discord.go index 9d5b22171..e93aab585 100644 --- a/app/controlplane/plugins/core/discord-webhook/v1/discord.go +++ b/app/controlplane/plugins/core/discord-webhook/v1/discord.go @@ -65,7 +65,6 @@ func New(l log.Logger) (sdk.FanOut, error) { Attachment: attachmentRequest{}, }, }, - sdk.WithEnvelope(), ) if err != nil { diff --git a/app/controlplane/plugins/core/oci-registry/v1/ociregistry.go b/app/controlplane/plugins/core/oci-registry/v1/ociregistry.go index ae506e920..f4cfaf283 100644 --- a/app/controlplane/plugins/core/oci-registry/v1/ociregistry.go +++ b/app/controlplane/plugins/core/oci-registry/v1/ociregistry.go @@ -67,7 +67,6 @@ func New(l log.Logger) (sdk.FanOut, error) { Attachment: &attachmentRequest{}, }, }, - sdk.WithEnvelope(), ) if err != nil { diff --git a/app/controlplane/plugins/core/smtp/v1/extension.go b/app/controlplane/plugins/core/smtp/v1/extension.go index f7f6569a9..0c4be8a4b 100644 --- a/app/controlplane/plugins/core/smtp/v1/extension.go +++ b/app/controlplane/plugins/core/smtp/v1/extension.go @@ -70,7 +70,6 @@ func New(l log.Logger) (sdk.FanOut, error) { Attachment: attachmentRequest{}, }, }, - sdk.WithEnvelope(), ) if err != nil { diff --git a/app/controlplane/plugins/core/template/v1/extension.go b/app/controlplane/plugins/core/template/v1/extension.go index 09af5be2a..77267af79 100644 --- a/app/controlplane/plugins/core/template/v1/extension.go +++ b/app/controlplane/plugins/core/template/v1/extension.go @@ -154,55 +154,54 @@ func (i *Integration) Attach(_ context.Context, req *sdk.AttachmentRequest) (*sd func (i *Integration) Execute(_ context.Context, req *sdk.ExecutionRequest) error { i.Logger.Info("execution requested") - // Example of custom validation - if err := validateExecuteRequest(req); err != nil { - return fmt.Errorf("running validation: %w", err) + // You can receive more than one material + for _, sbom := range req.Input.Materials { + // Example of custom validation + if err := validateExecuteOpts(sbom, req.RegistrationInfo, req.AttachmentInfo); err != nil { + return fmt.Errorf("running validation: %w", err) + } + + // Extract registration and attachment configuration if needed + // var registrationConfig *registrationState + // if err := sdk.FromConfig(req.RegistrationInfo.Configuration, ®istrationConfig); err != nil { + // return fmt.Errorf("invalid registration configuration %w", err) + // } + + // // Extract attachment configuration + // var attachmentConfig *attachmentState + // if err := sdk.FromConfig(req.AttachmentInfo.Configuration, &attachmentConfig); err != nil { + // return fmt.Errorf("invalid attachment configuration %w", err) + // } + + // START CUSTOM LOGIC + // EO CUSTOM LOGIC } - // Extract registration and attachment configuration if needed - // var registrationConfig *registrationState - // if err := sdk.FromConfig(req.RegistrationInfo.Configuration, ®istrationConfig); err != nil { - // return fmt.Errorf("invalid registration configuration %w", err) - // } - - // // Extract attachment configuration - // var attachmentConfig *attachmentState - // if err := sdk.FromConfig(req.AttachmentInfo.Configuration, &attachmentConfig); err != nil { - // return fmt.Errorf("invalid attachment configuration %w", err) - // } - - // START CUSTOM LOGIC - // EO CUSTOM LOGIC - + i.Logger.Info("execution finished") return nil } // Validator example for the execution phase // In this case we expect to receive a SBOM in CycloneDX format // plus registration, attachment state and credentials -func validateExecuteRequest(req *sdk.ExecutionRequest) error { - if req == nil || req.Input == nil { - return errors.New("execution input not received") - } - - if req.Input.Material == nil || req.Input.Material.Content == nil { - return errors.New("execution input invalid, material not provided or empty content") +func validateExecuteOpts(m *sdk.ExecuteMaterial, regConfig *sdk.RegistrationResponse, attConfig *sdk.AttachmentResponse) error { + if m == nil || m.Content == nil { + return errors.New("invalid input") } - // This is just an example of guard clause, you can remove it if you want to accept any kind of input - if req.Input.Material.Type != schemaapi.CraftingSchema_Material_SBOM_CYCLONEDX_JSON.String() { - return fmt.Errorf("invalid input type: %s", req.Input.Material.Type) + if m.Type != schemaapi.CraftingSchema_Material_SBOM_CYCLONEDX_JSON.String() { + return fmt.Errorf("invalid input type: %s", m.Type) } - if req.RegistrationInfo == nil || req.RegistrationInfo.Configuration == nil { + if regConfig == nil || regConfig.Configuration == nil { return errors.New("missing registration configuration") } - if req.RegistrationInfo.Credentials == nil { + if regConfig.Credentials == nil { return errors.New("missing credentials") } - if req.AttachmentInfo == nil || req.AttachmentInfo.Configuration == nil { + if attConfig == nil || attConfig.Configuration == nil { return errors.New("missing attachment configuration") } diff --git a/app/controlplane/plugins/sdk/readme-generator/main.go b/app/controlplane/plugins/sdk/readme-generator/main.go index db096a296..85f66f412 100644 --- a/app/controlplane/plugins/sdk/readme-generator/main.go +++ b/app/controlplane/plugins/sdk/readme-generator/main.go @@ -83,7 +83,7 @@ func updateIntegrationsIndex(plugins sdk.AvailablePlugins) error { info := p.Describe() // Load the materials var subscribedMaterials = make([]string, 0) - for _, m := range info.SubscribedInputs.Materials { + for _, m := range info.SubscribedMaterials { subscribedMaterials = append(subscribedMaterials, m.Type.String()) } diff --git a/app/controlplane/plugins/sdk/v1/fanout.go b/app/controlplane/plugins/sdk/v1/fanout.go index 54ea4bfe1..d67806013 100644 --- a/app/controlplane/plugins/sdk/v1/fanout.go +++ b/app/controlplane/plugins/sdk/v1/fanout.go @@ -44,8 +44,8 @@ type FanOutIntegration struct { version string // Optional description description string - // Kind of inputs does the integration expect as part of the execution - subscribedInputs *Inputs + // Material types an integration expect as part of the execution + subscribedMaterials []*InputMaterial // Rendered schema definitions // Generated from the schema definitions using https://github.com/invopop/jsonschema registrationJSONSchema []byte @@ -74,6 +74,8 @@ type Core interface { Describe() *IntegrationInfo ValidateRegistrationRequest(jsonPayload []byte) error ValidateAttachmentRequest(jsonPayload []byte) error + // Return if the integration is subscribed to the material type + IsSubscribedTo(materialType string) bool } // To be implemented per integration @@ -129,7 +131,7 @@ type ExecutionRequest struct { // The material will contain its content as well as the metadata type ExecuteInput struct { Attestation *ExecuteAttestation - Material *ExecuteMaterial + Materials []*ExecuteMaterial } type ExecuteAttestation struct { @@ -148,13 +150,6 @@ type Credentials struct { URL, Username, Password string } -// An integration can be subscribed to an envelope and/or a list of materials -// To subscribe to any material type it will use schemaapi.CraftingSchema_Material_MATERIAL_TYPE_UNSPECIFIED -type Inputs struct { - DSSEnvelope bool - Materials []*InputMaterial -} - type InputMaterial struct { // Name of the material kind that the integration expects Type schemaapi.CraftingSchema_Material_MaterialType @@ -168,11 +163,11 @@ type NewParams struct { func NewFanOut(p *NewParams, opts ...NewOpt) (*FanOutIntegration, error) { c := &FanOutIntegration{ - id: p.ID, - version: p.Version, - description: p.Description, - log: p.Logger, - subscribedInputs: &Inputs{}, + id: p.ID, + version: p.Version, + description: p.Description, + log: p.Logger, + subscribedMaterials: []*InputMaterial{}, } if c.log == nil { @@ -234,17 +229,9 @@ func validateInputs(c *FanOutIntegration) error { return fmt.Errorf("version is required") } - // Subscribed inputs - if c.subscribedInputs == nil || (!c.subscribedInputs.DSSEnvelope && (c.subscribedInputs.Materials == nil || len(c.subscribedInputs.Materials) == 0)) { - return fmt.Errorf("the integration needs to subscribe to at least one input type. An envelope and/or a material") - } - - // If you subscribe to a generic material type you can't subscribe to an specific one - if c.subscribedInputs.Materials != nil && len(c.subscribedInputs.Materials) > 1 { - for _, m := range c.subscribedInputs.Materials { - if m.Type == schemaapi.CraftingSchema_Material_MATERIAL_TYPE_UNSPECIFIED { - return fmt.Errorf("can't subscribe to specific material type since you are already subscribed to a generic one") - } + for _, m := range c.subscribedMaterials { + if m.Type == schemaapi.CraftingSchema_Material_MATERIAL_TYPE_UNSPECIFIED { + return fmt.Errorf("%s is not a valid material type", m.Type) } } @@ -275,7 +262,7 @@ type IntegrationInfo struct { // Integration description Description string // Kind of inputs does the integration expect as part of the execution - SubscribedInputs *Inputs + SubscribedMaterials []*InputMaterial // Schemas in JSON schema format RegistrationJSONSchema, AttachmentJSONSchema []byte } @@ -285,7 +272,7 @@ func (i *FanOutIntegration) Describe() *IntegrationInfo { ID: i.id, Version: i.version, Description: i.description, - SubscribedInputs: i.subscribedInputs, + SubscribedMaterials: i.subscribedMaterials, RegistrationJSONSchema: i.registrationJSONSchema, AttachmentJSONSchema: i.attachmentJSONSchema, } @@ -301,6 +288,20 @@ func (i *FanOutIntegration) ValidateAttachmentRequest(jsonPayload []byte) error return validatePayloadAgainstJSONSchema(jsonPayload, i.attachmentJSONSchema) } +func (i *FanOutIntegration) IsSubscribedTo(m string) bool { + if i.subscribedMaterials == nil { + return false + } + + for _, material := range i.subscribedMaterials { + if material.Type.String() == m { + return true + } + } + + return false +} + func validatePayloadAgainstJSONSchema(jsonPayload []byte, jsonSchema []byte) error { schema, err := CompileJSONSchema(jsonSchema) if err != nil { @@ -330,39 +331,27 @@ func validatePayloadAgainstJSONSchema(jsonPayload []byte, jsonSchema []byte) err } func (i *FanOutIntegration) String() string { - inputs := i.subscribedInputs + inputs := i.subscribedMaterials - subscribedMaterials := make([]string, len(inputs.Materials)) - for i, m := range inputs.Materials { + subscribedMaterials := make([]string, len(inputs)) + for i, m := range inputs { subscribedMaterials[i] = m.Type.String() } - return fmt.Sprintf("id=%s, version=%s, expectsEnvelope=%t, expectedMaterials=%s", i.id, i.version, inputs.DSSEnvelope, subscribedMaterials) + return fmt.Sprintf("id=%s, version=%s, expectedMaterials=%s", i.id, i.version, subscribedMaterials) } type NewOpt func(*FanOutIntegration) -func WithEnvelope() NewOpt { - return func(c *FanOutIntegration) { - if c.subscribedInputs == nil { - c.subscribedInputs = &Inputs{DSSEnvelope: true} - } else { - c.subscribedInputs.DSSEnvelope = true - } - } -} - func WithInputMaterial(materialType schemaapi.CraftingSchema_Material_MaterialType) NewOpt { return func(c *FanOutIntegration) { material := &InputMaterial{Type: materialType} switch { - case c.subscribedInputs == nil: // Inputs is not defined - c.subscribedInputs = &Inputs{Materials: []*InputMaterial{material}} - case len(c.subscribedInputs.Materials) == 0: // Materials struct is empty - c.subscribedInputs.Materials = []*InputMaterial{material} + case len(c.subscribedMaterials) == 0: // Materials struct is empty + c.subscribedMaterials = []*InputMaterial{material} default: // Materials struct contains data - c.subscribedInputs.Materials = append(c.subscribedInputs.Materials, material) + c.subscribedMaterials = append(c.subscribedMaterials, material) } } } diff --git a/app/controlplane/plugins/sdk/v1/fanout_test.go b/app/controlplane/plugins/sdk/v1/fanout_test.go index db79ec34f..72c03107f 100644 --- a/app/controlplane/plugins/sdk/v1/fanout_test.go +++ b/app/controlplane/plugins/sdk/v1/fanout_test.go @@ -39,81 +39,53 @@ var inputSchema = &sdk.InputSchema{ func TestNewBaseIntegration(t *testing.T) { testCases := []struct { - name string - id string - version string - description string - opts []sdk.NewOpt - errMsg string - wantInput *sdk.Inputs - schema *sdk.InputSchema + name string + id string + version string + description string + opts []sdk.NewOpt + errMsg string + wantMaterials []*sdk.InputMaterial + schema *sdk.InputSchema }{ {name: "invalid - missing id", description: "desc", errMsg: "id is required"}, {name: "invalid - missing version", id: "id", description: "desc", errMsg: "version is required"}, - {name: "invalid - need one input", id: "id", version: "123", description: "description", errMsg: "at least one input"}, - {name: "invalid - missing schema", id: "id", version: "123", description: "description", - opts: []sdk.NewOpt{sdk.WithEnvelope()}, wantInput: &sdk.Inputs{DSSEnvelope: true}, - errMsg: "input schema is required", - }, - {name: "ok - has envelope", id: "id", version: "123", description: "description", - opts: []sdk.NewOpt{sdk.WithEnvelope()}, wantInput: &sdk.Inputs{DSSEnvelope: true}, - schema: inputSchema, - }, - {name: "ok - generic material", id: "id", version: "123", description: "description", - opts: []sdk.NewOpt{sdk.WithInputMaterial(schemaapi.CraftingSchema_Material_MATERIAL_TYPE_UNSPECIFIED)}, - wantInput: &sdk.Inputs{Materials: []*sdk.InputMaterial{{Type: schemaapi.CraftingSchema_Material_MATERIAL_TYPE_UNSPECIFIED}}}, - schema: inputSchema, - }, + {name: "invalid - missing schema", id: "id", version: "123", description: "description", errMsg: "input schema is required"}, + {name: "ok - subscribed to no materials", id: "id", version: "123", description: "description", wantMaterials: []*sdk.InputMaterial{}, schema: inputSchema}, { name: "ok - specific material", id: "id", version: "123", description: "description", opts: []sdk.NewOpt{ sdk.WithInputMaterial(schemaapi.CraftingSchema_Material_JUNIT_XML), }, - wantInput: &sdk.Inputs{Materials: []*sdk.InputMaterial{ + wantMaterials: []*sdk.InputMaterial{ { Type: schemaapi.CraftingSchema_Material_JUNIT_XML, }, - }}, - schema: inputSchema, - }, - { - name: "ok - both material and envelope", id: "id", version: "123", description: "description", - opts: []sdk.NewOpt{ - sdk.WithInputMaterial(schemaapi.CraftingSchema_Material_JUNIT_XML), - sdk.WithEnvelope(), }, - wantInput: &sdk.Inputs{Materials: []*sdk.InputMaterial{ - { - Type: schemaapi.CraftingSchema_Material_JUNIT_XML, - }, - }, DSSEnvelope: true}, schema: inputSchema, }, { - name: "ok - multiple materials and envelope", id: "id", version: "123", description: "description", + name: "ok - multiple materials", id: "id", version: "123", description: "description", opts: []sdk.NewOpt{ sdk.WithInputMaterial(schemaapi.CraftingSchema_Material_JUNIT_XML), sdk.WithInputMaterial(schemaapi.CraftingSchema_Material_CONTAINER_IMAGE), - sdk.WithEnvelope(), }, - wantInput: &sdk.Inputs{Materials: []*sdk.InputMaterial{ + wantMaterials: []*sdk.InputMaterial{ { Type: schemaapi.CraftingSchema_Material_JUNIT_XML, }, { Type: schemaapi.CraftingSchema_Material_CONTAINER_IMAGE, }, - }, DSSEnvelope: true}, + }, schema: inputSchema, }, { - name: "ok - cant have both generic and specific", id: "id", version: "123", description: "description", + name: "ok - cant have both generic/all materials", id: "id", version: "123", description: "description", opts: []sdk.NewOpt{ sdk.WithInputMaterial(schemaapi.CraftingSchema_Material_MATERIAL_TYPE_UNSPECIFIED), - sdk.WithInputMaterial(schemaapi.CraftingSchema_Material_CONTAINER_IMAGE), - sdk.WithEnvelope(), }, - errMsg: "can't subscribe to specific material", + errMsg: "is not a valid material type", schema: inputSchema, }, } @@ -131,7 +103,7 @@ func TestNewBaseIntegration(t *testing.T) { } else { require.NoError(t, err) d := got.Describe() - assert.Equal(t, tc.wantInput, d.SubscribedInputs) + assert.Equal(t, tc.wantMaterials, d.SubscribedMaterials) assert.Equal(t, tc.id, d.ID) } }) @@ -169,23 +141,22 @@ func TestString(t *testing.T) { }{ { name: "with envelope", id: "id", version: "123", - opts: []sdk.NewOpt{sdk.WithEnvelope()}, - want: "id=id, version=123, expectsEnvelope=true, expectedMaterials=[]", + want: "id=id, version=123, expectedMaterials=[]", }, { name: "only material", id: "id", version: "234", opts: []sdk.NewOpt{sdk.WithInputMaterial(schemaapi.CraftingSchema_Material_CONTAINER_IMAGE)}, - want: "id=id, version=234, expectsEnvelope=false, expectedMaterials=[CONTAINER_IMAGE]", + want: "id=id, version=234, expectedMaterials=[CONTAINER_IMAGE]", }, { name: "both material and envelope", id: "id", version: "123", - opts: []sdk.NewOpt{sdk.WithEnvelope(), sdk.WithInputMaterial(schemaapi.CraftingSchema_Material_CONTAINER_IMAGE)}, - want: "id=id, version=123, expectsEnvelope=true, expectedMaterials=[CONTAINER_IMAGE]", + opts: []sdk.NewOpt{sdk.WithInputMaterial(schemaapi.CraftingSchema_Material_CONTAINER_IMAGE)}, + want: "id=id, version=123, expectedMaterials=[CONTAINER_IMAGE]", }, { name: "multiple materials", id: "id", version: "123", opts: []sdk.NewOpt{sdk.WithInputMaterial(schemaapi.CraftingSchema_Material_CONTAINER_IMAGE), sdk.WithInputMaterial(schemaapi.CraftingSchema_Material_JUNIT_XML)}, - want: "id=id, version=123, expectsEnvelope=false, expectedMaterials=[CONTAINER_IMAGE JUNIT_XML]", + want: "id=id, version=123, expectedMaterials=[CONTAINER_IMAGE JUNIT_XML]", }, } @@ -257,7 +228,7 @@ func TestValidateRegistrationRequest(t *testing.T) { &sdk.NewParams{ ID: "ID", Version: "123", InputSchema: &sdk.InputSchema{Registration: ®istrationSchema{}, Attachment: &attachmentSchema{}}, - }, sdk.WithEnvelope()) + }) require.NoError(t, err) payload, err := json.Marshal(tc.input) @@ -273,6 +244,64 @@ func TestValidateRegistrationRequest(t *testing.T) { } } +func TestIsSubscribedTo(t *testing.T) { + testCases := []struct { + name string + subscribedMaterials []schemaapi.CraftingSchema_Material_MaterialType + input string + want bool + }{ + { + name: "empty", + subscribedMaterials: []schemaapi.CraftingSchema_Material_MaterialType{}, + input: "foo", + want: false, + }, + { + name: "not subscribed", + subscribedMaterials: []schemaapi.CraftingSchema_Material_MaterialType{schemaapi.CraftingSchema_Material_CONTAINER_IMAGE}, + input: "foo", + want: false, + }, + { + name: "subscribed", + subscribedMaterials: []schemaapi.CraftingSchema_Material_MaterialType{schemaapi.CraftingSchema_Material_CONTAINER_IMAGE}, + input: "CONTAINER_IMAGE", + want: true, + }, + { + name: "subscribed multiple", + subscribedMaterials: []schemaapi.CraftingSchema_Material_MaterialType{schemaapi.CraftingSchema_Material_CONTAINER_IMAGE, schemaapi.CraftingSchema_Material_JUNIT_XML}, + input: "CONTAINER_IMAGE", + want: true, + }, + { + name: "subscribed multiple 2", + subscribedMaterials: []schemaapi.CraftingSchema_Material_MaterialType{schemaapi.CraftingSchema_Material_CONTAINER_IMAGE, schemaapi.CraftingSchema_Material_JUNIT_XML}, + input: "JUNIT_XML", + want: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + opts := []sdk.NewOpt{} + for _, m := range tc.subscribedMaterials { + opts = append(opts, sdk.WithInputMaterial(m)) + } + + got, err := sdk.NewFanOut( + &sdk.NewParams{ + ID: "ID", Version: "123", + InputSchema: &sdk.InputSchema{Registration: ®istrationSchema{}, Attachment: &attachmentSchema{}}, + }, opts...) + + require.NoError(t, err) + assert.Equal(t, tc.want, got.IsSubscribedTo(tc.input)) + }) + } +} + type attachmentSchema struct { ProjectID int `json:"projectID,omitempty" jsonschema:"oneof_required=projectID,minLength=1"` ProjectName string `json:"projectName,omitempty" jsonschema:"oneof_required=projectName,minLength=1"` @@ -324,7 +353,7 @@ func TestValidateAttachmentRequest(t *testing.T) { &sdk.NewParams{ ID: "ID", Version: "123", InputSchema: &sdk.InputSchema{Registration: ®istrationSchema{}, Attachment: &attachmentSchema{}}, - }, sdk.WithEnvelope()) + }) require.NoError(t, err) payload, err := json.Marshal(tc.input) diff --git a/app/controlplane/plugins/sdk/v1/mocks/FanOut.go b/app/controlplane/plugins/sdk/v1/mocks/FanOut.go index 1d78548f3..39f4545cf 100644 --- a/app/controlplane/plugins/sdk/v1/mocks/FanOut.go +++ b/app/controlplane/plugins/sdk/v1/mocks/FanOut.go @@ -70,6 +70,20 @@ func (_m *FanOut) Execute(ctx context.Context, req *sdk.ExecutionRequest) error return r0 } +// IsSubscribedTo provides a mock function with given fields: materialType +func (_m *FanOut) IsSubscribedTo(materialType string) bool { + ret := _m.Called(materialType) + + var r0 bool + if rf, ok := ret.Get(0).(func(string) bool); ok { + r0 = rf(materialType) + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + // Register provides a mock function with given fields: ctx, req func (_m *FanOut) Register(ctx context.Context, req *sdk.RegistrationRequest) (*sdk.RegistrationResponse, error) { ret := _m.Called(ctx, req) diff --git a/app/controlplane/plugins/sdk/v1/mocks/FanOutExtension.go b/app/controlplane/plugins/sdk/v1/mocks/FanOutPlugin.go similarity index 100% rename from app/controlplane/plugins/sdk/v1/mocks/FanOutExtension.go rename to app/controlplane/plugins/sdk/v1/mocks/FanOutPlugin.go diff --git a/docs/img/fanout-execute-materials.png b/docs/img/fanout-execute-materials.png new file mode 100644 index 000000000..8b9eb9ee1 Binary files /dev/null and b/docs/img/fanout-execute-materials.png differ diff --git a/docs/img/fanout-execute.png b/docs/img/fanout-execute.png new file mode 100644 index 000000000..3edd15b97 Binary files /dev/null and b/docs/img/fanout-execute.png differ