diff --git a/README.md b/README.md index 7350af9..c1886a5 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,17 @@ timeout, memory size, and description. --memory 128 \ --description "ebs backup function" +You can also deploy your Lambda functions to your VPC. + + lambder functions new \ + --name curl-backend-site \ + --bucket my-s3-bucket \ + --timeout 30 \ + --memory 128 \ + --description "curl a site on a private subnet" + --subnet-ids "subnet-1abcdef,subnet-2abcdef" + --security-group-ids "sg-12345678" + Deploy the Lambda function (from within the project directory) lambder functions deploy diff --git a/lambder/cli.py b/lambder/cli.py index 76f2f06..215e0f3 100644 --- a/lambder/cli.py +++ b/lambder/cli.py @@ -71,6 +71,8 @@ def __init__(self, config_file): self.timeout = config['timeout'] self.memory = config['memory'] self.description = config['description'] + self.subnet_ids = config['subnet_ids'] + self.security_group_ids = config['security_group_ids'] @cli.group() @click.pass_context @@ -102,7 +104,9 @@ def list(): @click.option('--timeout', help='function timeout in seconds') @click.option('--memory', help='function memory') @click.option('--description', help='function description') -def new(name, bucket, timeout, memory, description): +@click.option('--subnet-ids', help='comma-separated list of VPC subnet ids') +@click.option('--security-group-ids', help='comma-separated list of VPC security group ids') +def new(name, bucket, timeout, memory, description, subnet_ids, security_group_ids): """ Create a new lambda project """ config = {} if timeout: @@ -111,6 +115,10 @@ def new(name, bucket, timeout, memory, description): config['memory'] = memory if description: config['description'] = description + if subnet_ids: + config['subnet_ids'] = subnet_ids + if security_group_ids: + config['security_group_ids'] = security_group_ids lambder.create_project(name, bucket, config) @@ -121,8 +129,10 @@ def new(name, bucket, timeout, memory, description): @click.option('--timeout', help='function timeout in seconds') @click.option('--memory', help='function memory') @click.option('--description', help='function description') +@click.option('--subnet-ids', help='comma-separated list of VPC subnet ids') +@click.option('--security-group-ids', help='comma-separated list of VPC security group ids') @click.pass_obj -def deploy(config, name, bucket, timeout, memory, description): +def deploy(config, name, bucket, timeout, memory, description, subnet_ids, security_group_ids): """ Deploy/Update a function from a project directory """ # options should override config if it is there myname = name or config.name @@ -130,9 +140,18 @@ def deploy(config, name, bucket, timeout, memory, description): mytimeout = timeout or config.timeout mymemory = memory or config.memory mydescription = description or config.description + mysubnet_ids = subnet_ids or config.subnet_ids + mysecurity_group_ids = security_group_ids or config.security_group_ids + + vpc_config = {} + if mysubnet_ids and mysecurity_group_ids: + vpc_config = { + 'SubnetIds': mysubnet_ids.split(','), + 'SecurityGroupIds': mysecurity_group_ids.split(',') + } click.echo('Deploying {} to {}'.format(myname, mybucket)) - lambder.deploy_function(myname, mybucket, mytimeout, mymemory, mydescription) + lambder.deploy_function(myname, mybucket, mytimeout, mymemory, mydescription, vpc_config) # lambder functions rm @functions.command() diff --git a/lambder/lambder.py b/lambder/lambder.py index e531739..a045b51 100644 --- a/lambder/lambder.py +++ b/lambder/lambder.py @@ -259,6 +259,13 @@ def _put_role_policy(self, role, policy_name, policy_doc): PolicyDocument=policy_doc ) + def _attach_vpc_policy(self, role): + iam = boto3.client('iam') + iam.attach_role_policy( + RoleName=role, + PolicyArn='arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole' + ) + def _lambda_exists(self, name): awslambda = boto3.client('lambda') try: @@ -273,7 +280,7 @@ def _lambda_exists(self, name): return True - def _update_lambda(self, name, bucket, key, timeout, memory, description): + def _update_lambda(self, name, bucket, key, timeout, memory, description, vpc_config): awslambda = boto3.client('lambda') resp = awslambda.update_function_code( FunctionName=self._long_name(name), @@ -285,10 +292,11 @@ def _update_lambda(self, name, bucket, key, timeout, memory, description): FunctionName=self._long_name(name), Timeout=timeout, MemorySize=memory, - Description=description + Description=description, + VpcConfig=vpc_config ) - def _create_lambda(self, name, bucket, key, role_arn, timeout, memory, description): + def _create_lambda(self, name, bucket, key, role_arn, timeout, memory, description, vpc_config): awslambda = boto3.client('lambda') resp = awslambda.create_function( FunctionName=self._long_name(name), @@ -301,7 +309,8 @@ def _create_lambda(self, name, bucket, key, role_arn, timeout, memory, descripti }, Timeout=timeout, MemorySize=memory, - Description=description + Description=description, + VpcConfig=vpc_config ) def _delete_lambda(self, name): @@ -323,7 +332,7 @@ def _role_name(self, name): def _policy_name(self, name): return self._long_name(name) + 'ExecutePolicy' - def deploy_function(self, name, bucket, timeout, memory, description): + def deploy_function(self, name, bucket, timeout, memory, description, vpc_config): long_name = self._long_name(name) s3_key = self._s3_key(name) role_name = self._role_name(name) @@ -337,7 +346,7 @@ def deploy_function(self, name, bucket, timeout, memory, description): # upload it to s3 self._s3_cp(zfile, bucket, s3_key) - # remote tempfile + # remove tempfile os.remove(zfile) # create the lambda execute role if it does not already exist @@ -350,13 +359,17 @@ def deploy_function(self, name, bucket, timeout, memory, description): self._put_role_policy(role, policy_name, policy_doc) + # add the vpc policy to the role if vpc_config is set + if vpc_config: + self._attach_vpc_policy(role_name) + # create or update the lambda function timeout_i = int(timeout) if self._lambda_exists(name): - self._update_lambda(name, bucket, s3_key, timeout_i, memory, description) + self._update_lambda(name, bucket, s3_key, timeout_i, memory, description, vpc_config) else: time.sleep(5) # wait for role to be created - self._create_lambda(name, bucket, s3_key, role.arn, timeout_i, memory, description) + self._create_lambda(name, bucket, s3_key, role.arn, timeout_i, memory, description, vpc_config) # List only the lambder functions, i.e. ones starting with 'Lambder-' def list_functions(self): diff --git a/requirements.txt b/requirements.txt index a83e366..50a5748 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,23 @@ -boto3==1.2.3 -botocore==1.3.20 +awslogs==0.2.0 +binaryornot==0.4.0 +boto3==1.2.6 +botocore==1.4.0 +chardet==2.3.0 click==6.2 +cookiecutter==1.3.0 docutils==0.12 -futures==3.0.4 +future==0.15.2 +futures==3.0.5 +Jinja2==2.8 jmespath==0.9.0 -python-dateutil==2.4.2 +MarkupSafe==0.23 +pipsi==0.9 +python-dateutil==2.5.0 +ruamel.base==1.0.0 +ruamel.ordereddict==0.4.9 +ruamel.yaml==0.10.15 six==1.10.0 +termcolor==1.1.0 +virtualenv==14.0.0 wheel==0.24.0 +whichcraft==0.1.1 diff --git a/setup.py b/setup.py index dbe3dbd..4e95886 100644 --- a/setup.py +++ b/setup.py @@ -5,14 +5,14 @@ dependencies = [ 'click>=6.2', - 'boto3>=1.2.3', - 'botocore>=1.3.20', + 'boto3>=1.2.6', + 'botocore>=1.4.0', 'cookiecutter>=1.3.0' ] setup( name='lambder', - version='1.1.1', + version='1.2.1', url='https://github.com/LeafSoftware/python-lambder', license='MIT', author='Chris Chalfant',