
com.netflix.spinnaker.halyard.cli.services.v1.ResponseUnwrapper Maven / Gradle / Ivy
/*
* Copyright 2016 Google, 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.halyard.cli.services.v1;
import ch.qos.logback.classic.Level;
import com.netflix.spinnaker.halyard.cli.command.v1.GlobalOptions;
import com.netflix.spinnaker.halyard.cli.ui.v1.*;
import com.netflix.spinnaker.halyard.core.DaemonResponse;
import com.netflix.spinnaker.halyard.core.problem.v1.Problem;
import com.netflix.spinnaker.halyard.core.problem.v1.Problem.Severity;
import com.netflix.spinnaker.halyard.core.problem.v1.ProblemSet;
import com.netflix.spinnaker.halyard.core.tasks.v1.DaemonEvent;
import com.netflix.spinnaker.halyard.core.tasks.v1.DaemonTask;
import com.netflix.spinnaker.halyard.core.tasks.v1.DaemonTask.State;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.IntStream;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@Slf4j
public class ResponseUnwrapper {
private static final Long WAIT_MILLIS = 400L;
private static int cycle;
private static String[] cursors = {"_", "-", "^", "*"};
private static boolean interrupted;
public static void interrupt() {
interrupted = true;
}
public static T get(DaemonTask task) {
int lastTaskCount = 0;
task = Daemon.getTask(task.getUuid());
Set loggedEvents = new HashSet<>();
while (!task.getState().isTerminal()) {
updateCycle();
if (interrupted) {
Daemon.interruptTask(task.getUuid());
throw TaskKilledException.interrupted(new InterruptedException("Interrupted by user"));
}
List aggregatedTasks = aggregateTasks(task);
lastTaskCount = formatTasks(aggregatedTasks, lastTaskCount);
logTasks(aggregatedTasks, loggedEvents);
try {
Thread.sleep(WAIT_MILLIS);
} catch (InterruptedException ignored) {
}
task = Daemon.getTask(task.getUuid());
}
List aggregatedTasks = aggregateTasks(task);
formatTasks(aggregatedTasks, lastTaskCount);
logTasks(aggregatedTasks, loggedEvents);
DaemonResponse response = task.getResponse();
formatProblemSet(response.getProblemSet());
switch (task.getState()) {
case TIMED_OUT:
throw TaskKilledException.timeout();
case INTERRUPTED:
throw TaskKilledException.interrupted(task.getFatalError());
case FAILED:
Exception fatal = task.getFatalError();
if (fatal == null) {
throw new RuntimeException("Task failed without reason. This is a bug.");
} else {
throw new ExpectedDaemonFailureException(fatal);
}
default:
return response.getResponseBody();
}
}
private static String formatLoggedDaemonTask(DaemonTask task, DaemonEvent event) {
return "Message from task "
+ task.getName()
+ ": "
+ event.getStage()
+ " - "
+ event.getMessage();
}
private static void logTasks(List tasks, Set loggedEvents) {
// This is expensive, so don't check all tasks to log unless it's necessary.
if (GlobalOptions.getGlobalOptions().getLog() == Level.OFF) {
return;
}
for (DaemonTask task : tasks) {
for (Object oEvent : task.getEvents()) {
DaemonEvent event = (DaemonEvent) oEvent;
String loggedEvent = formatLoggedDaemonTask(task, event);
if (!loggedEvents.contains(loggedEvent)) {
loggedEvents.add(loggedEvent);
log.info(loggedEvent);
}
}
}
}
private static List aggregateTasks(DaemonTask task) {
List result = new ArrayList<>();
task.consumeTaskTree((t) -> result.add((DaemonTask) t));
return result;
}
private static int formatTasks(List tasks, int lastChildCount) {
if (tasks.size() == 0 || GlobalOptions.getGlobalOptions().isQuiet()) {
return tasks.size();
}
int taskCountGrowth = tasks.size() - lastChildCount;
IntStream.range(0, taskCountGrowth * 2).forEach(i -> AnsiPrinter.out.println(""));
AnsiSnippet snippet = new AnsiSnippet("").addMove(AnsiMove.UP, tasks.size() * 2);
AnsiPrinter.out.print(snippet.toString());
for (DaemonTask task : tasks) {
formatLastEvent(task);
}
return tasks.size();
}
private static DaemonEvent getLastEvent(DaemonTask task) {
int eventCount = task.getEvents().size();
DaemonEvent event = null;
if (eventCount > 0) {
event = (DaemonEvent) task.getEvents().get(eventCount - 1);
}
return event;
}
private static void formatLastEvent(DaemonTask task) {
AnsiParagraphBuilder builder = new AnsiParagraphBuilder().setMaxLineWidth(-1);
builder.addSnippet("\r").setErase(AnsiErase.ERASE_LINE);
DaemonEvent event = getLastEvent(task);
State state = task.getState();
String taskName = task.getName();
switch (state) {
case NOT_STARTED:
case RUNNING:
builder
.addSnippet(nextCursor() + " ")
.setForegroundColor(AnsiForegroundColor.BLUE)
.addStyle(AnsiStyle.BOLD);
break;
case SUCCEEDED:
builder
.addSnippet("+ ")
.setForegroundColor(AnsiForegroundColor.GREEN)
.addStyle(AnsiStyle.BOLD);
event = new DaemonEvent().setStage("Success");
break;
case FAILED:
builder
.addSnippet("- ")
.setForegroundColor(AnsiForegroundColor.RED)
.addStyle(AnsiStyle.BOLD);
event = new DaemonEvent().setStage("Failure");
break;
case INTERRUPTED:
builder
.addSnippet("! ")
.setForegroundColor(AnsiForegroundColor.YELLOW)
.addStyle(AnsiStyle.BOLD);
event = new DaemonEvent().setStage("Interrupted");
break;
case TIMED_OUT:
builder
.addSnippet("/ ")
.setForegroundColor(AnsiForegroundColor.YELLOW)
.addStyle(AnsiStyle.BOLD);
event = new DaemonEvent().setStage("Timed out");
break;
}
builder.addSnippet(taskName).addStyle(AnsiStyle.BOLD);
builder.addSnippet("\n");
builder.addSnippet("\r").setErase(AnsiErase.ERASE_LINE);
if (event != null) {
builder.addSnippet(" ");
String stage = event.getStage();
String message = event.getMessage();
builder.addSnippet(stage);
if (!StringUtils.isEmpty(message)) {
builder.addSnippet(": " + message);
}
}
AnsiPrinter.out.println(builder.toString());
}
private static void formatProblemSet(ProblemSet problemSet) {
if (problemSet == null || problemSet.isEmpty()) {
return;
}
AnsiSnippet snippet = new AnsiSnippet("\r").setErase(AnsiErase.ERASE_LINE);
AnsiPrinter.err.print(snippet.toString());
Map> locationGroup = problemSet.groupByLocation();
for (Entry> entry : locationGroup.entrySet()) {
AnsiUi.problemLocation(entry.getKey());
for (Problem problem : entry.getValue()) {
Severity severity = problem.getSeverity();
String message = problem.getMessage();
String remediation = problem.getRemediation();
List options = problem.getOptions();
switch (severity) {
case INFO:
AnsiUi.info(message);
break;
case FATAL:
case ERROR:
AnsiUi.error(message);
break;
case WARNING:
AnsiUi.warning(message);
break;
default:
throw new RuntimeException("Unknown severity level " + severity);
}
if (remediation != null && !remediation.isEmpty()) {
AnsiUi.remediation(remediation);
}
if (options != null && !options.isEmpty()) {
AnsiUi.remediation("Options include: ");
options.forEach(AnsiUi::listRemediation);
}
// Newline between errors
AnsiUi.raw("");
}
}
}
private static String nextCursor() {
return cursors[cycle];
}
private static void updateCycle() {
cycle = (cycle + 1) % cursors.length;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy