From 4e964e119e6399a0034dd178a355fa799f78a16a Mon Sep 17 00:00:00 2001 From: "Baker, Rory" Date: Tue, 27 Feb 2024 16:13:57 -0600 Subject: [PATCH 1/4] Use CDEvents SDK ver. 0.1.2 This commit increments our cdevents-sdk-java dependency from v0.1.0-draft6 to version 0.1.2, which corresponds to [CDEvents spec v0.1.2](https://github.com/cdevents/spec/blob/v0.1.2/spec.md). We encourage users to review the [CDEvents spec repository](https://github.com/cdevents/spec/compare/v0.1.0...v0.1.2) to determine if these changes will have a material impact on your use case. We have also incremented the optional dependency on the AWS SDK plugin for our Kinesis users. Finally, we are targeting a minimum Jenkins release version of 2.401.3. --- CONTRIBUTING.md | 2 + pom.xml | 12 +- .../plugins/cdevents/BuildCDEvent.java | 124 +++++++++++------- .../cdevents/CDEventsGlobalConfig.java | 4 +- .../plugins/cdevents/sinks/KinesisSink.java | 8 +- .../plugins/cdevents/util/OutcomeMapper.java | 10 +- .../plugins/cdevents/BuildCDEvent.java | 10 +- .../cdevents/sinks/KinesisSinkTest.java | 49 ++++--- .../cdevents/sinks/SyslogSinkTest.java | 37 ++++-- 9 files changed, 156 insertions(+), 100 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 092a715..64e3fd7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,6 +20,8 @@ Early adopters and testers are welcome to try out the plugin and provide feedbac ```shell mvn clean hpi:run ``` + +You may need to add `-Dhost=0.0.0.0` to the command if you are running Jenkins in a container or virtual machine. 3. To access your local instance, open a browser to http://localhost:8080/jenkins diff --git a/pom.xml b/pom.xml index 2770be5..179ef1f 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.jenkins-ci.plugins plugin - 4.61 + 4.78 io.jenkins.plugins @@ -36,12 +36,12 @@ 1 999999-SNAPSHOT - v0.1.0-draft6 + 0.1.2 jenkinsci/cdevents-plugin - 2.375.4 + 2.401.3 Max Low @@ -51,7 +51,7 @@ io.jenkins.tools.bom bom-2.401.x - 2143.ve4c3c9ec790a + 2745.vc7b_fe4c876fa_ pom import @@ -81,12 +81,12 @@ org.jenkins-ci.plugins aws-java-sdk - 1.12.447-382.vda_68e2007233 + 1.12.633-430.vf9a_e567a_244f true - dev.cdevents.sdk-java + dev.cdevents cdevents-sdk-java ${cdevents.version} diff --git a/src/main/java/io/jenkins/plugins/cdevents/BuildCDEvent.java b/src/main/java/io/jenkins/plugins/cdevents/BuildCDEvent.java index 12fe47c..8f36b6c 100644 --- a/src/main/java/io/jenkins/plugins/cdevents/BuildCDEvent.java +++ b/src/main/java/io/jenkins/plugins/cdevents/BuildCDEvent.java @@ -7,9 +7,9 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import dev.cdevents.CDEventTypes; +import dev.cdevents.CDEvents; import dev.cdevents.constants.CDEventConstants; -import dev.cdevents.models.PipelineRun; +import dev.cdevents.events.*; import hudson.model.Queue; import hudson.model.Result; import hudson.model.Run; @@ -43,19 +43,31 @@ public static String convertToJson(Object object) { return convertedJson; } - public static CloudEvent buildPipelineRunStartedModel(Run run, TaskListener listener) - throws IOException, InterruptedException { + public static CloudEvent buildPipelineRunStartedModel(Run run, + TaskListener listener) throws IOException, + InterruptedException { String pipelineFullName = run.getParent().getFullDisplayName(); Object pipelineData = ModelBuilder.buildJobModel(run.getParent(), run, listener); LOGGER.log(Level.INFO, "Building PipelineRunStarted model for " + pipelineFullName); - return CDEventTypes.createPipelineRunStartedEvent( - CDEventConstants.CDEventTypes.PipelineRunStartedEvent.getEventType(), run.getId(), - URI.create(run.getParent().getUrl().replaceAll(pipelineFullName, "").replaceAll("//", "/")), - pipelineFullName, URI.create(run.getUrl()), convertToJson(pipelineData)); + + PipelineRunStartedCDEvent event = new PipelineRunStartedCDEvent(); + event.setSubjectSource(URI.create(run.getParent() + .getUrl() + .replaceAll(pipelineFullName, "") + .replaceAll("//", "/"))); + event.setSubjectId(run.getId()); + event.setSource(URI.create(run.getUrl())); + event.setSubjectPipelineName(pipelineFullName); + event.setSubjectUrl(URI.create(run.getUrl())); + event.setCustomData(convertToJson(pipelineData)); + event.setCustomDataContentType("application/json"); + + return CDEvents.cdEventAsCloudEvent(event); } - public static CloudEvent buildPipelineRunFinishedModel(Run run, TaskListener listener) - throws IOException, InterruptedException { + public static CloudEvent buildPipelineRunFinishedModel(Run run, + TaskListener listener) throws IOException, + InterruptedException { String pipelineFullName = run.getParent().getFullDisplayName(); Object pipelineData = ModelBuilder.buildJobModel(run.getParent(), run, listener); LOGGER.log(Level.INFO, "Building PipelineRunFinished model for " + pipelineFullName); @@ -64,31 +76,44 @@ public static CloudEvent buildPipelineRunFinishedModel(Run run, TaskListener lis CDEventConstants.Outcome outcome; Result runResult = run.getResult(); if (runResult != null) { - outcome = OutcomeMapper - .mapResultToOutcome(runResult); - errors = outcome == CDEventConstants.Outcome.OutcomeSuccess ? "" - : run.getBuildStatusSummary().toString(); + outcome = OutcomeMapper.mapResultToOutcome(runResult); + errors = outcome == CDEventConstants.Outcome.SUCCESS ? "" : run.getBuildStatusSummary().toString(); } else { - outcome = CDEventConstants.Outcome.OutcomeError; + outcome = CDEventConstants.Outcome.ERROR; errors = "Run was not able to produce a result."; } - return CDEventTypes.createPipelineRunFinishedEvent( - CDEventConstants.CDEventTypes.PipelineRunFinishedEvent.getEventType(), run.getId(), - URI.create(run.getParent().getUrl().replaceAll(pipelineFullName, "").replaceAll("//", "/")), - pipelineFullName, URI.create(run.getUrl()), outcome, errors, convertToJson(pipelineData)); + PipelineRunFinishedCDEvent event = new PipelineRunFinishedCDEvent(); + + event.setSubjectSource(URI.create(run.getParent() + .getUrl() + .replaceAll(pipelineFullName, "") + .replaceAll("//", "/"))); + event.setSubjectId(run.getId()); + event.setSource(URI.create(run.getUrl())); + event.setSubjectPipelineName(pipelineFullName); + event.setCustomData(convertToJson(pipelineData)); + event.setCustomDataContentType("application/json"); + event.setSubjectOutcome(outcome); + event.setSubjectErrors(errors); + + return CDEvents.cdEventAsCloudEvent(event); } public static CloudEvent buildPipelineRunQueuedModel(Queue.WaitingItem item) { String pipelineFullName = item.task.getFullDisplayName(); Object pipelineData = ModelBuilder.buildQueuedJobModel(item); LOGGER.log(Level.INFO, "Building PipelineRunQueued model for " + pipelineFullName); - // String eventType, String id, URI source, String pipelineName, URI url, String - // pipelineRunData - return CDEventTypes.createPipelineRunQueuedEvent( - CDEventConstants.CDEventTypes.PipelineRunQueuedEvent.getEventType(), String.valueOf(item.getId()), - URI.create(item.task.getUrl()), pipelineFullName, URI.create(item.task.getUrl()), - convertToJson(pipelineData)); + + PipelineRunQueuedCDEvent event = new PipelineRunQueuedCDEvent(); + event.setSubjectSource(URI.create(item.task.getUrl())); + event.setSubjectId(String.valueOf(item.getId())); + event.setSource(URI.create(item.task.getUrl())); + event.setSubjectPipelineName(pipelineFullName); + event.setCustomData(convertToJson(pipelineData)); + event.setCustomDataContentType("application/json"); + + return CDEvents.cdEventAsCloudEvent(event); } public static CloudEvent buildTaskRunStartedModel(Run run, FlowNode node) { @@ -96,14 +121,18 @@ public static CloudEvent buildTaskRunStartedModel(Run run, FlowNode node) { Object taskRunData = ModelBuilder.buildTaskModel(run, node); LOGGER.info("Building TaskRunStarted model for " + displayName); - return CDEventTypes.createTaskRunStartedEvent( - CDEventConstants.CDEventTypes.TaskRunStartedEvent.getEventType(), - run.getId(), - URI.create(run.getParent().getUrl().replaceAll(displayName, "").replaceAll("//", "/")), - displayName, - new PipelineRun(), // TODO - implement this - URI.create(run.getUrl()), - convertToJson(taskRunData)); + TaskRunStartedCDEvent event = new TaskRunStartedCDEvent(); + + event.setSubjectSource(URI.create(run.getParent().getUrl().replaceAll(displayName, "").replaceAll("//", "/"))); + event.setSource(URI.create(run.getUrl())); + event.setSubjectId(run.getId()); + event.setSubjectTaskName(displayName); + event.setSubjectPipelineRunId(run.getId()); + event.setSubjectPipelineRunSource(URI.create(run.getUrl())); + event.setCustomData(convertToJson(taskRunData)); + event.setCustomDataContentType("application/json"); + + return CDEvents.cdEventAsCloudEvent(event); } public static CloudEvent buildTaskRunFinishedModel(Run run, FlowNode node) { @@ -115,24 +144,27 @@ public static CloudEvent buildTaskRunFinishedModel(Run run, FlowNode node) { ErrorAction nodeError = node.getError(); if (nodeError != null) { outcome = OutcomeMapper.mapResultToOutcome(nodeError); - errors = outcome == CDEventConstants.Outcome.OutcomeSuccess ? "" - : nodeError.getDisplayName(); + errors = outcome == CDEventConstants.Outcome.SUCCESS ? "" : nodeError.getDisplayName(); } else { - outcome = CDEventConstants.Outcome.OutcomeError; + outcome = CDEventConstants.Outcome.ERROR; errors = "Unable to get Display Name of the Node Error."; } LOGGER.info("Building TaskRunFinished model for " + displayName); - return CDEventTypes.createTaskRunFinishedEvent( - CDEventConstants.CDEventTypes.TaskRunFinishedEvent.getEventType(), - run.getId(), - URI.create(run.getParent().getUrl().replaceAll(displayName, "").replaceAll("//", "/")), - displayName, - new PipelineRun(), // TODO - implement this - URI.create(run.getUrl()), - outcome, - errors, - convertToJson(taskRunData)); + TaskRunFinishedCDEvent event = new TaskRunFinishedCDEvent(); + event.setSubjectSource(URI.create(run.getParent().getUrl().replaceAll(displayName, "").replaceAll("//", "/"))); + event.setSource(URI.create(run.getUrl())); + event.setSubjectId(run.getId()); + event.setSubjectTaskName(displayName); + event.setSubjectPipelineRunId(run.getId()); + event.setSubjectPipelineRunSource(URI.create(run.getUrl())); + event.setCustomData(convertToJson(taskRunData)); + event.setCustomDataContentType("application/json"); + + event.setSubjectOutcome(outcome); + event.setSubjectErrors(errors); + + return CDEvents.cdEventAsCloudEvent(event); } } diff --git a/src/main/java/io/jenkins/plugins/cdevents/CDEventsGlobalConfig.java b/src/main/java/io/jenkins/plugins/cdevents/CDEventsGlobalConfig.java index 08f150e..9cdfcae 100644 --- a/src/main/java/io/jenkins/plugins/cdevents/CDEventsGlobalConfig.java +++ b/src/main/java/io/jenkins/plugins/cdevents/CDEventsGlobalConfig.java @@ -113,9 +113,9 @@ public FormValidation doCheckKinesisRegion(@QueryParameter("kinesisRegion") Stri return FormValidation.ok(); } - public FormValidation doCheckKinesisEndpoint(@QueryParameter("kinesisEndpoint") String kinesisEndpoint, @QueryParameter("kinesisRegion") String kinesisRegion) { + public FormValidation doCheckKinesisEndpoint(@QueryParameter("kinesisEndpoint") String kinesisEndpoint, @QueryParameter("kinesisRegion") String kinesisRegion) throws FormValidation { if (!isNullOrEmpty(kinesisEndpoint) && isNullOrEmpty(kinesisRegion)) { - FormValidation.error("Kinesis requires a defined region for a custom endpoint"); + throw FormValidation.error("Kinesis requires a defined region for a custom endpoint"); } return FormValidation.ok(); } diff --git a/src/main/java/io/jenkins/plugins/cdevents/sinks/KinesisSink.java b/src/main/java/io/jenkins/plugins/cdevents/sinks/KinesisSink.java index 97333d4..b295da3 100644 --- a/src/main/java/io/jenkins/plugins/cdevents/sinks/KinesisSink.java +++ b/src/main/java/io/jenkins/plugins/cdevents/sinks/KinesisSink.java @@ -22,10 +22,10 @@ public class KinesisSink extends CDEventsSink { public static final Logger LOGGER = Logger.getLogger(KinesisSink.class.getName()); - public volatile static AmazonKinesis kinesis; - public volatile static String streamName; - public volatile static String region; - public volatile static String endpoint; + private volatile static AmazonKinesis kinesis; + private volatile static String streamName; + private volatile static String region; + private volatile static String endpoint; public KinesisSink() { if (Jenkins.get().getPlugin("aws-java-sdk") == null diff --git a/src/main/java/io/jenkins/plugins/cdevents/util/OutcomeMapper.java b/src/main/java/io/jenkins/plugins/cdevents/util/OutcomeMapper.java index 60c26e4..8aa05b1 100644 --- a/src/main/java/io/jenkins/plugins/cdevents/util/OutcomeMapper.java +++ b/src/main/java/io/jenkins/plugins/cdevents/util/OutcomeMapper.java @@ -18,20 +18,20 @@ public static CDEventConstants.Outcome mapResultToOutcome(Result result) { switch (result.toString().toUpperCase()) { case "SUCCESS": case "UNSTABLE": - return CDEventConstants.Outcome.OutcomeSuccess; + return CDEventConstants.Outcome.SUCCESS; case "NOT_BUILT": case "ABORTED": - return CDEventConstants.Outcome.OutcomeFailure; + return CDEventConstants.Outcome.FAILURE; default: // Jenkins status FAILURE and all others - return CDEventConstants.Outcome.OutcomeError; + return CDEventConstants.Outcome.ERROR; } } public static CDEventConstants.Outcome mapResultToOutcome(ErrorAction error) { if (error != null) { - return CDEventConstants.Outcome.OutcomeError; + return CDEventConstants.Outcome.ERROR; } else { - return CDEventConstants.Outcome.OutcomeSuccess; + return CDEventConstants.Outcome.SUCCESS; } } } diff --git a/src/test/java/io/jenkins/plugins/cdevents/BuildCDEvent.java b/src/test/java/io/jenkins/plugins/cdevents/BuildCDEvent.java index e6243b7..2beeccb 100644 --- a/src/test/java/io/jenkins/plugins/cdevents/BuildCDEvent.java +++ b/src/test/java/io/jenkins/plugins/cdevents/BuildCDEvent.java @@ -1,5 +1,7 @@ package io.jenkins.plugins.cdevents; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; import hudson.model.Job; import hudson.model.Run; import hudson.model.TaskListener; @@ -54,12 +56,16 @@ void buildPipelineRunStartedModel() throws IOException, InterruptedException { when(run.getParent().getFullDisplayName()).thenReturn("TestJob1"); when(run.getParent().getUrl()).thenReturn("http://localhost/job/1"); when(run.getUrl()).thenReturn("http://localhost/job/1/stage/1"); + when(run.getId()).thenReturn("1"); CloudEvent cloudEvent = BuildCDEvent.buildPipelineRunStartedModel(run, taskListener); - assertEquals(Set.of("specversion", "id", "source", "time", "type"), cloudEvent.getAttributeNames()); + assertEquals(Set.of("datacontenttype", "specversion", "id", "source", "time", "type"), cloudEvent.getAttributeNames()); assertEquals("dev.cdevents.pipelinerun.started.0.1.0", cloudEvent.getType()); - assertEquals("TestJob1", cloudEvent.getExtension("pipelinename")); + // assert that the JSON string returned by cloudEvent.getData().toString() contains "pipelineName": "TestJob1" in its key-value pairs + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode jsonNode = objectMapper.readTree(cloudEvent.getData().toBytes()); + assertEquals("TestJob1", jsonNode.get("subject").get("content").get("pipelineName").asText()); } } } \ No newline at end of file diff --git a/src/test/java/io/jenkins/plugins/cdevents/sinks/KinesisSinkTest.java b/src/test/java/io/jenkins/plugins/cdevents/sinks/KinesisSinkTest.java index 9fc6f06..15cbe7f 100644 --- a/src/test/java/io/jenkins/plugins/cdevents/sinks/KinesisSinkTest.java +++ b/src/test/java/io/jenkins/plugins/cdevents/sinks/KinesisSinkTest.java @@ -7,8 +7,8 @@ import com.amazonaws.services.kinesis.AmazonKinesis; import com.amazonaws.services.kinesis.AmazonKinesisClientBuilder; -import dev.cdevents.CDEventTypes; -import dev.cdevents.constants.CDEventConstants; +import dev.cdevents.CDEvents; +import dev.cdevents.events.PipelineRunStartedCDEvent; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.Plugin; import io.cloudevents.CloudEvent; @@ -30,22 +30,13 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.*; -@SuppressFBWarnings(value = {"RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE", "RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE", "RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE"}, +@SuppressFBWarnings(value = {"RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE", + "RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE", "RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE"}, justification = "Tests are just checking that exceptions are not thrown. Feel free to add more robust tests") @ExtendWith(MockitoExtension.class) class KinesisSinkTest { - private final CloudEvent cloudEvent = CDEventTypes.createPipelineRunStartedEvent( - CDEventConstants.CDEventTypes.PipelineRunStartedEvent.getEventType(), - "1", - URI.create("http://localhost:8080/jenkins/job/PipelineTest/1/"), - "unittest", - URI.create("http://localhost:8080/jenkins/job/PipelineTest/1/"), - "{\"userId\":null,\"userName\":null,\"name\":\"PipelineTest\",\"displayName\":" + - "\"PipelineTest\",\"url\":\"job/PipelineTest/\",\"build\":{\"fullUrl\":\"http://local" + - "host:8080/jenkins/job/PipelineTest/16/\",\"number\":16,\"queueId\":8,\"duration\":0," + - "\"status\":null,\"url\":\"job/PipelineTest/16/\",\"displayName\":null,\"parameters\"" + - ":null,\"scmState\":{\"url\":null,\"branch\":null,\"commit\":null}}}"); + private CloudEvent cloudEvent; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private AmazonKinesis mockKinesisClient; @Mock(answer = Answers.RETURNS_DEEP_STUBS) @@ -58,14 +49,33 @@ class KinesisSinkTest { @BeforeEach void setup() { reset(mockClientBuilder, mockKinesisClient, mockJenkins, mockGlobalConfig); + + /*when creating new object of any CDEvent type, the event will be initialized with + context.id, context.type, context.version, context.timestamp and subject.type */ + PipelineRunStartedCDEvent event = new PipelineRunStartedCDEvent(); + + /* set the required context fields to the PipelineRunStartedCDEvent */ + event.setSource(URI.create("http://localhost:8080/jenkins/job/PipelineTest/1/")); + + /* set the required subject fields to the PipelineRunStartedCDEvent */ + event.setSubjectId("1"); + event.setSubjectSource(URI.create("http://localhost:8080/jenkins/job/PipelineTest/1/")); + event.setSubjectUrl(URI.create("http://localhost:8080/jenkins/job/PipelineTest/1/")); + event.setSubjectPipelineName("unittest"); + + event.setCustomDataContentType("application/json"); + event.setCustomData("{\"userId\":null,\"userName\":null,\"name\":\"PipelineTest\",\"displayName\":" + + "\"PipelineTest\",\"url\":\"job/PipelineTest/\",\"build\":{\"fullUrl\":\"http://local" + "host:8080" + "/jenkins/job/PipelineTest/16/\",\"number\":16,\"queueId\":8,\"duration\":0," + "\"status\":null," + "\"url\":\"job/PipelineTest/16/\",\"displayName\":null,\"parameters\"" + ":null,\"scmState\":{\"url" + "\":null,\"branch\":null,\"commit\":null}}}"); + + /* Create a CloudEvent from a PipelineRunStartedCDEvent */ + cloudEvent = CDEvents.cdEventAsCloudEvent(event); } private MockedStatic getMockCDEventsGlobalConfigStatic() { return mockStatic(CDEventsGlobalConfig.class, Answers.RETURNS_DEEP_STUBS); } - private void configureMockCDEventsGlobalConfigStatic( - MockedStatic mockCDEventsGlobalConfigStatic) { + private void configureMockCDEventsGlobalConfigStatic(MockedStatic mockCDEventsGlobalConfigStatic) { mockCDEventsGlobalConfigStatic.when(CDEventsGlobalConfig::get).thenReturn(mockGlobalConfig); when(mockGlobalConfig.getKinesisStreamName()).thenReturn("hello"); } @@ -92,9 +102,7 @@ private void configureMockJenkinsStatic(MockedStatic mockJenkinsStatic) @Test void sinkTest() { - try (MockedStatic mockJenkinsStatic = getMockJenkinsStatic(); - MockedStatic mockCDEventsGlobalConfigStatic = getMockCDEventsGlobalConfigStatic(); - MockedStatic mockClientBuilderStatic = getMockClientBuilderStatic()) { + try (MockedStatic mockJenkinsStatic = getMockJenkinsStatic(); MockedStatic mockCDEventsGlobalConfigStatic = getMockCDEventsGlobalConfigStatic(); MockedStatic mockClientBuilderStatic = getMockClientBuilderStatic()) { configureMockJenkinsStatic(mockJenkinsStatic); configureMockCDEventsGlobalConfigStatic(mockCDEventsGlobalConfigStatic); configureMockClientBuilderStatic(mockClientBuilderStatic); @@ -118,8 +126,7 @@ void constructorFailsNoPlugin() { @Test void constructorFailsNoStreamName() { - try (MockedStatic mockJenkinsStatic = getMockJenkinsStatic(); - MockedStatic mockCDEventsGlobalConfigStatic = getMockCDEventsGlobalConfigStatic()) { + try (MockedStatic mockJenkinsStatic = getMockJenkinsStatic(); MockedStatic mockCDEventsGlobalConfigStatic = getMockCDEventsGlobalConfigStatic()) { configureMockJenkinsStatic(mockJenkinsStatic); configureMockCDEventsGlobalConfigStatic(mockCDEventsGlobalConfigStatic); diff --git a/src/test/java/io/jenkins/plugins/cdevents/sinks/SyslogSinkTest.java b/src/test/java/io/jenkins/plugins/cdevents/sinks/SyslogSinkTest.java index 2ab90a7..9dc4b95 100644 --- a/src/test/java/io/jenkins/plugins/cdevents/sinks/SyslogSinkTest.java +++ b/src/test/java/io/jenkins/plugins/cdevents/sinks/SyslogSinkTest.java @@ -5,8 +5,8 @@ package io.jenkins.plugins.cdevents.sinks; -import dev.cdevents.CDEventTypes; -import dev.cdevents.constants.CDEventConstants; +import dev.cdevents.CDEvents; +import dev.cdevents.events.PipelineRunStartedCDEvent; import io.cloudevents.CloudEvent; import org.junit.jupiter.api.Test; @@ -16,21 +16,30 @@ class SyslogSinkTest { - private final CloudEvent cloudEvent = CDEventTypes.createPipelineRunStartedEvent( - CDEventConstants.CDEventTypes.PipelineRunStartedEvent.getEventType(), - "1", - URI.create("http://localhost:8080/jenkins/job/PipelineTest/1/"), - "unittest", - URI.create("http://localhost:8080/jenkins/job/PipelineTest/1/"), - "{\"userId\":null,\"userName\":null,\"name\":\"PipelineTest\",\"displayName\":" + - "\"PipelineTest\",\"url\":\"job/PipelineTest/\",\"build\":{\"fullUrl\":\"http://local" + - "host:8080/jenkins/job/PipelineTest/16/\",\"number\":16,\"queueId\":8,\"duration\":0," + - "\"status\":null,\"url\":\"job/PipelineTest/16/\",\"displayName\":null,\"parameters\"" + - ":null,\"scmState\":{\"url\":null,\"branch\":null,\"commit\":null}}}"); - @Test void sendCloudEvent_works() { SyslogSink syslogSink = new SyslogSink(); + + /*when creating new object of any CDEvent type, the event will be initialized with + context.id, context.type, context.version, context.timestamp and subject.type */ + PipelineRunStartedCDEvent event = new PipelineRunStartedCDEvent(); + + /* set the required context fields to the PipelineRunStartedCDEvent */ + event.setSource(URI.create("http://localhost:8080/jenkins/job/PipelineTest/1/")); + + /* set the required subject fields to the PipelineRunStartedCDEvent */ + event.setSubjectId("1"); + event.setSubjectSource(URI.create("http://localhost:8080/jenkins/job/PipelineTest/1/")); + event.setSubjectUrl(URI.create("http://localhost:8080/jenkins/job/PipelineTest/1/")); + event.setSubjectPipelineName("unittest"); + + event.setCustomDataContentType("application/json"); + event.setCustomData("{\"userId\":null,\"userName\":null,\"name\":\"PipelineTest\",\"displayName\":" + + "\"PipelineTest\",\"url\":\"job/PipelineTest/\",\"build\":{\"fullUrl\":\"http://local" + "host:8080" + "/jenkins/job/PipelineTest/16/\",\"number\":16,\"queueId\":8,\"duration\":0," + "\"status\":null," + "\"url\":\"job/PipelineTest/16/\",\"displayName\":null,\"parameters\"" + ":null,\"scmState\":{\"url" + "\":null,\"branch\":null,\"commit\":null}}}"); + + /* Create a CloudEvent from a PipelineRunStartedCDEvent */ + CloudEvent cloudEvent = CDEvents.cdEventAsCloudEvent(event); + assertDoesNotThrow(() -> syslogSink.sendCloudEvent(cloudEvent)); } } \ No newline at end of file From 6c9a8e23daa8af0da89ac2bd62e7c3779677c882 Mon Sep 17 00:00:00 2001 From: "Baker, Rory" Date: Tue, 5 Mar 2024 07:31:52 -0600 Subject: [PATCH 2/4] Remove unnecessary JSON conversion from BuildCDEvent.java Co-authored-by: Chris Race --- .../plugins/cdevents/BuildCDEvent.java | 39 +++++++------------ 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/src/main/java/io/jenkins/plugins/cdevents/BuildCDEvent.java b/src/main/java/io/jenkins/plugins/cdevents/BuildCDEvent.java index 8f36b6c..0622d52 100644 --- a/src/main/java/io/jenkins/plugins/cdevents/BuildCDEvent.java +++ b/src/main/java/io/jenkins/plugins/cdevents/BuildCDEvent.java @@ -5,8 +5,6 @@ package io.jenkins.plugins.cdevents; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import dev.cdevents.CDEvents; import dev.cdevents.constants.CDEventConstants; import dev.cdevents.events.*; @@ -15,6 +13,9 @@ import hudson.model.Run; import hudson.model.TaskListener; import io.cloudevents.CloudEvent; +import io.jenkins.plugins.cdevents.models.JobModel; +import io.jenkins.plugins.cdevents.models.QueuedJobModel; +import io.jenkins.plugins.cdevents.models.StageModel; import io.jenkins.plugins.cdevents.util.ModelBuilder; import io.jenkins.plugins.cdevents.util.OutcomeMapper; import org.jenkinsci.plugins.workflow.actions.ErrorAction; @@ -29,25 +30,11 @@ public class BuildCDEvent { private static final Logger LOGGER = Logger.getLogger("BuildCDEvent"); - private static final ObjectMapper objectMapper = new ObjectMapper(); - - public static String convertToJson(Object object) { - String convertedJson = ""; - try { - convertedJson = objectMapper.writeValueAsString(object); - } catch (JsonProcessingException e) { - LOGGER.log(Level.WARNING, - "Failed to convert the build object into JSON with the following error " + e.getMessage()); - e.printStackTrace(); - } - return convertedJson; - } - public static CloudEvent buildPipelineRunStartedModel(Run run, TaskListener listener) throws IOException, InterruptedException { String pipelineFullName = run.getParent().getFullDisplayName(); - Object pipelineData = ModelBuilder.buildJobModel(run.getParent(), run, listener); + JobModel pipelineData = ModelBuilder.buildJobModel(run.getParent(), run, listener); LOGGER.log(Level.INFO, "Building PipelineRunStarted model for " + pipelineFullName); PipelineRunStartedCDEvent event = new PipelineRunStartedCDEvent(); @@ -59,7 +46,7 @@ public static CloudEvent buildPipelineRunStartedModel(Run run, event.setSource(URI.create(run.getUrl())); event.setSubjectPipelineName(pipelineFullName); event.setSubjectUrl(URI.create(run.getUrl())); - event.setCustomData(convertToJson(pipelineData)); + event.setCustomData(pipelineData); event.setCustomDataContentType("application/json"); return CDEvents.cdEventAsCloudEvent(event); @@ -69,7 +56,7 @@ public static CloudEvent buildPipelineRunFinishedModel(Run run, TaskListener listener) throws IOException, InterruptedException { String pipelineFullName = run.getParent().getFullDisplayName(); - Object pipelineData = ModelBuilder.buildJobModel(run.getParent(), run, listener); + JobModel pipelineData = ModelBuilder.buildJobModel(run.getParent(), run, listener); LOGGER.log(Level.INFO, "Building PipelineRunFinished model for " + pipelineFullName); String errors; @@ -92,7 +79,7 @@ public static CloudEvent buildPipelineRunFinishedModel(Run run, event.setSubjectId(run.getId()); event.setSource(URI.create(run.getUrl())); event.setSubjectPipelineName(pipelineFullName); - event.setCustomData(convertToJson(pipelineData)); + event.setCustomData(pipelineData); event.setCustomDataContentType("application/json"); event.setSubjectOutcome(outcome); event.setSubjectErrors(errors); @@ -102,7 +89,7 @@ public static CloudEvent buildPipelineRunFinishedModel(Run run, public static CloudEvent buildPipelineRunQueuedModel(Queue.WaitingItem item) { String pipelineFullName = item.task.getFullDisplayName(); - Object pipelineData = ModelBuilder.buildQueuedJobModel(item); + QueuedJobModel pipelineData = ModelBuilder.buildQueuedJobModel(item); LOGGER.log(Level.INFO, "Building PipelineRunQueued model for " + pipelineFullName); PipelineRunQueuedCDEvent event = new PipelineRunQueuedCDEvent(); @@ -110,7 +97,7 @@ public static CloudEvent buildPipelineRunQueuedModel(Queue.WaitingItem item) { event.setSubjectId(String.valueOf(item.getId())); event.setSource(URI.create(item.task.getUrl())); event.setSubjectPipelineName(pipelineFullName); - event.setCustomData(convertToJson(pipelineData)); + event.setCustomData(pipelineData); event.setCustomDataContentType("application/json"); return CDEvents.cdEventAsCloudEvent(event); @@ -118,7 +105,7 @@ public static CloudEvent buildPipelineRunQueuedModel(Queue.WaitingItem item) { public static CloudEvent buildTaskRunStartedModel(Run run, FlowNode node) { String displayName = run.getParent().getFullDisplayName(); - Object taskRunData = ModelBuilder.buildTaskModel(run, node); + StageModel taskRunData = ModelBuilder.buildTaskModel(run, node); LOGGER.info("Building TaskRunStarted model for " + displayName); TaskRunStartedCDEvent event = new TaskRunStartedCDEvent(); @@ -129,7 +116,7 @@ public static CloudEvent buildTaskRunStartedModel(Run run, FlowNode node) { event.setSubjectTaskName(displayName); event.setSubjectPipelineRunId(run.getId()); event.setSubjectPipelineRunSource(URI.create(run.getUrl())); - event.setCustomData(convertToJson(taskRunData)); + event.setCustomData(taskRunData); event.setCustomDataContentType("application/json"); return CDEvents.cdEventAsCloudEvent(event); @@ -137,7 +124,7 @@ public static CloudEvent buildTaskRunStartedModel(Run run, FlowNode node) { public static CloudEvent buildTaskRunFinishedModel(Run run, FlowNode node) { String displayName = run.getParent().getFullDisplayName(); - Object taskRunData = ModelBuilder.buildTaskModel(run, node); + StageModel taskRunData = ModelBuilder.buildTaskModel(run, node); String errors; CDEventConstants.Outcome outcome; @@ -159,7 +146,7 @@ public static CloudEvent buildTaskRunFinishedModel(Run run, FlowNode node) { event.setSubjectTaskName(displayName); event.setSubjectPipelineRunId(run.getId()); event.setSubjectPipelineRunSource(URI.create(run.getUrl())); - event.setCustomData(convertToJson(taskRunData)); + event.setCustomData(taskRunData); event.setCustomDataContentType("application/json"); event.setSubjectOutcome(outcome); From 3057896d0554313baf06946927f1141528b5651a Mon Sep 17 00:00:00 2001 From: "Baker, Rory" Date: Tue, 5 Mar 2024 07:33:55 -0600 Subject: [PATCH 3/4] Apply suggestions from code review Co-authored-by: Race, Chris Signed-off-by: Baker, Rory --- .../io/jenkins/plugins/cdevents/BuildCDEvent.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/main/java/io/jenkins/plugins/cdevents/BuildCDEvent.java b/src/main/java/io/jenkins/plugins/cdevents/BuildCDEvent.java index 0622d52..da579e5 100644 --- a/src/main/java/io/jenkins/plugins/cdevents/BuildCDEvent.java +++ b/src/main/java/io/jenkins/plugins/cdevents/BuildCDEvent.java @@ -38,10 +38,6 @@ public static CloudEvent buildPipelineRunStartedModel(Run run, LOGGER.log(Level.INFO, "Building PipelineRunStarted model for " + pipelineFullName); PipelineRunStartedCDEvent event = new PipelineRunStartedCDEvent(); - event.setSubjectSource(URI.create(run.getParent() - .getUrl() - .replaceAll(pipelineFullName, "") - .replaceAll("//", "/"))); event.setSubjectId(run.getId()); event.setSource(URI.create(run.getUrl())); event.setSubjectPipelineName(pipelineFullName); @@ -72,10 +68,6 @@ public static CloudEvent buildPipelineRunFinishedModel(Run run, PipelineRunFinishedCDEvent event = new PipelineRunFinishedCDEvent(); - event.setSubjectSource(URI.create(run.getParent() - .getUrl() - .replaceAll(pipelineFullName, "") - .replaceAll("//", "/"))); event.setSubjectId(run.getId()); event.setSource(URI.create(run.getUrl())); event.setSubjectPipelineName(pipelineFullName); @@ -93,7 +85,6 @@ public static CloudEvent buildPipelineRunQueuedModel(Queue.WaitingItem item) { LOGGER.log(Level.INFO, "Building PipelineRunQueued model for " + pipelineFullName); PipelineRunQueuedCDEvent event = new PipelineRunQueuedCDEvent(); - event.setSubjectSource(URI.create(item.task.getUrl())); event.setSubjectId(String.valueOf(item.getId())); event.setSource(URI.create(item.task.getUrl())); event.setSubjectPipelineName(pipelineFullName); @@ -110,7 +101,6 @@ public static CloudEvent buildTaskRunStartedModel(Run run, FlowNode node) { TaskRunStartedCDEvent event = new TaskRunStartedCDEvent(); - event.setSubjectSource(URI.create(run.getParent().getUrl().replaceAll(displayName, "").replaceAll("//", "/"))); event.setSource(URI.create(run.getUrl())); event.setSubjectId(run.getId()); event.setSubjectTaskName(displayName); @@ -140,7 +130,6 @@ public static CloudEvent buildTaskRunFinishedModel(Run run, FlowNode node) { LOGGER.info("Building TaskRunFinished model for " + displayName); TaskRunFinishedCDEvent event = new TaskRunFinishedCDEvent(); - event.setSubjectSource(URI.create(run.getParent().getUrl().replaceAll(displayName, "").replaceAll("//", "/"))); event.setSource(URI.create(run.getUrl())); event.setSubjectId(run.getId()); event.setSubjectTaskName(displayName); From 1dda2b49ad293c51f09c4e8355104792b47cceb5 Mon Sep 17 00:00:00 2001 From: "Baker, Rory" Date: Tue, 5 Mar 2024 07:37:07 -0600 Subject: [PATCH 4/4] Remove unnecessary stubbing from BuildCDEvent.java test --- src/test/java/io/jenkins/plugins/cdevents/BuildCDEvent.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/io/jenkins/plugins/cdevents/BuildCDEvent.java b/src/test/java/io/jenkins/plugins/cdevents/BuildCDEvent.java index 2beeccb..085ea17 100644 --- a/src/test/java/io/jenkins/plugins/cdevents/BuildCDEvent.java +++ b/src/test/java/io/jenkins/plugins/cdevents/BuildCDEvent.java @@ -54,7 +54,6 @@ void buildPipelineRunStartedModel() throws IOException, InterruptedException { try (MockedStatic modelBuilder = getMockedModelBuilder()) { modelBuilder.when(() -> ModelBuilder.buildJobModel(job, run, taskListener)).thenReturn(new JobModel()); when(run.getParent().getFullDisplayName()).thenReturn("TestJob1"); - when(run.getParent().getUrl()).thenReturn("http://localhost/job/1"); when(run.getUrl()).thenReturn("http://localhost/job/1/stage/1"); when(run.getId()).thenReturn("1");