/*
* Copyright 2008-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.ec2.ui.keypair;
import java.io.IOException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.ui.statushandlers.StatusManager;
import com.amazonaws.eclipse.core.regions.Region;
import com.amazonaws.eclipse.ec2.Ec2Plugin;
import com.amazonaws.eclipse.ec2.keypairs.KeyPairManager;
import com.amazonaws.eclipse.ec2.ui.SelectionTable;
import com.amazonaws.services.ec2.AmazonEC2;
import com.amazonaws.services.ec2.model.DeleteKeyPairRequest;
import com.amazonaws.services.ec2.model.DescribeKeyPairsRequest;
import com.amazonaws.services.ec2.model.DescribeKeyPairsResult;
import com.amazonaws.services.ec2.model.KeyPairInfo;
/**
* Table for selecting key pairs.
*/
public class KeyPairSelectionTable extends SelectionTable {
private final Collection<KeyPairRefreshListener> listeners = new LinkedList<KeyPairRefreshListener>();
@Override
public TreeViewer getViewer() {
return (TreeViewer) super.getViewer();
}
public Action deleteKeyPairAction;
public Action createNewKeyPairAction;
public Action refreshAction;
public Action registerKeyPairAction;
private final String accountId;
private static final KeyPairManager keyPairManager = new KeyPairManager();
private final Image errorImage = Ec2Plugin.getDefault().getImageRegistry().getDescriptor("error").createImage();
private final Image checkImage = Ec2Plugin.getDefault().getImageRegistry().getDescriptor("check").createImage();
public static final String INVALID_KEYPAIR_MESSAGE =
"The selected key pair is missing its private key. " +
"You can associate the private key file with this key pair if you have it, " +
"or you can create a new key pair. ";
/**
* Refreshes the EC2 key pairs displayed.
*/
public void refreshKeyPairs() {
new RefreshKeyPairsThread().start();
}
public synchronized void addRefreshListener(KeyPairRefreshListener listener) {
listeners.add(listener);
}
public synchronized void removeRefreshListener(KeyPairSelectionTable listener) {
listeners.remove(listener);
}
/* (non-Javadoc)
* @see org.eclipse.swt.widgets.Widget#dispose()
*/
@Override
public void dispose() {
errorImage.dispose();
checkImage.dispose();
super.dispose();
}
public void updateActionsForSelection() {
KeyPairInfo selectedKeyPair = getSelectedKeyPair();
refreshAction.setEnabled(true);
createNewKeyPairAction.setEnabled(true);
deleteKeyPairAction.setEnabled(selectedKeyPair != null);
// We only enable the register key pair action if the selected
// key pair doesn't have a private key file registered yet
registerKeyPairAction.setEnabled(false);
if (selectedKeyPair != null) {
String privateKeyFile = keyPairManager.lookupKeyPairPrivateKeyFile(accountId, selectedKeyPair.getKeyName());
registerKeyPairAction.setEnabled(privateKeyFile == null);
}
}
/**
* Label and content provider for the key pair table.
*/
class KeyPairTableProvider extends LabelProvider implements ITreeContentProvider, ITableLabelProvider {
List<KeyPairInfo> keyPairs;
/*
* IStructuredContentProvider Interface
*/
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object)
*/
public Object[] getElements(Object inputElement) {
if (keyPairs == null) return null;
return keyPairs.toArray();
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object)
*/
@SuppressWarnings("unchecked")
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
keyPairs = (List<KeyPairInfo>)newInput;
}
/*
* ITableLabelProvider Interface
*/
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnImage(java.lang.Object, int)
*/
public Image getColumnImage(Object element, int columnIndex) {
if (columnIndex != 0) return null;
if (!(element instanceof KeyPairInfo)) return null;
KeyPairInfo keyPairInfo = (KeyPairInfo)element;
if (keyPairManager.isKeyPairValid(accountId, keyPairInfo.getKeyName())) {
return checkImage;
} else {
return errorImage;
}
}
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnText(java.lang.Object, int)
*/
public String getColumnText(Object element, int columnIndex) {
if (!(element instanceof KeyPairInfo)) {
return "???";
}
KeyPairInfo keyPairInfo = (KeyPairInfo)element;
switch (columnIndex) {
case 0:
return keyPairInfo.getKeyName();
}
return "?";
}
public Object[] getChildren(Object parentElement) {
return new Object[0];
}
public Object getParent(Object element) {
return null;
}
public boolean hasChildren(Object element) {
return false;
}
}
/**
* Simple comparator to sort key pairs by name.
*
* @author Jason Fulghum <fulghum@amazon.com>
*/
class KeyPairComparator extends ViewerComparator {
/* (non-Javadoc)
* @see org.eclipse.jface.viewers.ViewerComparator#compare(org.eclipse.jface.viewers.Viewer, java.lang.Object, java.lang.Object)
*/
@Override
public int compare(Viewer viewer, Object e1, Object e2) {
if (!(e1 instanceof KeyPairInfo && e2 instanceof KeyPairInfo)) {
return 0;
}
KeyPairInfo keyPair1 = (KeyPairInfo)e1;
KeyPairInfo keyPair2 = (KeyPairInfo)e2;
return keyPair1.getKeyName().compareTo(keyPair2.getKeyName());
}
}
/**
* Create a key pair table for the specified account Id.
*/
public KeyPairSelectionTable(Composite parent, String accountId) {
this(parent, accountId, null);
}
/**
* Create a key pair table for the specified account Id and ec2 endpoint.
*/
public KeyPairSelectionTable(Composite parent, String accountId, Region ec2RegionOverride) {
super(parent);
this.accountId = accountId;
KeyPairTableProvider keyPairTableProvider = new KeyPairTableProvider();
viewer.setContentProvider(keyPairTableProvider);
viewer.setLabelProvider(keyPairTableProvider);
viewer.setComparator(new KeyPairComparator());
refreshKeyPairs();
viewer.addSelectionChangedListener(new ISelectionChangedListener() {
public void selectionChanged(SelectionChangedEvent event) {
updateActionsForSelection();
}
});
this.ec2RegionOverride = ec2RegionOverride;
}
/* (non-Javadoc)
* @see com.amazonaws.eclipse.ec2.ui.SelectionTable#createColumns()
*/
@Override
protected void createColumns() {
newColumn("Name", 100);
}
/**
* Sets the list of key pairs that are displayed in the key pair table.
*
* @param keyPairs
* The list of key pairs to display in the key pair table.
*/
private void setInput(final List<KeyPairInfo> keyPairs) {
Display.getDefault().asyncExec(new Runnable() {
public void run() {
final KeyPairInfo previouslySelectedKeyPair = (KeyPairInfo)getSelection();
viewer.setInput(keyPairs);
if (previouslySelectedKeyPair != null) {
for (int i = 0; i < viewer.getTree().getItemCount(); i++) {
KeyPairInfo keyPair = (KeyPairInfo) viewer.getTree().getItem(i).getData();
if (keyPair.getKeyName().equals(previouslySelectedKeyPair.getKeyName())) {
viewer.getTree().select(viewer.getTree().getItem(i));
// Selection listeners aren't notified when we
// select like this, so fire an event.
viewer.getTree().notifyListeners(SWT.Selection, null);
break;
}
}
}
}
});
}
/* (non-Javadoc)
* @see com.amazonaws.eclipse.ec2.ui.SelectionTable#fillContextMenu(org.eclipse.jface.action.IMenuManager)
*/
@Override
protected void fillContextMenu(IMenuManager manager) {
manager.add(refreshAction);
manager.add(new Separator());
manager.add(createNewKeyPairAction);
manager.add(deleteKeyPairAction);
manager.add(registerKeyPairAction);
updateActionsForSelection();
}
/* (non-Javadoc)
* @see com.amazonaws.eclipse.ec2.ui.SelectionTable#makeActions()
*/
@Override
protected void makeActions() {
refreshAction = new Action() {
public void run() {
refreshKeyPairs();
}
};
refreshAction.setText("Refresh");
refreshAction.setToolTipText("Refresh the list of key pairs.");
refreshAction.setImageDescriptor(Ec2Plugin.getDefault().getImageRegistry().getDescriptor("refresh"));
createNewKeyPairAction = new Action() {
public void run() {
CreateKeyPairDialog dialog = new CreateKeyPairDialog(Display.getCurrent().getActiveShell(), accountId);
if (dialog.open() != Dialog.OK) return;
new CreateKeyPairThread(dialog.getKeyPairName(), dialog.getPrivateKeyDirectory()).start();
}
};
createNewKeyPairAction.setText("New Key Pair...");
createNewKeyPairAction.setToolTipText("Create a new key pair.");
createNewKeyPairAction.setImageDescriptor(Ec2Plugin.getDefault().getImageRegistry().getDescriptor("add"));
deleteKeyPairAction = new Action() {
public void run() {
KeyPairInfo keyPair = (KeyPairInfo)getSelection();
new DeleteKeyPairThread(keyPair).start();
}
};
deleteKeyPairAction.setText("Delete Key Pair");
deleteKeyPairAction.setToolTipText("Delete the selected key pair.");
deleteKeyPairAction.setImageDescriptor(Ec2Plugin.getDefault().getImageRegistry().getDescriptor("remove"));
deleteKeyPairAction.setEnabled(false);
registerKeyPairAction = new Action() {
public void run() {
KeyPairInfo keyPair = (KeyPairInfo)getSelection();
FileDialog fileDialog = new FileDialog(getShell(), SWT.OPEN);
fileDialog.setText("Select the existing private key file for " + keyPair.getKeyName());
fileDialog.setFilterExtensions(new String[] { "*.pem", "*.*" });
String privateKeyFile = fileDialog.open();
if (privateKeyFile != null) {
try {
keyPairManager.registerKeyPair(accountId, keyPair.getKeyName(), privateKeyFile);
refreshKeyPairs();
} catch (IOException e) {
String errorMessage = "Unable to register key pair " +
"(" + keyPair.getKeyName() + " => " + privateKeyFile + "): " + e.getMessage();
Status status = new Status(Status.ERROR, Ec2Plugin.PLUGIN_ID, errorMessage, e);
StatusManager.getManager().handle(status, StatusManager.LOG | StatusManager.SHOW);
}
}
}
};
registerKeyPairAction.setText("Select Private Key File...");
registerKeyPairAction.setToolTipText("Register an existing private key with this key pair.");
registerKeyPairAction.setImageDescriptor(Ec2Plugin.getDefault().getImageRegistry().getDescriptor("configure"));
registerKeyPairAction.setEnabled(false);
}
/**
* Returns the user selected key pair.
*
* @return The user selected key pair.
*/
public KeyPairInfo getSelectedKeyPair() {
return (KeyPairInfo)getSelection();
}
/**
* Returns true if, and only if, a valid key pair is selected, which
* includes checking that a private key is registered for that key pair.
*
* @return True if and only if a valid key pair is selected, which includes
* checking that a private key is registered for that key pair.
*/
public boolean isValidKeyPairSelected() {
KeyPairInfo selectedKeyPair = getSelectedKeyPair();
if (selectedKeyPair == null) return false;
return keyPairManager.isKeyPairValid(accountId, selectedKeyPair.getKeyName());
}
/*
* Private Thread subclasses for making EC2 service calls
*/
/**
* Thread for making an EC2 service call to list all key pairs.
*/
private class RefreshKeyPairsThread extends Thread {
/* (non-Javadoc)
* @see java.lang.Thread#run()
*/
@Override
public void run() {
try {
AmazonEC2 ec2 = getAwsEc2Client(KeyPairSelectionTable.this.accountId);
DescribeKeyPairsResult response = ec2.describeKeyPairs(new DescribeKeyPairsRequest());
List<KeyPairInfo> keyPairs = response.getKeyPairs();
setInput(keyPairs);
Display.getDefault().syncExec(new Runnable() {
public void run() {
for ( KeyPairRefreshListener listener : listeners ) {
listener.keyPairsRefreshed();
}
}
});
} catch (Exception e) {
Status status = new Status(IStatus.ERROR, Ec2Plugin.PLUGIN_ID,
"Unable to list key pairs: " + e.getMessage(), e);
StatusManager.getManager().handle(status, StatusManager.LOG);
}
}
}
/**
* Thread for making an EC2 service call to delete a key pair.
*/
private class DeleteKeyPairThread extends Thread {
/** The key pair to delete */
private final KeyPairInfo keyPair;
/**
* Creates a new thread ready to be started to delete the specified key
* pair.
*
* @param keyPair
* The key pair to delete.
*/
public DeleteKeyPairThread(final KeyPairInfo keyPair) {
this.keyPair = keyPair;
}
/* (non-Javadoc)
* @see java.lang.Thread#run()
*/
@Override
public void run() {
try {
DeleteKeyPairRequest request = new DeleteKeyPairRequest();
request.setKeyName(keyPair.getKeyName());
getAwsEc2Client(accountId).deleteKeyPair(request);
} catch (Exception e) {
Status status = new Status(IStatus.ERROR, Ec2Plugin.PLUGIN_ID,
"Unable to delete key pair: " + e.getMessage(), e);
StatusManager.getManager().handle(status, StatusManager.SHOW | StatusManager.LOG);
}
refreshKeyPairs();
}
}
/**
* Thread for making an EC2 service call to create a new key pair.
*/
private class CreateKeyPairThread extends Thread {
/** The name for the new key pair */
private final String name;
/** The directory to store the private key in */
private final String directory;
/**
* Creates a new thread ready to be started to create a new key pair
* with the specified name.
*
* @param name
* The name being requested for the new key pair.
* @param directory
* The directory to save the private key file in.
*/
public CreateKeyPairThread(String name, String directory) {
this.directory = directory;
this.name = name;
}
/* (non-Javadoc)
* @see java.lang.Thread#run()
*/
@Override
public void run() {
try {
keyPairManager.createNewKeyPair(accountId, name, directory, ec2RegionOverride);
} catch (Exception e) {
Status status = new Status(Status.ERROR, Ec2Plugin.PLUGIN_ID,
"Unable to create key pair: " + e.getMessage());
StatusManager.getManager().handle(status, StatusManager.SHOW | StatusManager.LOG);
}
refreshKeyPairs();
}
}
@Override
public void setEc2RegionOverride(Region region) {
super.setEc2RegionOverride(region);
refreshKeyPairs();
}
}