/*
* Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. 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.lambda.upload.wizard.page;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newComposite;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newFillingLabel;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newGroup;
import static com.amazonaws.eclipse.core.ui.wizards.WizardWidgetFactory.newLink;
import static com.amazonaws.eclipse.lambda.upload.wizard.model.FunctionConfigPageDataModel.P_CREATE_NEW_VERSION_ALIAS;
import static com.amazonaws.eclipse.lambda.upload.wizard.model.FunctionConfigPageDataModel.P_KMS_ENCRYPTION;
import static com.amazonaws.eclipse.lambda.upload.wizard.model.FunctionConfigPageDataModel.P_NONE_ENCRYPTION;
import static com.amazonaws.eclipse.lambda.upload.wizard.model.FunctionConfigPageDataModel.P_PUBLISH_NEW_VERSION;
import static com.amazonaws.eclipse.lambda.upload.wizard.model.FunctionConfigPageDataModel.P_S3_ENCRYPTION;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.databinding.AggregateValidationStatus;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.beans.PojoProperties;
import org.eclipse.core.databinding.observable.ChangeEvent;
import org.eclipse.core.databinding.observable.IChangeListener;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Rectangle;
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 com.amazonaws.eclipse.core.AwsToolkitCore;
import com.amazonaws.eclipse.core.model.AbstractAwsResourceScopeParam.AwsResourceScopeParamBase;
import com.amazonaws.eclipse.core.ui.SelectOrCreateBucketComposite;
import com.amazonaws.eclipse.core.ui.SelectOrCreateKmsKeyComposite;
import com.amazonaws.eclipse.core.widget.CheckboxComplex;
import com.amazonaws.eclipse.core.widget.RadioButtonComplex;
import com.amazonaws.eclipse.core.widget.TextComplex;
import com.amazonaws.eclipse.databinding.RangeValidator;
import com.amazonaws.eclipse.lambda.LambdaConstants;
import com.amazonaws.eclipse.lambda.model.LambdaFunctionAliasesScopeParam;
import com.amazonaws.eclipse.lambda.project.metadata.LambdaFunctionProjectMetadata;
import com.amazonaws.eclipse.lambda.project.metadata.LambdaFunctionProjectMetadata.LambdaFunctionDeploymentMetadata;
import com.amazonaws.eclipse.lambda.project.metadata.LambdaFunctionProjectMetadata.LambdaFunctionMetadata;
import com.amazonaws.eclipse.lambda.ui.SelectOrCreateBasicLambdaRoleComposite;
import com.amazonaws.eclipse.lambda.ui.SelectOrInputFunctionAliasComposite;
import com.amazonaws.eclipse.lambda.upload.wizard.model.FunctionConfigPageDataModel;
import com.amazonaws.eclipse.lambda.upload.wizard.model.UploadFunctionWizardDataModel;
import com.amazonaws.services.lambda.model.FunctionConfiguration;
public class FunctionConfigurationPage extends WizardPageWithOnEnterHook {
private static final int MIN_MEMORY = 128;
private static final int MAX_MEMORY = 3008;
private static final int DEFAULT_MEMORY = 512;
private static final int MIN_TIMEOUT = 1;
private static final int MAX_TIMEOUT = 900;
private static final int DEFAULT_TIMEOUT = 15;
/* Data model and binding */
private UploadFunctionWizardDataModel dataModel;
private final DataBindingContext bindingContext;
private final AggregateValidationStatus aggregateValidationStatus;
/* Basic settings */
private Label functionNameLabel;
private TextComplex descriptionTextComplex;
/* Function execution role setting */
private SelectOrCreateBasicLambdaRoleComposite selectOrCreateBasicLambdaRoleComposite;
/* Function versioning and alias setting */
private CheckboxComplex publishNewVersionCheckbox;
private CheckboxComplex newVersionAliasCheckbox;
private SelectOrInputFunctionAliasComposite selectOrInputFunctionAliasComposite;
/* Target S3 bucket setting */
private SelectOrCreateBucketComposite selectOrCreateBucketComposite;
private RadioButtonComplex noneEncryptionButton;
private RadioButtonComplex amazonS3EncryptionButton;
private RadioButtonComplex awsKmsEncryptionButton;
private SelectOrCreateKmsKeyComposite selectOrCreateKmsKeyComposite;
/* Advanced settings */
private TextComplex memoryTextComplex;
private TextComplex timeoutTextComplex;
/**
* The validation status listener to be registered to the composite
*/
private final IChangeListener functionConfigValidationStatusListener = new IChangeListener() {
@Override
public void handleChange(ChangeEvent event) {
Object observable = event.getObservable();
if (observable instanceof AggregateValidationStatus == false) return;
AggregateValidationStatus statusObservable = (AggregateValidationStatus)observable;
Object statusObservableValue = statusObservable.getValue();
if (statusObservableValue instanceof IStatus == false) return;
IStatus status = (IStatus)statusObservableValue;
boolean success = (status.getSeverity() == IStatus.OK);
if (success) {
setErrorMessage(null);
} else {
setErrorMessage(status.getMessage());
}
FunctionConfigurationPage.super.setPageComplete(success);
}
};
public FunctionConfigurationPage(UploadFunctionWizardDataModel dataModel) {
super("Function Configuration");
setTitle("Function Configuration");
setDescription("Configure this Lambda function and upload to AWS.");
setPageComplete(false);
this.dataModel = dataModel;
this.bindingContext = new DataBindingContext();
this.aggregateValidationStatus = new AggregateValidationStatus(
bindingContext, AggregateValidationStatus.MAX_SEVERITY);
}
@Override
public void createControl(Composite parent) {
ScrolledComposite scrolledComposite = new ScrolledComposite(parent, SWT.V_SCROLL);
scrolledComposite.setExpandHorizontal(true);
scrolledComposite.setExpandVertical(true);
GridDataFactory.fillDefaults().grab(true, true).applyTo(scrolledComposite);
Composite functionConfigurationComposite = new Composite(scrolledComposite, SWT.NONE);
functionConfigurationComposite.setLayout(new GridLayout(1, false));
scrolledComposite.addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
if (functionConfigurationComposite != null) {
Rectangle r = scrolledComposite.getClientArea();
scrolledComposite.setMinSize(functionConfigurationComposite.computeSize(r.width, SWT.DEFAULT));
}
}
});
scrolledComposite.setContent(functionConfigurationComposite);
createBasicSettingSection(functionConfigurationComposite);
createFunctionRoleSettingSection(functionConfigurationComposite);
createFunctionVersioningAndAliasSettingSection(functionConfigurationComposite);
createS3BucketSection(functionConfigurationComposite);
createAdvancedSettingSection(functionConfigurationComposite);
aggregateValidationStatus.addChangeListener(functionConfigValidationStatusListener);
setControl(scrolledComposite);
}
@Override
public void onEnterPage() {
refreshIamRole();
refreshFunctionAliases();
refreshS3Bucket();
refreshKmsKey();
functionNameLabel.setText(dataModel.getFunctionDataModel().getFunctionName());
if (dataModel.getFunctionDataModel().isSelectExistingResource()) {
FunctionConfiguration function = dataModel.getFunctionDataModel().getExistingResource();
descriptionTextComplex.setText(function.getDescription());
memoryTextComplex.setText(String.valueOf(function.getMemorySize()));
timeoutTextComplex.setText(String.valueOf(function.getTimeout()));
}
}
private void createBasicSettingSection(Composite parent) {
Group group = newGroup(parent, "Basic Settings");
group.setLayout(createSectionGroupLayout());
newLabel(group, "Name:");
functionNameLabel = newFillingLabel(group, "", 2);
descriptionTextComplex = TextComplex.builder(group,
bindingContext, PojoProperties.value(FunctionConfigPageDataModel.P_DESCRIPTION).observe(dataModel.getFunctionConfigPageDataModel()))
.createLabel(true)
.labelColSpan(1)
.labelValue("Description:")
.textColSpan(2)
.defaultValue(dataModel.getFunctionConfigPageDataModel().getDescription())
.textMessage("The description for the function (optional)")
.build();
}
private void createFunctionRoleSettingSection(Composite parent) {
Group group = newGroup(parent, "Function Role");
setItalicFont(newLink(
group,
LambdaConstants.webLinkListener,
"Select the IAM role that AWS Lambda can assume to execute the function on your behalf. Learn more about Lambda execution roles.", 1, 100, 30));
selectOrCreateBasicLambdaRoleComposite = new SelectOrCreateBasicLambdaRoleComposite(
group, bindingContext, dataModel.getLambdaRoleDataModel());
}
private void createFunctionVersioningAndAliasSettingSection(Composite parent) {
Group group = newGroup(parent, "Function Versioning and Alias");
setItalicFont(newLink(
group,
LambdaConstants.webLinkListener,
"You can publish a new immutable version and an alias to that version whenever you have a new revision of the Lambda function. Learn more about Lambda function versioning and aliases.", 1, 100, 30));
publishNewVersionCheckbox = CheckboxComplex.builder()
.composite(group)
.dataBindingContext(bindingContext)
.defaultValue(false)
.labelValue("Publish new version")
.pojoObservableValue(PojoProperties.value(P_PUBLISH_NEW_VERSION).observe(dataModel.getFunctionConfigPageDataModel()))
.selectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onPublishNewVersionCheckboxSelected();
}
})
.build();
newVersionAliasCheckbox = CheckboxComplex.builder()
.composite(group)
.dataBindingContext(bindingContext)
.defaultValue(false)
.labelValue("Provide an alias to this new version")
.pojoObservableValue(PojoProperties.value(P_CREATE_NEW_VERSION_ALIAS).observe(dataModel.getFunctionConfigPageDataModel()))
.selectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onNewVersionAliasCheckboxSelected();
}
})
.build();
selectOrInputFunctionAliasComposite = new SelectOrInputFunctionAliasComposite(
group, bindingContext, dataModel.getFunctionAliasDataModel());
GridLayout gridLayout = (GridLayout)selectOrInputFunctionAliasComposite.getLayout();
gridLayout.marginLeft = 20;
selectOrInputFunctionAliasComposite.setLayout(gridLayout);
}
public void onPublishNewVersionCheckboxSelected() {
newVersionAliasCheckbox.getCheckbox().setEnabled(publishNewVersionCheckbox.getCheckbox().getSelection());
selectOrInputFunctionAliasComposite.setEnabled(publishNewVersionCheckbox.getCheckbox().getSelection() && newVersionAliasCheckbox.getCheckbox().getSelection());
}
private void onEncryptionRadioButtonsSelected() {
selectOrCreateKmsKeyComposite.setEnabled(awsKmsEncryptionButton.getRadioButton().getSelection());
}
private void onNewVersionAliasCheckboxSelected() {
selectOrInputFunctionAliasComposite.setEnabled(newVersionAliasCheckbox.getCheckbox().getSelection());
}
private void createS3BucketSection(Composite parent) {
Group group = newGroup(parent, "S3 Bucket for Function Code");
selectOrCreateBucketComposite = new SelectOrCreateBucketComposite(
group, bindingContext, dataModel.getS3BucketDataModel());
GridLayout gridLayout = (GridLayout) selectOrCreateBucketComposite.getLayout();
gridLayout.verticalSpacing = 0;
selectOrCreateBucketComposite.setLayout(gridLayout);
setItalicFont(newLink(group, LambdaConstants.webLinkListener,
"Upload Lambda zip file with encrytion to protect data at rest by using Amazon S3 master-key or by using AWS KMS master-key. Learn more about Amazon S3 encryption.",
1, 100, 30));
Composite radioButtonComposite = newComposite(group, 1, 3, true);
SelectionAdapter selectAdapter = new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
onEncryptionRadioButtonsSelected();
}
};
noneEncryptionButton = createEncryptionButton(
radioButtonComposite,
P_NONE_ENCRYPTION,
dataModel.getFunctionConfigPageDataModel().isNoneEncryption(),
"None",
selectAdapter
);
amazonS3EncryptionButton = createEncryptionButton(
radioButtonComposite,
P_S3_ENCRYPTION,
dataModel.getFunctionConfigPageDataModel().isS3Encryption(),
"Amazon S3 master-key",
selectAdapter
);
awsKmsEncryptionButton = createEncryptionButton(
radioButtonComposite,
P_KMS_ENCRYPTION,
dataModel.getFunctionConfigPageDataModel().isKmsEncryption(),
"AWS KMS master-key",
selectAdapter
);
selectOrCreateKmsKeyComposite = new SelectOrCreateKmsKeyComposite(
group, bindingContext, dataModel.getKmsKeyDataModel());
}
private void createAdvancedSettingSection(Composite parent) {
Group group = newGroup(parent, "Advanced Settings");
group.setLayout(createSectionGroupLayout());
String memoryErrMsg = String.format(
"Please enter a memory size within the range of [%d, %d]",
MIN_MEMORY, MAX_MEMORY);
String timeoutErrMsg = String.format(
"Please enter a timeout within the range of [%d, %d]",
MIN_TIMEOUT, MAX_TIMEOUT);
memoryTextComplex = TextComplex.builder(group,
bindingContext, PojoProperties.value(FunctionConfigPageDataModel.P_MEMORY).observe(dataModel.getFunctionConfigPageDataModel()))
.addValidator(new RangeValidator(memoryErrMsg,
MIN_MEMORY, MAX_MEMORY))
.createLabel(true)
.defaultValue(Integer.toString(DEFAULT_MEMORY))
.labelColSpan(1)
.labelValue("Memory (MB):")
.textColSpan(2)
.build();
timeoutTextComplex = TextComplex.builder(group,
bindingContext, PojoProperties.value(FunctionConfigPageDataModel.P_TIMEOUT).observe(dataModel.getFunctionConfigPageDataModel()))
.addValidator(new RangeValidator(timeoutErrMsg,
MIN_TIMEOUT, MAX_TIMEOUT))
.createLabel(true)
.defaultValue(Integer.toString(DEFAULT_TIMEOUT))
.labelColSpan(1)
.labelValue("Timeout (s):")
.textColSpan(2)
.build();
}
private RadioButtonComplex createEncryptionButton(Composite composite, String propertyName, boolean defaultValue, String labelValue, SelectionListener listener) {
return RadioButtonComplex.builder()
.composite(composite)
.dataBindingContext(bindingContext)
.pojoObservableValue(PojoProperties.value(propertyName).observe(dataModel.getFunctionConfigPageDataModel()))
.defaultValue(defaultValue)
.labelValue(labelValue)
.selectionListener(listener)
.build();
}
private GridLayout createSectionGroupLayout() {
GridLayout layout = new GridLayout(3, true);
layout.marginWidth = 15;
return layout;
}
private String getLastDeploymentBucketName() {
String handler = dataModel.getHandler();
String defaultS3Bucket = null;
LambdaFunctionProjectMetadata projectMetadata = dataModel.getProjectMetadataBeforeUpload();
if (projectMetadata != null && projectMetadata.getHandlerMetadata().get(handler) != null) {
LambdaFunctionMetadata functionMetadata = projectMetadata.getHandlerMetadata().get(handler);
LambdaFunctionDeploymentMetadata deployment = functionMetadata.getDeployment();
if (deployment != null) {
defaultS3Bucket = deployment.getAwsS3BucketName();
}
}
return defaultS3Bucket;
}
private String getDefaultIamRole() {
FunctionConfiguration function = dataModel.getFunctionDataModel().getExistingResource();
String defaultRoleName = null;
if (dataModel.getFunctionDataModel().isSelectExistingResource() && function != null) {
defaultRoleName = function.getRole().split("/")[1];
} else {
defaultRoleName = dataModel.getProjectMetadataBeforeUpload() == null
? null
: this.dataModel.getProjectMetadataBeforeUpload()
.getLastDeploymentRoleName();
}
return defaultRoleName;
}
private void refreshFunctionAliases() {
selectOrInputFunctionAliasComposite.refreshComposite(
new LambdaFunctionAliasesScopeParam(
AwsToolkitCore.getDefault().getCurrentAccountId(),
dataModel.getRegionDataModel().getRegion().getId(),
dataModel.getFunctionDataModel().getFunctionName()),
null);
onPublishNewVersionCheckboxSelected();
}
/*
* Async loading of S3 buckets. S3 buckets might be loaded multiple times
* since it depends on the current region being selected
*/
private void refreshS3Bucket() {
selectOrCreateBucketComposite.refreshComposite(new AwsResourceScopeParamBase(
AwsToolkitCore.getDefault().getCurrentAccountId(),
dataModel.getRegionDataModel().getRegion().getId()), getLastDeploymentBucketName());
}
private void refreshKmsKey() {
selectOrCreateKmsKeyComposite.refreshComposite(new AwsResourceScopeParamBase(
AwsToolkitCore.getDefault().getCurrentAccountId(),
dataModel.getRegionDataModel().getRegion().getId()), null);
onEncryptionRadioButtonsSelected();
}
private void refreshIamRole() {
selectOrCreateBasicLambdaRoleComposite.refreshComposite(new AwsResourceScopeParamBase(
AwsToolkitCore.getDefault().getCurrentAccountId(),
dataModel.getRegionDataModel().getRegion().getId()), getDefaultIamRole());
}
private static Label newLabel(Composite parent, String text) {
Label label = new Label(parent, SWT.NONE);
label.setText(text);
return label;
}
/*
* Italic font resource
*/
private List italicFontList = new ArrayList<>();
private void setItalicFont(Control control) {
FontData[] fontData = control.getFont().getFontData();
for (FontData fd : fontData) {
fd.setStyle(SWT.ITALIC);
}
Font italicFont = new Font(Display.getDefault(), fontData);
control.setFont(italicFont);
italicFontList.add(italicFont);
}
@Override
public void dispose() {
italicFontList.forEach(Font::dispose);
super.dispose();
}
}