/* * Copyright 2012 Amazon Technologies, 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://aws.amazon.com/apache2.0 * * This file 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.amazonaws.eclipse.explorer.cloudformation; import java.util.Arrays; import java.util.Date; import java.util.List; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.action.Action; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Link; import org.eclipse.swt.widgets.TabFolder; import org.eclipse.swt.widgets.TabItem; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.PartInitException; import org.eclipse.ui.forms.IFormColors; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.forms.widgets.ScrolledForm; import org.eclipse.ui.part.EditorPart; import org.eclipse.ui.statushandlers.StatusManager; import com.amazonaws.eclipse.cloudformation.CloudFormationPlugin; import com.amazonaws.eclipse.core.AWSClientFactory; import com.amazonaws.eclipse.core.AwsToolkitCore; import com.amazonaws.eclipse.core.regions.Region; import com.amazonaws.eclipse.core.regions.RegionUtils; import com.amazonaws.eclipse.core.ui.WebLinkListener; import com.amazonaws.services.cloudformation.AmazonCloudFormation; import com.amazonaws.services.cloudformation.model.DescribeStackResourcesRequest; import com.amazonaws.services.cloudformation.model.DescribeStacksRequest; import com.amazonaws.services.cloudformation.model.Stack; import com.amazonaws.services.cloudformation.model.StackResource; public class StackEditor extends EditorPart { private static final String SERVERLESS_REST_API = "ServerlessRestApi"; private static final String SERVERLESS_REST_API_PROD_STAGE = "ServerlessRestApiProdStage"; private static final String[] STABLE_STATES = { "CREATE_COMPLETE", "CREATE_FAILED", "DELETE_COMPLETE", "DELETE_FAILED", "ROLLBACK_COMPLETE", "ROLLBACK_FAILED", "UPDATE_COMPLETE", "UPDATE_ROLLBACK_COMPLETE", "UPDATE_ROLLBACK_FAILED"}; private static final List<String> STABLE_STATE_LIST = Arrays.asList(STABLE_STATES); private StackEditorInput stackEditorInput; private Text stackNameLabel; private Text createdLabel; private Text statusLabel; private Text createTimeoutLabel; private Text statusReasonLabel; private Text lastUpdatedLabel; private Text descriptionLabel; private Text rollbackOnFailureLabel; private Link outputLink; private volatile boolean stackInStableState; private Thread autoRefreshThread; private StackEventsTable stackEventsTable; private StackOutputsTable stackOutputsTable; private StackParametersTable stackParametersTable; private StackResourcesTable stackResourcesTable; private RefreshAction refreshAction; @Override public void doSave(IProgressMonitor monitor) {} @Override public void doSaveAs() {} @Override public void init(IEditorSite site, IEditorInput input) throws PartInitException { setSite(site); setInput(input); setPartName(input.getName()); this.stackEditorInput = (StackEditorInput)input; } @Override public boolean isDirty() { return false; } @Override public boolean isSaveAsAllowed() { return false; } @Override public void createPartControl(Composite parent) { FormToolkit toolkit = new FormToolkit(Display.getDefault()); ScrolledForm form = new ScrolledForm(parent, SWT.V_SCROLL); form.setExpandHorizontal(true); form.setExpandVertical(true); form.setBackground(toolkit.getColors().getBackground()); form.setForeground(toolkit.getColors().getColor(IFormColors.TITLE)); form.setFont(JFaceResources.getHeaderFont()); form.setText(getFormTitle()); toolkit.decorateFormHeading(form.getForm()); form.setImage(AwsToolkitCore.getDefault().getImageRegistry().get(AwsToolkitCore.IMAGE_STACK)); form.getBody().setLayout(new GridLayout()); createSummarySection(form.getBody(), toolkit); createTabsSection(form.getBody(), toolkit); refreshAction = new RefreshAction(); form.getToolBarManager().add(refreshAction); form.getToolBarManager().update(true); new LoadStackSummaryThread().start(); if (stackEditorInput.isAutoRefresh()) { autoRefreshThread = new Thread(new Runnable() { public void run() { while (!isStackInStableState()) { try { Thread.sleep(5 * 1000); refreshAction.run(); } catch (InterruptedException e) { // When exception happens, we leave this thread. return; } } } }); autoRefreshThread.start(); } } private String getFormTitle() { String formTitle = stackEditorInput.getName(); Region region = RegionUtils.getRegionByEndpoint(stackEditorInput.getRegionEndpoint()); if (region != null) { formTitle += " - " + region.getName(); } else { formTitle += " - " + stackEditorInput.getRegionEndpoint(); } return formTitle; } private void createSummarySection(Composite parent, FormToolkit toolkit) { GridDataFactory gridDataFactory = GridDataFactory.swtDefaults() .align(SWT.FILL, SWT.TOP).grab(true, false).minSize(200, SWT.DEFAULT).hint(200, SWT.DEFAULT); Composite composite = toolkit.createComposite(parent, SWT.NONE); composite.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); composite.setLayout(new GridLayout(4, false)); toolkit.createLabel(composite, "Stack Name:"); stackNameLabel = new Text(composite, SWT.READ_ONLY | SWT.NONE); gridDataFactory.applyTo(stackNameLabel); toolkit.createLabel(composite, "Created:"); createdLabel = new Text(composite, SWT.READ_ONLY | SWT.NONE); gridDataFactory.applyTo(createdLabel); toolkit.createLabel(composite, "Status:"); statusLabel = new Text(composite, SWT.READ_ONLY | SWT.NONE); gridDataFactory.applyTo(statusLabel); toolkit.createLabel(composite, "Create Timeout:"); createTimeoutLabel = new Text(composite, SWT.READ_ONLY | SWT.NONE); gridDataFactory.applyTo(createTimeoutLabel); toolkit.createLabel(composite, "Status Reason:"); statusReasonLabel = new Text(composite, SWT.READ_ONLY | SWT.NONE); gridDataFactory.applyTo(statusReasonLabel); toolkit.createLabel(composite, "Last Updated:"); lastUpdatedLabel = new Text(composite, SWT.READ_ONLY | SWT.NONE); gridDataFactory.applyTo(lastUpdatedLabel); toolkit.createLabel(composite, "Rollback on Failure:"); rollbackOnFailureLabel = new Text(composite, SWT.READ_ONLY | SWT.NONE); toolkit.createLabel(composite, ""); toolkit.createLabel(composite, ""); Label l = toolkit.createLabel(composite, "Description:"); gridDataFactory.copy().hint(100, SWT.DEFAULT).minSize(1, SWT.DEFAULT).align(SWT.LEFT, SWT.TOP).grab(false, false).applyTo(l); descriptionLabel = new Text(composite, SWT.READ_ONLY | SWT.NONE | SWT.MULTI | SWT.WRAP); gridDataFactory.copy().span(3, 1).applyTo(descriptionLabel); Label outputL = toolkit.createLabel(composite, "Output:"); gridDataFactory.copy().hint(100, SWT.DEFAULT).minSize(1, SWT.DEFAULT).align(SWT.LEFT, SWT.TOP).grab(false, false).applyTo(outputL); outputLink = new Link(composite, SWT.READ_ONLY | SWT.NONE | SWT.MULTI | SWT.WRAP); outputLink.addListener(SWT.Selection, new WebLinkListener()); gridDataFactory.copy().span(3, 1).applyTo(outputLink); } private void createTabsSection(Composite parent, FormToolkit toolkit) { Composite tabsSection = toolkit.createComposite(parent, SWT.NONE); tabsSection.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); tabsSection.setLayout(new FillLayout()); TabFolder tabFolder = new TabFolder (tabsSection, SWT.BORDER); Rectangle clientArea = parent.getClientArea(); tabFolder.setLocation(clientArea.x, clientArea.y); TabItem eventsTab = new TabItem(tabFolder, SWT.NONE); eventsTab.setText("Events"); stackEventsTable = new StackEventsTable(tabFolder, toolkit, stackEditorInput); eventsTab.setControl(stackEventsTable); TabItem resourcesTab = new TabItem(tabFolder, SWT.NONE); resourcesTab.setText("Resources"); stackResourcesTable = new StackResourcesTable(tabFolder, toolkit, stackEditorInput); resourcesTab.setControl(stackResourcesTable); TabItem parametersTab = new TabItem(tabFolder, SWT.NONE); parametersTab.setText("Parameters"); stackParametersTable = new StackParametersTable(tabFolder, toolkit); parametersTab.setControl(stackParametersTable); TabItem outputsTab = new TabItem(tabFolder, SWT.NONE); outputsTab.setText("Outputs"); stackOutputsTable = new StackOutputsTable(tabFolder, toolkit); outputsTab.setControl(stackOutputsTable); tabFolder.pack(); } @Override public void setFocus() {} @Override public void dispose() { super.dispose(); if (autoRefreshThread != null) { autoRefreshThread.interrupt(); } } private AmazonCloudFormation getClient() { AWSClientFactory clientFactory = AwsToolkitCore.getClientFactory(stackEditorInput.getAccountId()); return clientFactory.getCloudFormationClientByEndpoint(stackEditorInput.getRegionEndpoint()); } public boolean isStackInStableState() { return stackInStableState; } private void setStackInStableState(boolean stackInStableState) { this.stackInStableState = stackInStableState; } private class LoadStackSummaryThread extends Thread { private Stack describeStack() { DescribeStacksRequest request = new DescribeStacksRequest().withStackName(stackEditorInput.getStackName()); List<Stack> stacks = getClient().describeStacks(request).getStacks(); if (stacks.size() == 0) { return new Stack(); } else if (stacks.size() > 1) { throw new RuntimeException("Unexpected number of stacks returned"); } return stacks.get(0); } private String createRestApiProdLink(String restApi, String region, String restApiProdStage) { return String.format("https://%s.execute-api.%s.amazonaws.com/%s", restApi, region, restApiProdStage); } @Override public void run() { try { final Stack stack = describeStack(); setStackInStableState(STABLE_STATE_LIST.contains(stack.getStackStatus())); DescribeStackResourcesRequest request = new DescribeStackResourcesRequest().withStackName(stackEditorInput.getStackName()); final List<StackResource> stackResources = getClient().describeStackResources(request).getStackResources(); Display.getDefault().asyncExec(new Runnable() { public void run() { descriptionLabel.setText(valueOrDefault(stack.getDescription(), "")); lastUpdatedLabel.setText(valueOrDefault(stack.getLastUpdatedTime(), "N/A")); stackNameLabel.setText(stack.getStackName()); statusLabel.setText(stack.getStackStatus()); statusReasonLabel.setText(valueOrDefault(stack.getStackStatusReason(), "")); createdLabel.setText(valueOrDefault(stack.getCreationTime(), "N/A")); createTimeoutLabel.setText(valueOrDefault(stack.getTimeoutInMinutes(), "N/A")); Boolean disableRollback = stack.getDisableRollback(); if (disableRollback != null) disableRollback = !disableRollback; rollbackOnFailureLabel.setText(booleanYesOrNo(disableRollback)); String serverlessRestApi = null; String serverlessRestApiProdStage = null; for (StackResource resource : stackResources) { if (resource.getLogicalResourceId().equals(SERVERLESS_REST_API)) { serverlessRestApi = resource.getPhysicalResourceId(); } if (resource.getLogicalResourceId().equals(SERVERLESS_REST_API_PROD_STAGE)) { serverlessRestApiProdStage = resource.getPhysicalResourceId(); } } if (serverlessRestApi != null && serverlessRestApiProdStage != null) { String region = RegionUtils.getRegionByEndpoint(stackEditorInput.getRegionEndpoint()).getId(); outputLink.setText(createLinkText(createRestApiProdLink( serverlessRestApi, region, serverlessRestApiProdStage))); } stackNameLabel.getParent().layout(); stackNameLabel.getParent().getParent().layout(true); stackOutputsTable.setStackOutputs(stack.getOutputs()); stackParametersTable.setStackParameters(stack.getParameters()); } }); } catch (Exception e) { Status status = new Status(IStatus.WARNING, CloudFormationPlugin.PLUGIN_ID, "Unable to describe stack " + stackEditorInput.getStackName(), e); StatusManager.getManager().handle(status, StatusManager.LOG | StatusManager.SHOW); } } private String booleanYesOrNo(Boolean b) { if (b == null) return ""; if (b == true) return "Yes"; else return "No"; } private String valueOrDefault(Date date, String defaultValue) { if (date != null) return date.toString(); else return defaultValue; } private String valueOrDefault(Integer integer, String defaultValue) { if (integer != null) return integer.toString(); else return defaultValue; } private String valueOrDefault(String value, String defaultValue) { if (value != null) return value; else return defaultValue; } } private class RefreshAction extends Action { public RefreshAction() { this.setText("Refresh"); this.setToolTipText("Refresh stack information"); this.setImageDescriptor(AwsToolkitCore.getDefault().getImageRegistry().getDescriptor(AwsToolkitCore.IMAGE_REFRESH)); } @Override public void run() { new LoadStackSummaryThread().start(); stackEventsTable.refresh(); stackResourcesTable.refresh(); } } private String createLinkText(String url) { return String.format("<a href=\"%s\">%s</a>", url, url); } }