package com.amazonaws.eclipse.codedeploy.deploy.progress;
import java.util.List;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.ProgressIndicator;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TableViewerColumn;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.Text;
import com.amazonaws.eclipse.codedeploy.CodeDeployPlugin;
import com.amazonaws.eclipse.codedeploy.ServiceAPIUtils;
import com.amazonaws.eclipse.codedeploy.explorer.image.CodeDeployExplorerImages;
import com.amazonaws.eclipse.core.AwsToolkitCore;
import com.amazonaws.eclipse.core.regions.Region;
import com.amazonaws.eclipse.core.regions.ServiceAbbreviations;
import com.amazonaws.services.codedeploy.AmazonCodeDeploy;
import com.amazonaws.services.codedeploy.model.DeploymentInfo;
import com.amazonaws.services.codedeploy.model.DeploymentStatus;
import com.amazonaws.services.codedeploy.model.GetDeploymentInstanceRequest;
import com.amazonaws.services.codedeploy.model.GetDeploymentRequest;
import com.amazonaws.services.codedeploy.model.InstanceStatus;
import com.amazonaws.services.codedeploy.model.InstanceSummary;
import com.amazonaws.services.codedeploy.model.LifecycleEvent;
import com.amazonaws.services.codedeploy.model.LifecycleEventStatus;
public class DeploymentProgressTrackerDialog extends Dialog {
private final String deploymentId;
private final String deploymentGroupName;
private final String applicationName;
private final AmazonCodeDeploy client;
/**
* Used as the direct input for the instance table view
*/
private InstanceSummary[] instanceSummaries;
/*
* UI widgets
*/
private Label titleLabel;
private Text latestEventMessageText;
private ProgressIndicator progressIndicator;
private TableViewer instancesTableViewer;
private Label viewDiagnosticLabel;
private static final String[] EVENTS = new String[] {
"ApplicationStop",
"DownloadBundle",
"BeforeInstall",
"Install",
"AfterInstall",
"ApplicationStart",
"ValidateService"
};
private static final int INSTANCE_ID_COL_WIDTH = 150;
private static final int EVENT_COL_WIDTH = 120;
private static final int INSTANCE_TABLE_VIEWER_HEIGHT_HINT = 200;
private static final int REFRESH_INTERVAL_MS = 5 * 1000;
public DeploymentProgressTrackerDialog(Shell parentShell,
String deploymentId, String deploymentGroupName,
String applicationName, Region region) {
super(parentShell);
this.deploymentId = deploymentId;
this.deploymentGroupName = deploymentGroupName;
this.applicationName = applicationName;
String endpoint = region.getServiceEndpoints()
.get(ServiceAbbreviations.CODE_DEPLOY);
this.client = AwsToolkitCore.getClientFactory()
.getCodeDeployClientByEndpoint(endpoint);
}
/**
* To customize the dialog title
*/
@Override
protected void configureShell(Shell newShell) {
super.configureShell(newShell);
newShell.setText(String.format(
"Deploying to %s [%s]", applicationName, deploymentGroupName));
}
/**
* To customize the dialog button
*/
@Override
protected void createButtonsForButtonBar(Composite parent) {
// only create the close button
createButton(parent, IDialogConstants.CANCEL_ID,
"Close", false);
}
@Override
protected Control createDialogArea(Composite parent) {
Composite container = (Composite) super.createDialogArea(parent);
GridLayout layout = new GridLayout(1, false);
layout.marginWidth = 15;
layout.marginHeight = 15;
container.setLayout(layout);
titleLabel = new Label(container, SWT.NONE);
titleLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
titleLabel.setText("Waiting deployment to complete...");
titleLabel.setFont(JFaceResources.getBannerFont());
Group deploymentInfoGroup = new Group(container, SWT.NONE);
deploymentInfoGroup.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
deploymentInfoGroup.setLayout(new GridLayout(1, false));
GridData labelGridData = new GridData(SWT.FILL, SWT.CENTER, true, false);
Label label_Application = new Label(deploymentInfoGroup, SWT.NONE);
label_Application.setText("Application Name: " + applicationName);
label_Application.setLayoutData(labelGridData);
Label label_DeploymentGroup = new Label(deploymentInfoGroup, SWT.NONE);
label_DeploymentGroup.setText("Deployment Group Name: " + deploymentGroupName);
label_DeploymentGroup.setLayoutData(labelGridData);
Label label_DeploymentId = new Label(deploymentInfoGroup, SWT.NONE);
label_DeploymentId.setText("Deployment ID: " + deploymentId);
label_DeploymentId.setLayoutData(labelGridData);
progressIndicator = new ProgressIndicator(container);
progressIndicator.beginAnimatedTask();
progressIndicator.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
latestEventMessageText = new Text(container, SWT.NONE | SWT.WRAP | SWT.READ_ONLY);
GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, false);
gridData.heightHint = 2 * latestEventMessageText.getLineHeight();
latestEventMessageText.setLayoutData(gridData);
latestEventMessageText.setEnabled(false); // gray out the background
setItalicFont(latestEventMessageText);
createDeploymentInstancesTable(container);
loadAndStartUpdatingDeploymentInstancesAsync();
return container;
}
private void updateTitleLabel(final String message) {
Display.getDefault().syncExec(new Runnable() {
public void run() {
if (!titleLabel.isDisposed()) {
titleLabel.setText(message);
}
}
});
}
private void updateLatestEventLabel(final String message) {
Display.getDefault().syncExec(new Runnable() {
public void run() {
if (!latestEventMessageText.isDisposed()) {
latestEventMessageText.setText(message);
}
}
});
}
/**
* @return true if the deployment has transitioned into the given status.
*/
private boolean waitTillDeploymentReachState(DeploymentStatus expectedState) {
try {
while ( this.getContents() != null
&& !this.getContents().isDisposed() ) {
boolean isFinalStatus = false;
DeploymentInfo deploymentInfo = client
.getDeployment(new GetDeploymentRequest()
.withDeploymentId(deploymentId))
.getDeploymentInfo();
String deploymentStatus = deploymentInfo.getStatus();
if (DeploymentStatus.Created.toString().equals(deploymentStatus)
|| DeploymentStatus.Queued.toString().equals(deploymentStatus)
|| DeploymentStatus.InProgress.toString().equals(deploymentStatus)) {
updateLatestEventLabel("Deployment status - " + deploymentStatus);
}
if (DeploymentStatus.Succeeded.toString().equals(deploymentStatus)
|| DeploymentStatus.Stopped.toString().equals(deploymentStatus)
|| DeploymentStatus.Failed.toString().equals(deploymentStatus)) {
isFinalStatus = true;
StringBuilder sb = new StringBuilder("Deployment " + deploymentStatus);
if (deploymentInfo.getErrorInformation() != null) {
sb.append(String.format(
" (Error Code: %s, Error Message: %s)",
deploymentInfo.getErrorInformation().getCode(),
deploymentInfo.getErrorInformation().getMessage()));
}
updateLatestEventLabel(sb.toString());
if (DeploymentStatus.Succeeded.toString().equals(deploymentStatus)) {
Display.getDefault().syncExec(new Runnable() {
public void run() {
progressIndicator.done();
updateTitleLabel("Deployment complete");
}
});
} else {
Display.getDefault().syncExec(new Runnable() {
public void run() {
progressIndicator.showError();
updateTitleLabel("Deployment didn't finish successfully.");
}
});
}
}
// return true if the expected status
if (expectedState.toString().equals(deploymentStatus)) {
return true;
} else if (isFinalStatus) {
CodeDeployPlugin.getDefault()
.logInfo("Deployment reached final status: " + deploymentStatus);
return false;
}
// Otherwise keep polling
try {
Thread.sleep(REFRESH_INTERVAL_MS);
} catch (InterruptedException e) {
CodeDeployPlugin.getDefault()
.logInfo("Interrupted when polling deployment status");
return false;
}
}
} catch (Exception e) {
CodeDeployPlugin.getDefault().reportException(
"Error when polling deployment status.", e);
}
return false;
}
private void loadAndStartUpdatingDeploymentInstancesAsync() {
new Thread(new Runnable() {
public void run() {
// A short pause before requesting deployment status
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {
}
// Start to load deployment status, and Wait till the deployment
// is "InProgress"
boolean pollInstances = waitTillDeploymentReachState(
DeploymentStatus.InProgress);
if (!pollInstances) {
updateTitleLabel("Deployment didn't finish successfully.");
return;
}
updateLatestEventLabel("Loading all the deployment instances...");
List<InstanceSummary> instances = ServiceAPIUtils
.getAllDeploymentInstances(client, deploymentId);
CodeDeployPlugin.getDefault().logInfo(
instances.size() + " instances are being deployed.");
synchronized (DeploymentProgressTrackerDialog.this) {
instanceSummaries = instances.toArray(
new InstanceSummary[instances.size()]);
Display.getDefault().syncExec(new Runnable() {
public void run() {
instancesTableViewer.setInput(instanceSummaries);
instancesTableViewer.refresh();
if ( !viewDiagnosticLabel.isDisposed() ) {
viewDiagnosticLabel.setVisible(true);
}
}
});
}
updateLatestEventLabel("Updating deployment lifecycle events...");
updateInstanceLifecycleEvents();
updateLatestEventLabel("All instances have reached final status... " +
"Waiting for the deployment to finish...");
waitTillDeploymentReachState(DeploymentStatus.Succeeded);
}
}).start();
}
private void updateInstanceLifecycleEvents() {
try {
while ( this.getContents() != null
&& !this.getContents().isDisposed() ) {
int pendingInstances = 0;
synchronized (DeploymentProgressTrackerDialog.this) {
if (instanceSummaries == null) {
continue;
}
for (int i = 0; i < instanceSummaries.length; i++) {
InstanceSummary instance = instanceSummaries[i];
if (InstanceStatus.InProgress.toString().equals(instance.getStatus())
|| InstanceStatus.Pending.toString().equals(instance.getStatus())) {
InstanceSummary latestSummary = client.getDeploymentInstance(
new GetDeploymentInstanceRequest()
.withDeploymentId(deploymentId)
.withInstanceId(instance.getInstanceId()))
.getInstanceSummary();
instanceSummaries[i] = latestSummary;
pendingInstances++;
}
}
}
updateLatestEventLabel(String.format(
"Waiting for %d instances to complete...(%d done)",
pendingInstances, instanceSummaries.length - pendingInstances));
if (pendingInstances > 0) {
Display.getDefault().syncExec(new Runnable() {
public void run() {
if ( !instancesTableViewer.getTable().isDisposed() ) {
instancesTableViewer.refresh();
}
}
});
} else {
// All instances have reached the final states
return;
}
try {
Thread.sleep(REFRESH_INTERVAL_MS);
} catch (InterruptedException e) {
System.err.println("Interrupted when polling " +
"lifecycle events from deployment instances.");
}
}
} catch (Exception e) {
CodeDeployPlugin.getDefault().reportException(
"Error when polling lifecycle events from deployment instances.", e);
}
}
private void createDeploymentInstancesTable(Composite container) {
instancesTableViewer = new TableViewer(container,
SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
GridData tableGridData = new GridData(SWT.FILL, SWT.FILL, true, true);
tableGridData.heightHint = INSTANCE_TABLE_VIEWER_HEIGHT_HINT;
instancesTableViewer.getTable().setLayoutData(tableGridData);
final Table table = instancesTableViewer.getTable();
table.setHeaderVisible(true);
table.setLinesVisible(true);
TableViewerColumn instanceIdColumn = new TableViewerColumn(instancesTableViewer, SWT.CENTER);
instanceIdColumn.getColumn().setWidth(INSTANCE_ID_COL_WIDTH);
instanceIdColumn.getColumn().setText("Instance ID");
instanceIdColumn.setLabelProvider(new ColumnLabelProvider() {
@Override
public String getText(Object element) {
InstanceSummary instanceSummary = (InstanceSummary)element;
// The service returns the full ARN of the instance
String instanceArn = instanceSummary.getInstanceId();
int lastSlashIndex = instanceArn.lastIndexOf("/");
String instanceId = lastSlashIndex == -1 ?
instanceArn : instanceArn.substring(lastSlashIndex + 1);
return instanceId;
}
@Override
public Font getFont(Object element) {
// bold font
return JFaceResources.getBannerFont();
}
});
for (String eventName : EVENTS) {
TableViewerColumn eventColumn = new TableViewerColumn(instancesTableViewer, SWT.CENTER);
eventColumn.getColumn().setWidth(EVENT_COL_WIDTH);
eventColumn.getColumn().setText(eventName);
final String eventName_local = eventName;
eventColumn.setLabelProvider(new ColumnLabelProvider() {
@Override
public String getText(Object element) {
InstanceSummary instanceSummary = (InstanceSummary)element;
LifecycleEvent event = ServiceAPIUtils.findLifecycleEventByEventName(
instanceSummary, eventName_local);
return event.getStatus();
}
@Override
public Image getImage(Object element) {
InstanceSummary instanceSummary = (InstanceSummary)element;
LifecycleEvent event = ServiceAPIUtils.findLifecycleEventByEventName(
instanceSummary, eventName_local);
String statusString = event.getStatus();
if (LifecycleEventStatus.Succeeded.toString().equals(statusString)) {
return CodeDeployPlugin.getDefault().getImageRegistry()
.get(CodeDeployExplorerImages.IMG_CHECK_ICON);
}
return null;
}
@Override
public Color getForeground(Object element) {
InstanceSummary instanceSummary = (InstanceSummary)element;
LifecycleEvent event = ServiceAPIUtils.findLifecycleEventByEventName(
instanceSummary, eventName_local);
String statusString = event.getStatus();
if (LifecycleEventStatus.Pending.toString().equals(statusString)
|| LifecycleEventStatus.Unknown.toString().equals(statusString)) {
return Display.getCurrent().getSystemColor(SWT.COLOR_GRAY);
}
if (LifecycleEventStatus.InProgress.toString().equals(statusString)) {
return Display.getCurrent().getSystemColor(SWT.COLOR_BLUE);
}
if (LifecycleEventStatus.Failed.toString().equals(statusString)) {
return Display.getCurrent().getSystemColor(SWT.COLOR_RED);
}
return null;
}
});
}
instancesTableViewer.setContentProvider(ArrayContentProvider.getInstance());
instancesTableViewer.addDoubleClickListener(new IDoubleClickListener() {
public void doubleClick(DoubleClickEvent event) {
StructuredSelection selection = (StructuredSelection) event.getSelection();
Object element = selection.getFirstElement();
if (element instanceof InstanceSummary) {
new InstanceSummaryDetailDialog(
getShell(), (InstanceSummary)element)
.open();
}
}
});
viewDiagnosticLabel = new Label(container, SWT.NONE);
viewDiagnosticLabel.setText("Double-click instance-ID to view the detailed diagnostic information");
setItalicFont(viewDiagnosticLabel);
viewDiagnosticLabel.setVisible(false);
}
private Font italicFont;
private void setItalicFont(Control control) {
FontData[] fontData = control.getFont()
.getFontData();
for (FontData fd : fontData) {
fd.setStyle(SWT.ITALIC);
}
italicFont = new Font(Display.getDefault(), fontData);
control.setFont(italicFont);
}
@Override
public boolean close() {
if (italicFont != null)
italicFont.dispose();
return super.close();
}
}