com.netflix.spinnaker.echo.notification.InteractiveNotificationCallbackHandler.groovy Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2020 Netflix, 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.jakewharton.retrofit.Ok3Client
import com.netflix.spinnaker.config.DefaultServiceEndpoint
import com.netflix.spinnaker.config.okhttp3.OkHttpClientProvider;
import com.netflix.spinnaker.echo.api.Notification;
import com.netflix.spinnaker.echo.api.Notification.InteractiveActionCallback;
import com.netflix.spinnaker.kork.web.exceptions.InvalidRequestException;
import com.netflix.spinnaker.kork.web.exceptions.NotFoundException;
import com.netflix.spinnaker.retrofit.Slf4jRetrofitLogger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component
import retrofit.Endpoint;
import retrofit.Endpoints;
import retrofit.RestAdapter;
import retrofit.client.Response;
import retrofit.converter.JacksonConverter;
import retrofit.http.Body;
import retrofit.http.Header;
import retrofit.http.POST;
/**
* Implements the flow of interactive notification processing as described in {@link InteractiveNotificationService}.
*/
@Component
class InteractiveNotificationCallbackHandler {
private final Logger log = LoggerFactory.getLogger(InteractiveNotificationCallbackHandler.class);
private OkHttpClientProvider clientProvider;
private List notificationServices;
private Environment environment;
private Map spinnakerServices = new HashMap<>();
@Autowired
InteractiveNotificationCallbackHandler(
OkHttpClientProvider clientProvider,
List notificationServices,
Environment environment
) {
this.clientProvider = clientProvider;
this.notificationServices = notificationServices;
this.environment = environment;
}
// For access from tests only
InteractiveNotificationCallbackHandler(
List notificationServices,
Map spinnakerServices,
Environment environment
) {
this(null, notificationServices, environment);
this.spinnakerServices = spinnakerServices;
}
/**
* Processes a callback request from the notification service by relaying it to the downstream
* Spinnaker service that originated the message referenced in the payload.
*
* @param source The unique name of the source of the callback (e.g. "slack")
* @param request The request received from the notification service
*/
ResponseEntity processCallback(final String source, RequestEntity request) {
log.debug("Received interactive notification callback request from " + source);
InteractiveNotificationService notificationService = getNotificationService(source);
if (notificationService == null) {
throw new NotFoundException("NotificationService for " + source + " not registered");
}
final Notification.InteractiveActionCallback callback =
notificationService.parseInteractionCallback(request);
SpinnakerService spinnakerService = getSpinnakerService(callback.getServiceId());
log.debug("Routing notification callback to originating service " + callback.getServiceId());
// TODO(lfp): error handling (retries?). I'd like to respond to the message in a thread, but
// have been unable to make that work. Troubleshooting with Slack support.
// TODO(lfp): need to retrieve user's acccounts to pass in X-SPINNAKER-ACCOUNTS
final Response response = spinnakerService.notificationCallback(callback, callback.getUser());
log.debug("Received callback response from downstream Spinnaker service: " + response.toString());
// Allows the notification service implementation to respond to the callback as needed
Optional> outwardResponse =
notificationService.respondToCallback(request);
return outwardResponse.orElse(new ResponseEntity(HttpStatus.OK));
}
private InteractiveNotificationService getNotificationService(String source) {
return notificationServices.find { it ->
it.supportsType(source.toUpperCase())
}
}
private SpinnakerService getSpinnakerService(String serviceId) {
if (!spinnakerServices.containsKey(serviceId)) {
String baseUrl = environment.getProperty(serviceId + ".baseUrl");
if (baseUrl == null) {
throw new InvalidRequestException(
"Base URL for service " + serviceId + " not found in the configuration.");
}
Endpoint endpoint = Endpoints.newFixedEndpoint(baseUrl)
spinnakerServices.put(
serviceId,
new RestAdapter.Builder()
.setEndpoint(endpoint)
.setClient(
new Ok3Client(
clientProvider.getClient(
new DefaultServiceEndpoint(serviceId, endpoint.getUrl())
)
)
)
.setLogLevel(RestAdapter.LogLevel.BASIC)
.setLog(new Slf4jRetrofitLogger(SpinnakerService.class))
.setConverter(new JacksonConverter())
.build()
.create(SpinnakerService.class)
);
}
return spinnakerServices.get(serviceId);
}
interface SpinnakerService {
@POST("/notifications/callback")
Response notificationCallback(
@Body InteractiveActionCallback callback, @Header("X-SPINNAKER-USER") String user);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy