mirror of https://github.com/Squidex/squidex.git
Browse Source
* Added kafka Extension which can push events to Kafka Topics. * review comments * kafka testedpull/402/head
committed by
GitHub
8 changed files with 253 additions and 13 deletions
@ -0,0 +1,28 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.ComponentModel.DataAnnotations; |
|||
using Squidex.Domain.Apps.Core.HandleRules; |
|||
using Squidex.Domain.Apps.Core.Rules; |
|||
|
|||
namespace Squidex.Extensions.Actions.Kafka |
|||
{ |
|||
[RuleAction( |
|||
IconImage = "<svg version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 1000 1000' enable-background='new 0 0 1000 1000' xml:space='preserve'><g><path d = 'M674.2,552.7c-38.2,0-72.4,17-95.9,43.6l-60.1-42.5c6.5-17.4,10.1-36.4,10.1-56.1c0-19.5-3.6-38-9.6-55.2l59.9-42c23.5,26.4,57.5,43.4,95.7,43.4c70.4,0,127.7-57.2,127.7-127.7c0-70.4-57.2-127.7-127.7-127.7c-70.4,0-127.7,57.2-127.7,127.7c0,12.5,2,24.8,5.4,36.2l-60.1,42c-25-31.1-61.3-52.8-102.2-59.5v-72.2c57.9-12.3,101.5-63.7,101.5-125C491.1,67.2,433.8,10,363.4,10S235.7,67.2,235.7,137.7c0,60.6,42.5,111.3,99.3,124.5v73.1c-77.8,13.4-136.8,80.9-136.8,162.3c0,81.6,59.7,149.4,137.5,162.5v77.4c-57.2,12.5-100.4,63.7-100.4,124.8c0,70.4,57.2,127.7,127.7,127.7c70.4,0,128.1-57.2,128.1-127.9c0-61-43.2-112.2-100.4-124.8V660c40.2-6.7,75.6-27.9,100.4-58.4l60.4,42.7c-3.4,11.4-5.1,23.5-5.1,36c0,70.4,57.2,127.7,127.7,127.7c70.4,0,127.7-57.2,127.7-127.7C801.6,609.9,744.6,552.7,674.2,552.7L674.2,552.7z M674.2,253.9c34.2,0,61.9,27.7,61.9,61.9c0,34.2-27.7,61.9-61.9,61.9c-34.2,0-62.2-27.7-62.2-61.9C612,281.7,640,253.9,674.2,253.9L674.2,253.9z M301.2,137.7c0-34.2,27.7-61.9,61.9-61.9c34.2,0,61.9,27.7,61.9,61.9s-27.7,61.9-61.9,61.9C329,199.6,301.2,171.7,301.2,137.7L301.2,137.7z M425.1,862.1c0,34.2-27.7,61.9-61.9,61.9c-34.2,0-61.9-27.7-61.9-61.9c0-34.2,27.7-61.9,61.9-61.9C397.4,800.2,425.1,828.1,425.1,862.1L425.1,862.1z M363.2,584c-47.6,0-86.3-38.7-86.3-86.3c0-47.6,38.7-86.3,86.3-86.3c47.6,0,86.3,38.7,86.3,86.3C449.7,545.3,410.8,584,363.2,584L363.2,584z M674.2,742.5c-34.2,0-61.9-27.7-61.9-61.9c0-34.2,27.7-61.9,61.9-61.9c34.2,0,61.9,27.7,61.9,61.9C736.1,714.8,708.2,742.5,674.2,742.5L674.2,742.5z'/></g></svg>", |
|||
IconColor = "#404244", |
|||
Display = "Push to kafka", |
|||
Description = "Connect to Kafka stream and push data to that stream.", |
|||
ReadMore = "https://kafka.apache.org/quickstart")] |
|||
public sealed class KafkaAction : RuleAction |
|||
{ |
|||
[Required] |
|||
[Display(Name = "Topic Name", Description = "The name of the topic.")] |
|||
[DataType(DataType.Text)] |
|||
[Formattable] |
|||
public string TopicName { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Squidex.Domain.Apps.Core.HandleRules; |
|||
using Squidex.Domain.Apps.Core.HandleRules.EnrichedEvents; |
|||
|
|||
namespace Squidex.Extensions.Actions.Kafka |
|||
{ |
|||
public sealed class KafkaActionHandler : RuleActionHandler<KafkaAction, KafkaJob> |
|||
{ |
|||
private const string Description = "Push to Kafka"; |
|||
private readonly KafkaProducer kafkaProducer; |
|||
|
|||
public KafkaActionHandler(RuleEventFormatter formatter, KafkaProducer kafkaProducer) |
|||
: base(formatter) |
|||
{ |
|||
this.kafkaProducer = kafkaProducer; |
|||
} |
|||
|
|||
protected override (string Description, KafkaJob Data) CreateJob(EnrichedEvent @event, KafkaAction action) |
|||
{ |
|||
var ruleJob = new KafkaJob |
|||
{ |
|||
TopicName = action.TopicName, |
|||
MessageKey = @event.Name, |
|||
MessageValue = ToEnvelopeJson(@event) |
|||
}; |
|||
|
|||
return (Description, ruleJob); |
|||
} |
|||
|
|||
protected override async Task<Result> ExecuteJobAsync(KafkaJob job, CancellationToken ct) |
|||
{ |
|||
try |
|||
{ |
|||
await kafkaProducer.Send(job.TopicName, job.MessageKey, job.MessageValue); |
|||
|
|||
return Result.Success($"Event pushed to {job.TopicName} kafka topic."); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
return Result.Failed(ex, "Push to Kafka failed."); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public sealed class KafkaJob |
|||
{ |
|||
public string TopicName { get; set; } |
|||
|
|||
public string MessageKey { get; set; } |
|||
|
|||
public string MessageValue { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.Extensions.Configuration; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using Squidex.Infrastructure.Plugins; |
|||
|
|||
namespace Squidex.Extensions.Actions.Kafka |
|||
{ |
|||
public sealed class KafkaPlugin : IPlugin |
|||
{ |
|||
public void ConfigureServices(IServiceCollection services, IConfiguration config) |
|||
{ |
|||
var kafkaOptions = config.GetSection("kafka").Get<KafkaProducerOptions>(); |
|||
|
|||
if (kafkaOptions.IsProducerConfigured()) |
|||
{ |
|||
services.AddRuleAction<KafkaAction, KafkaActionHandler>(); |
|||
|
|||
services.AddSingleton<KafkaProducer>(); |
|||
services.AddSingleton(Options.Create(kafkaOptions)); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,93 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using Confluent.Kafka; |
|||
using Microsoft.Extensions.Options; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Log; |
|||
|
|||
namespace Squidex.Extensions.Actions.Kafka |
|||
{ |
|||
public sealed class KafkaProducer |
|||
{ |
|||
private readonly IProducer<string, string> producer; |
|||
|
|||
public KafkaProducer(IOptions<KafkaProducerOptions> options, ISemanticLog log) |
|||
{ |
|||
producer = new ProducerBuilder<string, string>(options.Value) |
|||
.SetErrorHandler((p, error) => |
|||
{ |
|||
LogError(log, error); |
|||
}) |
|||
.SetLogHandler((p, message) => |
|||
{ |
|||
LogMessage(log, message); |
|||
}) |
|||
.SetKeySerializer(Serializers.Utf8) |
|||
.SetValueSerializer(Serializers.Utf8) |
|||
.Build(); |
|||
} |
|||
|
|||
private static void LogMessage(ISemanticLog log, LogMessage message) |
|||
{ |
|||
var level = SemanticLogLevel.Information; |
|||
|
|||
switch (message.Level) |
|||
{ |
|||
case SyslogLevel.Emergency: |
|||
level = SemanticLogLevel.Error; |
|||
break; |
|||
case SyslogLevel.Alert: |
|||
level = SemanticLogLevel.Error; |
|||
break; |
|||
case SyslogLevel.Critical: |
|||
level = SemanticLogLevel.Error; |
|||
break; |
|||
case SyslogLevel.Error: |
|||
level = SemanticLogLevel.Error; |
|||
break; |
|||
case SyslogLevel.Warning: |
|||
level = SemanticLogLevel.Warning; |
|||
break; |
|||
case SyslogLevel.Notice: |
|||
level = SemanticLogLevel.Information; |
|||
break; |
|||
case SyslogLevel.Info: |
|||
level = SemanticLogLevel.Information; |
|||
break; |
|||
case SyslogLevel.Debug: |
|||
level = SemanticLogLevel.Debug; |
|||
break; |
|||
} |
|||
|
|||
log.Log<None>(level, default, (_, w) => w |
|||
.WriteProperty("action", "KafkaAction") |
|||
.WriteProperty("name", message.Name) |
|||
.WriteProperty("message", message.Message)); |
|||
} |
|||
|
|||
private static void LogError(ISemanticLog log, Error error) |
|||
{ |
|||
log.LogWarning(w => w |
|||
.WriteProperty("action", "KafkaError") |
|||
.WriteProperty("reason", error.Reason)); |
|||
} |
|||
|
|||
public async Task<DeliveryResult<string, string>> Send(string topicName, string key, string value) |
|||
{ |
|||
var message = new Message<string, string> { Key = key, Value = value }; |
|||
|
|||
return await producer.ProduceAsync(topicName, message); |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
producer?.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Confluent.Kafka; |
|||
|
|||
namespace Squidex.Extensions.Actions.Kafka |
|||
{ |
|||
public class KafkaProducerOptions : ProducerConfig |
|||
{ |
|||
public bool IsProducerConfigured() |
|||
{ |
|||
return !string.IsNullOrWhiteSpace(this.BootstrapServers); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue