diff --git a/echo-api/src/main/java/com/netflix/spinnaker/echo/api/events/NotificationAgent.java b/echo-api/src/main/java/com/netflix/spinnaker/echo/api/events/NotificationAgent.java new file mode 100644 index 000000000..64f6db941 --- /dev/null +++ b/echo-api/src/main/java/com/netflix/spinnaker/echo/api/events/NotificationAgent.java @@ -0,0 +1,43 @@ +/* + * Copyright 2020 Armory, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.echo.api.events; + +import com.netflix.spinnaker.kork.annotations.Alpha; +import com.netflix.spinnaker.kork.plugins.api.internal.SpinnakerExtensionPoint; +import java.util.Map; +import javax.annotation.Nonnull; + +/** A NotificationAgent handles user-configured pipeline notifications. */ +@Alpha +public interface NotificationAgent extends SpinnakerExtensionPoint { + /** The notification's type (e.g., Slack, SMS, email). */ + @Nonnull + String getNotificationType(); + + /** + * @param notificationConfig - User-defined configuration. Arbitrary key-value pairs can be passed + * to the agent here (e.g., a Slack channel name). + * @param application - The name of the application where the pipeline was run. + * @param event - a Spinnaker {@link Event}. + * @param status - The state of the referenced resource (e.g., failed, complete, etc.). + */ + void sendNotifications( + @Nonnull Map notificationConfig, + @Nonnull String application, + @Nonnull Event event, + @Nonnull String status); +} diff --git a/echo-notifications/src/main/groovy/com/netflix/spinnaker/echo/notification/AbstractEventNotificationAgent.groovy b/echo-notifications/src/main/groovy/com/netflix/spinnaker/echo/notification/AbstractEventNotificationAgent.groovy index 6cad0d346..106212f20 100644 --- a/echo-notifications/src/main/groovy/com/netflix/spinnaker/echo/notification/AbstractEventNotificationAgent.groovy +++ b/echo-notifications/src/main/groovy/com/netflix/spinnaker/echo/notification/AbstractEventNotificationAgent.groovy @@ -19,19 +19,14 @@ package com.netflix.spinnaker.echo.notification import com.fasterxml.jackson.databind.ObjectMapper import com.netflix.spinnaker.echo.api.events.EventListener import com.netflix.spinnaker.echo.api.events.Event -import com.netflix.spinnaker.echo.services.Front50Service +import com.netflix.spinnaker.echo.jackson.EchoObjectMapper import groovy.util.logging.Slf4j -import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Value @Slf4j abstract class AbstractEventNotificationAgent implements EventListener { - @Autowired - Front50Service front50Service - - @Autowired(required = false) - protected ObjectMapper mapper + protected ObjectMapper mapper = EchoObjectMapper.getInstance() @Value('${spinnaker.base-url}') String spinnakerUrl diff --git a/echo-notifications/src/main/groovy/com/netflix/spinnaker/echo/notification/ExtensionNotificationAgent.java b/echo-notifications/src/main/groovy/com/netflix/spinnaker/echo/notification/ExtensionNotificationAgent.java new file mode 100644 index 000000000..632d98c59 --- /dev/null +++ b/echo-notifications/src/main/groovy/com/netflix/spinnaker/echo/notification/ExtensionNotificationAgent.java @@ -0,0 +1,41 @@ +/* + * Copyright 2020 Armory, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.echo.notification; + +import com.netflix.spinnaker.echo.api.events.Event; +import com.netflix.spinnaker.echo.api.events.NotificationAgent; +import java.util.Map; + +public class ExtensionNotificationAgent extends AbstractEventNotificationAgent { + + private final NotificationAgent agent; + + public ExtensionNotificationAgent(NotificationAgent agent) { + this.agent = agent; + } + + @Override + public String getNotificationType() { + return agent.getNotificationType(); + } + + @Override + public void sendNotifications( + Map notification, String application, Event event, Map config, String status) { + agent.sendNotifications(notification, application, event, status); + } +} diff --git a/echo-plugins-test/src/main/kotlin/com/netflix/spinnaker/echo/plugins/NotificationAgentExtension.kt b/echo-plugins-test/src/main/kotlin/com/netflix/spinnaker/echo/plugins/NotificationAgentExtension.kt new file mode 100644 index 000000000..8a483b951 --- /dev/null +++ b/echo-plugins-test/src/main/kotlin/com/netflix/spinnaker/echo/plugins/NotificationAgentExtension.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2020 Armory, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.echo.plugins + +import com.netflix.spinnaker.echo.api.events.Event +import com.netflix.spinnaker.echo.api.events.NotificationAgent +import org.pf4j.Extension + +@Extension +class NotificationAgentExtension : NotificationAgent { + override fun getNotificationType() = "extension_notification" + override fun sendNotifications(notification: MutableMap, application: String, event: Event, status: String) {} +} diff --git a/echo-plugins-test/src/test/kotlin/com/netflix/spinnaker/echo/plugins/test/EchoPluginsFixture.kt b/echo-plugins-test/src/test/kotlin/com/netflix/spinnaker/echo/plugins/test/EchoPluginsFixture.kt index fb6a52f61..2d8e5095e 100644 --- a/echo-plugins-test/src/test/kotlin/com/netflix/spinnaker/echo/plugins/test/EchoPluginsFixture.kt +++ b/echo-plugins-test/src/test/kotlin/com/netflix/spinnaker/echo/plugins/test/EchoPluginsFixture.kt @@ -19,6 +19,7 @@ package com.netflix.spinnaker.echo.plugins.test import com.netflix.spinnaker.echo.Application import com.netflix.spinnaker.echo.plugins.EchoPlugin import com.netflix.spinnaker.echo.plugins.EventListenerExtension +import com.netflix.spinnaker.echo.plugins.NotificationAgentExtension import com.netflix.spinnaker.kork.plugins.SpinnakerPluginManager import com.netflix.spinnaker.kork.plugins.internal.PluginJar import com.netflix.spinnaker.kork.plugins.tck.PluginsTckFixture @@ -40,7 +41,8 @@ class EchoPluginsFixture : PluginsTckFixture, EchoTestService() { final override val versionNotSupportedPlugin: PluginJar override val extensionClassNames: MutableList = mutableListOf( - EventListenerExtension::class.java.name + EventListenerExtension::class.java.name, + NotificationAgentExtension::class.java.name ) final override fun buildPlugin(pluginId: String, systemVersionRequirement: String): PluginJar { diff --git a/echo-plugins-test/src/test/kotlin/com/netflix/spinnaker/echo/plugins/test/EchoPluginsTest.kt b/echo-plugins-test/src/test/kotlin/com/netflix/spinnaker/echo/plugins/test/EchoPluginsTest.kt index d031c9bbc..a56d237b6 100644 --- a/echo-plugins-test/src/test/kotlin/com/netflix/spinnaker/echo/plugins/test/EchoPluginsTest.kt +++ b/echo-plugins-test/src/test/kotlin/com/netflix/spinnaker/echo/plugins/test/EchoPluginsTest.kt @@ -17,6 +17,7 @@ package com.netflix.spinnaker.echo.plugins.test import com.netflix.spinnaker.echo.api.events.EventListener +import com.netflix.spinnaker.echo.api.events.NotificationAgent import com.netflix.spinnaker.kork.plugins.tck.PluginsTck import com.netflix.spinnaker.kork.plugins.tck.serviceFixture import dev.minutest.rootContext @@ -41,6 +42,15 @@ class EchoPluginsTest : PluginsTck() { that(extension).isNotNull() } } + + test("Notification agent extension is loaded into context") { + val eventListeners = applicationContext.getBeansOfType(NotificationAgent::class.java) + val extensionBeanName = "com.netflix.echo.enabled.plugin.NotificationAgentExtension".replace(".", "") + val extension = eventListeners[extensionBeanName] + expect { + that(extension).isNotNull() + } + } } } } diff --git a/echo-web/echo-web.gradle b/echo-web/echo-web.gradle index 1b6f55bcd..94fb0f9cb 100644 --- a/echo-web/echo-web.gradle +++ b/echo-web/echo-web.gradle @@ -40,6 +40,8 @@ dependencies { implementation "com.netflix.spinnaker.fiat:fiat-core:$fiatVersion" implementation "org.springframework.boot:spring-boot-starter-web" implementation "org.springframework.boot:spring-boot-starter-actuator" + implementation "com.netflix.spinnaker.kork:kork-artifacts" + implementation "com.netflix.spinnaker.kork:kork-core" implementation "com.netflix.spinnaker.kork:kork-config" implementation "com.netflix.spinnaker.kork:kork-web" implementation "com.squareup.retrofit:retrofit" diff --git a/echo-core/src/main/java/com/netflix/spinnaker/echo/config/EchoCoreConfig.java b/echo-web/src/main/java/com/netflix/spinnaker/echo/config/EchoCoreConfig.java similarity index 79% rename from echo-core/src/main/java/com/netflix/spinnaker/echo/config/EchoCoreConfig.java rename to echo-web/src/main/java/com/netflix/spinnaker/echo/config/EchoCoreConfig.java index b181320e8..c566cf050 100644 --- a/echo-core/src/main/java/com/netflix/spinnaker/echo/config/EchoCoreConfig.java +++ b/echo-web/src/main/java/com/netflix/spinnaker/echo/config/EchoCoreConfig.java @@ -18,11 +18,18 @@ import com.netflix.spinnaker.config.PluginsAutoConfiguration; import com.netflix.spinnaker.echo.api.events.EventListener; +import com.netflix.spinnaker.echo.api.events.NotificationAgent; import com.netflix.spinnaker.echo.events.EventPropagator; +import com.netflix.spinnaker.echo.notification.ExtensionNotificationAgent; import com.netflix.spinnaker.kork.artifacts.parsing.DefaultJinjavaFactory; import com.netflix.spinnaker.kork.artifacts.parsing.JinjaArtifactExtractor; import com.netflix.spinnaker.kork.artifacts.parsing.JinjavaFactory; import com.netflix.spinnaker.kork.core.RetrySupport; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import lombok.val; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.ApplicationContext; @@ -47,11 +54,19 @@ public EchoCoreConfig(ApplicationContext context) { } @Bean - public EventPropagator propagator() { + public EventPropagator propagator(Optional> notificationAgents) { + EventPropagator instance = new EventPropagator(); for (EventListener e : context.getBeansOfType(EventListener.class).values()) { instance.addListener(e); } + val extensionNotificationAgents = + notificationAgents.orElseGet(ArrayList::new).stream() + .map(ExtensionNotificationAgent::new) + .collect(Collectors.toList()); + for (ExtensionNotificationAgent e : extensionNotificationAgents) { + instance.addListener(e); + } return instance; }