All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.kestra.plugin.aws.sqs.Consume Maven / Gradle / Ivy

The newest version!
package io.kestra.plugin.aws.sqs;

import io.kestra.core.models.annotations.Example;
import io.kestra.core.models.annotations.Plugin;
import io.kestra.core.models.annotations.PluginProperty;
import io.kestra.core.models.executions.metrics.Counter;
import io.kestra.core.models.tasks.RunnableTask;
import io.kestra.core.runners.RunContext;
import io.kestra.core.serializers.FileSerde;
import io.kestra.plugin.aws.sqs.model.Message;
import io.kestra.plugin.aws.sqs.model.SerdeType;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.*;
import lombok.experimental.SuperBuilder;
import reactor.core.publisher.Flux;
import software.amazon.awssdk.services.sqs.SqsAsyncClient;
import software.amazon.awssdk.services.sqs.model.DeleteMessageRequest;
import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.net.URI;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.concurrent.atomic.AtomicInteger;

import static io.kestra.core.utils.Rethrow.throwConsumer;

@SuperBuilder
@ToString
@EqualsAndHashCode
@Getter
@NoArgsConstructor
@Schema(
    title = "Consume messages from a SQS queue.",
    description = "Requires `maxDuration` or `maxRecords`."
)
@Plugin(
    examples = {
        @Example(
            full = true,
            code = """
                id: aws_sqs_consume
                namespace: company.team

                tasks:
                  - id: consume
                    type: io.kestra.plugin.aws.sqs.Consume
                    accessKeyId: ""
                    secretKeyId: ""
                    region: "eu-central-1"
                    queueUrl: "https://sqs.eu-central-1.amazonaws.com/000000000000/test-queue"
                """
        )
    }
)
public class Consume extends AbstractSqs implements RunnableTask {

    @PluginProperty
    @Schema(title = "Maximum number of records; when reached, the task will end.")
    private Integer maxRecords;

    @PluginProperty
    @Schema(title = "Maximum duration in the Duration ISO format, after that the task will end.")
    private Duration maxDuration;

    @Builder.Default
    @PluginProperty
    @NotNull
    @Schema(title = "The serializer/deserializer to use.")
    private SerdeType serdeType = SerdeType.STRING;

    @SuppressWarnings("BusyWait")
    @Override
    public Output run(RunContext runContext) throws Exception {
        var queueUrl = runContext.render(getQueueUrl());
        if (this.maxDuration == null && this.maxRecords == null) {
            throw new IllegalArgumentException("'maxDuration' or 'maxRecords' must be set to avoid an infinite loop");
        }

        try (var sqsClient = this.client(runContext)) {
            var total = new AtomicInteger();
            var started = ZonedDateTime.now();
            var tempFile = runContext.workingDir().createTempFile(".ion").toFile();

            try (var outputFile = new BufferedOutputStream(new FileOutputStream(tempFile))) {
                do {
                    var receiveRequest = ReceiveMessageRequest.builder()
                        .waitTimeSeconds(1) // this would avoid generating too many calls if there are no messages
                        .queueUrl(queueUrl)
                        .build();
                    var msg = sqsClient.receiveMessage(receiveRequest);
                    msg.messages().forEach(throwConsumer(m -> {
                        FileSerde.write(outputFile, serdeType.deserialize(m.body()));
                        sqsClient.deleteMessage(DeleteMessageRequest.builder()
                            .queueUrl(queueUrl)
                            .receiptHandle(m.receiptHandle()).build()
                        );
                        total.getAndIncrement();
                    }));

                    Thread.sleep(100);
                } while (!this.ended(total, started));

                runContext.metric(Counter.of("records", total.get(), "queue", queueUrl));
                outputFile.flush();
            }

            return Output.builder()
                .uri(runContext.storage().putFile(tempFile))
                .count(total.get())
                .build();
        }
    }

    private boolean ended(AtomicInteger count, ZonedDateTime start) {
        if (this.maxRecords != null && count.get() >= this.maxRecords) {
            return true;
        }

        if (this.maxDuration != null && ZonedDateTime.now().toEpochSecond() > start.plus(this.maxDuration).toEpochSecond()) {
            return true;
        }

        return count.get() == 0;
    }

    @Builder
    @Getter
    public static class Output implements io.kestra.core.models.tasks.Output {
        @Schema(
            title = "Number of consumed rows."
        )
        private final Integer count;
        @Schema(
            title = "File URI containing consumed messages."
        )
        private final URI uri;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy