/*
* Copyright 2010-2016 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.elasticbeanstalk.server.ui;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.beans.PojoObservables;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.jface.databinding.swt.SWTObservables;
import org.eclipse.jface.viewers.ColumnWeightData;
import org.eclipse.jface.viewers.TableLayout;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.TableEditor;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.wst.server.ui.wizard.IWizardHandle;
import com.amazonaws.eclipse.core.AwsToolkitCore;
import com.amazonaws.eclipse.core.regions.ServiceAbbreviations;
import com.amazonaws.eclipse.elasticbeanstalk.ConfigurationOptionConstants;
import com.amazonaws.eclipse.elasticbeanstalk.deploy.DeployWizardDataModel;
import com.amazonaws.eclipse.elasticbeanstalk.jobs.LoadResourcesCallback;
import com.amazonaws.eclipse.elasticbeanstalk.jobs.LoadVpcsJob;
import com.amazonaws.eclipse.elasticbeanstalk.util.BeanstalkConstants;
import com.amazonaws.eclipse.elasticbeanstalk.util.OnUiThreadProxyFactory;
import com.amazonaws.services.ec2.AmazonEC2;
import com.amazonaws.services.ec2.model.DescribeSecurityGroupsRequest;
import com.amazonaws.services.ec2.model.DescribeSubnetsRequest;
import com.amazonaws.services.ec2.model.Filter;
import com.amazonaws.services.ec2.model.SecurityGroup;
import com.amazonaws.services.ec2.model.Subnet;
import com.amazonaws.services.ec2.model.Tag;
import com.amazonaws.services.ec2.model.Vpc;
import com.amazonaws.util.StringUtils;
public class DeployWizardVpcConfigurationPage extends AbstractDeployWizardPage {
private final VpcWidgetBuilder vpcWidgetBuilder = new VpcWidgetBuilder();
private static final String VPC_CONFIGURATION_DOC_URL = "https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/vpc.html";
private Composite wizardPageRoot;
private final LoadResourcesCallback<Vpc> loadVpcCallback;
protected DeployWizardVpcConfigurationPage(
DeployWizardDataModel wizardDataModel) {
super(wizardDataModel);
setComplete(false);
this.loadVpcCallback = OnUiThreadProxyFactory.getProxy(
LoadResourcesCallback.class, new LoadVpcsCallback());
}
@Override
public String getPageTitle() {
return "VPC Configuration";
}
@Override
public String getPageDescription() {
return "Configure VPC and subnets for your EC2 instances, and specify VPC security group.";
}
// When entering this page, according to the setup from the previous page,
// such as region and whether using non-default VPC,
// the VPC list will be refreshed, and the availability for the UI
// components will be refreshed as well.
@Override
public void enter() {
super.enter();
if (wizardDataModel.isUseNonDefaultVpc()) {
new LoadVpcsJob(wizardDataModel.getRegion(), loadVpcCallback).schedule();
} else {
wizardDataModel.setVpcId(null);
}
// Set complete true since all the resources are loaded and selected default values.
setComplete(true);
}
@Override
public Composite createComposite(Composite parent, IWizardHandle handle) {
wizardHandle = handle;
setDefaultsInDataModel();
handle.setImageDescriptor(AwsToolkitCore.getDefault()
.getImageRegistry()
.getDescriptor(AwsToolkitCore.IMAGE_AWS_LOGO));
this.wizardPageRoot = new Composite(parent, SWT.NONE);
wizardPageRoot.setLayout(new GridLayout(1, false));
initializeValidators();
vpcWidgetBuilder.buildVpcUiSection(wizardPageRoot);
return wizardPageRoot;
}
private class LoadVpcsCallback implements LoadResourcesCallback<Vpc> {
public void onSuccess(List<Vpc> vpcs) {
createVpcConfigurationSection(vpcs);
}
public void onFailure() {
onInsufficientPermissions();
}
public void onInsufficientPermissions() {
// currently do nothing, and let the caller handle the failure.
}
private void createVpcConfigurationSection(List<Vpc> vpcs) {
vpcWidgetBuilder.refreshVpcConfigurationSection(vpcs);
vpcWidgetBuilder.refreshVpcSectionAvailability();
}
}
/**
* Set the default values for the roles and vpc in the data model to be
* reflected in the UI when the model is bound to a control
*/
private void setDefaultsInDataModel() {
wizardDataModel.setAssociatePublicIpAddress(false);
if (StringUtils.isNullOrEmpty(wizardDataModel.getElbScheme())) {
wizardDataModel
.setElbScheme(BeanstalkConstants.ELB_SCHEME_EXTERNAL);
}
}
private enum CheckboxType {
ELB,
EC2
}
private class VpcWidgetBuilder {
private final String[] SUBNET_TABLE_TITLES = { "Availability Zone",
"Subnet ID", "Cidr Block", "ELB", "EC2" };
private final String[] ELB_SCHEMES = {
BeanstalkConstants.ELB_SCHEME_EXTERNAL,
BeanstalkConstants.ELB_SCHEME_INTERNAL };
private Combo vpcCombo;
private Table subnetsTable;
private Button apiaButton;// Associate Public Ip Address
private Combo securityGroupCombo;
private Combo elbSchemeCombo;
private List<Button> checkboxButtons = new ArrayList<Button>();
// build UI section only, not populating data
public void buildVpcUiSection(Composite composite) {
Composite group = newGroup(composite, "VPC Configuration:");
group.setLayout(new GridLayout(3, false));
createVpcSelectionSection(group);
vpcCombo.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
onVpcSelectionChanged();
}
});
apiaButton = newCheckbox(group, "Associate Public IP Address", 1);
bindAssociatePublicIpAddressButton(apiaButton);
createSubnetSelectionSection(group);
createSecurityGroupSelection(group);
securityGroupCombo.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent event) {
onSecurityGroupSelectionChanged();
}
});
createElbSchemesSelectionSection(group);
bindCombo(elbSchemeCombo, DeployWizardDataModel.ELB_SCHEME);
}
private void onCheckboxSelected(String subnetId, CheckboxType type, boolean selected) {
Set<String> subnets;
switch(type) {
case EC2:
subnets = wizardDataModel.getEc2Subnets();
break;
case ELB:
subnets = wizardDataModel.getElbSubnets();
break;
default:
subnets = new HashSet<String>();
}
if (selected) {
subnets.add(subnetId);
} else {
subnets.remove(subnetId);
}
}
public void refreshVpcSectionAvailability() {
vpcCombo.setEnabled(wizardDataModel.isUseNonDefaultVpc());
subnetsTable.setEnabled(wizardDataModel.isUseNonDefaultVpc());
apiaButton.setEnabled(wizardDataModel.isUseNonDefaultVpc());
elbSchemeCombo.setEnabled(wizardDataModel.isUseNonDefaultVpc()
&& ConfigurationOptionConstants.LOAD_BALANCED_ENV
.equals(wizardDataModel.getEnvironmentType()));
}
private void createSubnetSelectionSection(Composite composite) {
createSubnetsSelectionLabel(composite);
createSubnetsTable(composite);
}
private void createElbSchemesSelectionSection(Composite parent) {
newLabel(parent, "ELB visibility: ", 1)
.setToolTipText("This combo box is only enabled when you are selecting Load Balanced Web Server Environment type.");
elbSchemeCombo = newCombo(parent, 1);
elbSchemeCombo.setItems(ELB_SCHEMES);
newLabel(parent, "Select Internal when load balancing a back-end\nservice that should not be publicly available.", 1,
SWT.LEFT, SWT.BOTTOM);
}
private void createSecurityGroupSelection(Composite parent) {
newLabel(parent, "VPC security group:");
securityGroupCombo = newCombo(parent, 2);
}
public void refreshVpcConfigurationSection(List<Vpc> vpcs) {
List<String> vpcsString = transformVpcList(vpcs);
vpcCombo.setItems(new String[]{});
for (int i = 0; i < vpcsString.size(); ++i) {
vpcCombo.add(vpcsString.get(i));
vpcCombo.setData(vpcsString.get(i), vpcs.get(i));
}
if (!vpcs.isEmpty()) {
vpcCombo.select(0);
}
onVpcSelectionChanged();
}
private void createVpcSelectionSection(Composite composite) {
createVpcSelectionLabel(composite);
newLabel(composite, "VPC:");
vpcCombo = newCombo(composite);
}
private void createVpcSelectionLabel(Composite composite) {
adjustLinkLayout(
newLink(composite,
"Select the VPC to use when creating your environment. "
+ "<a href=\"" + VPC_CONFIGURATION_DOC_URL
+ "\">Learn more</a>."), 3);
}
private void bindAssociatePublicIpAddressButton(Button button) {
IObservableValue apiaObservable = SWTObservables
.observeSelection(button);
IObservableValue observable = PojoObservables.observeValue(
wizardDataModel,
DeployWizardDataModel.ASSOCIATE_PUBLIC_IP_ADDRESS);
getBindingContext().bindValue(apiaObservable, observable);
}
private void createSubnetsSelectionLabel(Composite composite) {
newLabel(
composite,
"Select different subnets for ELB and EC2 instances in your Availability Zone.",
3);
}
private void createSubnetsTable(Composite composite) {
subnetsTable = new Table(composite, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
subnetsTable.setLinesVisible(true);
subnetsTable.setHeaderVisible(true);
TableLayout layout = new TableLayout();
for (int i = 0; i < SUBNET_TABLE_TITLES.length; ++i) {
TableColumn column = new TableColumn(subnetsTable, SWT.NONE);
column.setText(SUBNET_TABLE_TITLES[i]);
layout.addColumnData(new ColumnWeightData(100));
}
GridData data = new GridData(SWT.FILL, SWT.FILL, true, true);
data.heightHint = 100;
data.widthHint = 200;
data.horizontalSpan = 5;
subnetsTable.setLayoutData(data);
subnetsTable.setLayout(layout);
}
private void onVpcSelectionChanged() {
Vpc selectedVpc = (Vpc)vpcCombo.getData(vpcCombo.getItem(vpcCombo.getSelectionIndex()));
// Reset Data Model
wizardDataModel.setVpcId(selectedVpc.getVpcId());
wizardDataModel.getEc2Subnets().clear();
wizardDataModel.getElbSubnets().clear();
// Redraw Subnet table UI
subnetsTable.removeAll();
for (Button button : checkboxButtons) {
if (button != null) button.dispose();
}
AmazonEC2 ec2 = AwsToolkitCore.getClientFactory()
.getEC2ClientByEndpoint(
wizardDataModel.getRegion().getServiceEndpoint(
ServiceAbbreviations.EC2));
List<Subnet> subnets = ec2.describeSubnets(
new DescribeSubnetsRequest().withFilters(new Filter()
.withName("vpc-id").withValues(
wizardDataModel.getVpcId()))).getSubnets();
for (int i = 0; i < subnets.size(); ++i) {
TableItem item = new TableItem(subnetsTable, SWT.CENTER);
final Subnet subnet = subnets.get(i);
item.setText(0, subnet.getAvailabilityZone());
item.setText(1, subnet.getSubnetId());
item.setText(2, subnet.getCidrBlock());
checkboxButtons.add(drawCheckboxOnSubnetTable(item, subnet.getSubnetId(), 3, CheckboxType.ELB,
wizardDataModel.isUseNonDefaultVpc() &&
ConfigurationOptionConstants.LOAD_BALANCED_ENV.equals(wizardDataModel.getEnvironmentType())));
checkboxButtons.add(drawCheckboxOnSubnetTable(item, subnet.getSubnetId(), 4, CheckboxType.EC2, true));
}
// Redraw security group UI
List<SecurityGroup> securityGroups = ec2.describeSecurityGroups(new DescribeSecurityGroupsRequest()
.withFilters(new Filter()
.withName("vpc-id").withValues(wizardDataModel.getVpcId()))).getSecurityGroups();
securityGroupCombo.removeAll();
for (SecurityGroup securityGroup : securityGroups) {
String securityGroupText = securityGroup.getGroupName() + " -- " + securityGroup.getGroupId();
securityGroupCombo.add(securityGroupText);
securityGroupCombo.setData(securityGroupText, securityGroup);
}
securityGroupCombo.select(0);
onSecurityGroupSelectionChanged();
}
private void onSecurityGroupSelectionChanged() {
SecurityGroup securityGroup = (SecurityGroup) securityGroupCombo.getData(
securityGroupCombo.getItem(securityGroupCombo.getSelectionIndex()));
wizardDataModel.setSecurityGroup(securityGroup.getGroupId());
}
private Button drawCheckboxOnSubnetTable(TableItem item, final String subnetId, int columnIndex, final CheckboxType type, final boolean enabled) {
TableEditor editor = new TableEditor(subnetsTable);
Button checkbox = new Button(subnetsTable, SWT.CHECK);
checkbox.setEnabled(enabled);
checkbox.pack();
editor.minimumWidth = checkbox.getSize().x;
editor.horizontalAlignment = SWT.LEFT;
editor.setEditor(checkbox, item, columnIndex);
checkbox.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent e) {
onCheckboxSelected(subnetId, type, ((Button)e.getSource()).getSelection());
}
});
return checkbox;
}
private void bindCombo(Combo combo, String fieldName) {
IObservableValue comboObservable = SWTObservables
.observeSelection(combo);
IObservableValue pojoObservable = PojoObservables.observeValue(
wizardDataModel, fieldName);
getBindingContext().bindValue(comboObservable, pojoObservable);
}
private List<String> transformVpcList(List<Vpc> vpcs) {
List<String> stringVpcs = new ArrayList<String>(vpcs.size());
for (Vpc vpc : vpcs) {
String vpcTextPrefix = getVpcName(vpc);
if (!StringUtils.isNullOrEmpty(vpcTextPrefix)) {
vpcTextPrefix += " -- ";
}
stringVpcs.add(vpcTextPrefix + vpc.getVpcId());
}
return stringVpcs;
}
private String getVpcName(Vpc vpc) {
if (vpc.getTags() != null && !vpc.getTags().isEmpty()) {
for (Tag tag : vpc.getTags()) {
if ("Name".equals(tag.getKey())) {
return tag.getValue();
}
}
}
return "";
}
/**
* DataBindingContext is setup in {@link AbstractDeployWizardPage}
*
* @return The current data binding context
*/
private DataBindingContext getBindingContext() {
return DeployWizardVpcConfigurationPage.this.bindingContext;
}
}
}