PKI^lambkin/metadata.pyimport json from os.path import join class Metadata(): def __init__(self, function_name): self.function_name = function_name self.metadata_file = join('functions', function_name, 'metadata.json') def write(self, **metadata): """Write keyword arguments, in JSON format, to metadata.json.""" with open(self.metadata_file, 'w') as f: serialized = json.dumps(metadata, indent=4, sort_keys=True) f.write(serialized) def read(self): """Read metadata for a function from disk (metadata.json).""" with open(self.metadata_file) as f: return json.load(f) PK IՄFFlambkin/runtime.pyfrom exceptions import Fatal def get_file_extension_for_runtime(runtime): """Return the correct file extension for a language runtime.""" runtime = get_sane_runtime(runtime) if runtime.startswith('python'): return 'py' elif runtime.startswith('nodejs'): return 'js' def get_sane_runtime(runtime): """Make a runtime string that Lambda will accept.""" # Default to Python if runtime is None: runtime = 'python' # Ensure we specify a version, not just a language. if runtime == 'python': runtime = 'python2.7' if runtime == 'node' or runtime == 'nodejs': runtime = 'nodejs4.3' valid_runtimes = ['python2.7', 'nodejs4.3'] if runtime in valid_runtimes: return runtime else: raise Fatal('Runtime "%s" is not supported' % runtime) PK.IY։Ӣlambkin/aws.pyimport boto3 def get_region(): """Return the (default) AWS region.""" return boto3.session.Session().region_name def get_account_id(): """Get the AWS account id for our current credentials.""" return boto3.resource('iam').CurrentUser().arn.split(':')[4] def get_iam_arn_prefix(): return "arn:aws:iam::%s" % get_account_id() def get_event_arn_prefix(): return "arn:aws:events:%s:%s" % (get_region(), get_account_id()) def get_role_arn(role): """Given a short role name, return the ARN of the role. eg. get_role_arn('lambda-basic-execution') "arn:aws:iam::329487123:role/lambda-basic-execution" """ return "%s:role/%s" % (get_iam_arn_prefix(), role) def get_event_rule_arn(rule): """Given a short event rule name, return the ARN of the rule. eg. get_event_rule_arn('allow-from-s3') "arn:aws:events:us-west-2:329487123:rule/allow-from-s3" """ return "%s:rule/%s" % (get_event_arn_prefix(), rule) def get_function_arn(function): """Given a short function name, return the ARN of function.""" return 'arn:aws:lambda:%s:%s:function:%s' % ( get_region(), get_account_id(), function ) PK I|Y== lambkin/ux.pyfrom click import echo def say(message): """Write an info message to STDERR. We deliberatly send the message to STDERR to keep STDOUT clear for any JSON output we may return. Interleaving info messages would break the ability to pipe our output to a JSON parser. """ echo(message, err=True) PK IQ5lambkin/template.pyimport pystache from os.path import join def render_template(template_name, function_name, output_filename=None): if not output_filename: output_filename = template_name template = join('templates', '%s.mustache' % template_name) output = join('functions/', function_name, output_filename) expansions = { 'function_name': function_name } with open(template) as src, open(output, 'w') as dst: dst.write(pystache.render(src.read(), expansions)) PK IFh!!lambkin/exceptions.pyclass Fatal(Exception): pass PKIlambkin/lambkin.py#!/usr/bin/env python from __future__ import absolute_import if __name__ == "__main__" and __package__ is None: __package__ = "lambkin" import boto3 import click import json import os from base64 import b64decode from botocore.exceptions import ClientError from lambkin.aws import get_account_id, get_role_arn, get_event_rule_arn from lambkin.aws import get_function_arn from lambkin.metadata import Metadata from lambkin.exceptions import Fatal from lambkin.runtime import get_sane_runtime, get_file_extension_for_runtime from lambkin.template import render_template from lambkin.ux import say from shutil import make_archive from subprocess import check_output, CalledProcessError, STDOUT lmbda = boto3.client('lambda') @click.command(help='Make a new Lambda function from a basic template.') @click.argument('function') @click.option('--runtime', help='The language runtime to use. eg. "python2.7".') def create(function, runtime): runtime = get_sane_runtime(runtime) ext = get_file_extension_for_runtime(runtime) func_dir = 'functions/%s' % function # "functions/funky" func_file = '%s/%s.%s' % (func_dir, function, ext) # "functions/funky/funky.py" for path in (func_dir, func_file): if os.path.exists(path): raise Fatal('Path "%s" already exists.' % path) os.mkdir(func_dir) template = 'basic.%s' % ext render_template(template, function, output_filename="%s.%s" % (function, ext)) render_template('Makefile', function) render_template('gitignore', function, output_filename='.gitignore') Metadata(function).write(runtime=runtime) say('%s created as %s' % (function, func_file)) def get_published_function_names(): """Return the names of all published functions.""" return [f['FunctionName'] for f in lmbda.list_functions()['Functions']] @click.command(name='list-published', help='List published Lambda functions.') def list_published(): for f in lmbda.list_functions()['Functions']: print f['FunctionName'] @click.command(help="Run 'make' for a function.") @click.argument('function') def make(function): make_invocation = ['make', '-C', os.path.join('functions', function)] try: make_log = check_output(make_invocation, stderr=STDOUT) for line in make_log.rstrip().split("\n"): say(line) except CalledProcessError as e: for line in e.output.rstrip().split("\n"): say(line) raise Fatal('make failure') @click.command(help='Publish a function to Lambda.') @click.argument('function') @click.option('--description', help="Descriptive text in AWS Lamda.") @click.option('--timeout', type=click.IntRange(min=1, max=300), default=60, help="Maximum time the function can run, in seconds.") @click.option('--role', default='lambda-basic-execution') def publish(function, description, timeout, role): metadata = Metadata(function).read() runtime = metadata['runtime'] code_dir = os.path.join('functions', function) zip_file = make_archive('/tmp/lambda-publish', 'zip', code_dir) zip_data = open(zip_file).read() if function in get_published_function_names(): # Push the latest code to the existing function in Lambda. update_code_response = lmbda.update_function_code( FunctionName=function, ZipFile=zip_data, Publish=True) # Update any settings for the function too. if not description: # then keep the existing description. description = update_code_response['Description'] final_response = lmbda.update_function_configuration( FunctionName=function, Description=description, Timeout=timeout) say('%s updated in Lambda' % function) else: # we need to explictly create the function in AWS. if not description: raise Fatal('Please provide a description with "--description"') final_response = lmbda.create_function( FunctionName=function, Description=description, Runtime=runtime, Role=get_role_arn(role), Handler='%s.handler' % function, Code={'ZipFile': zip_data}, Timeout=timeout) say('%s created in Lambda' % function) print json.dumps(final_response, sort_keys=True, indent=2) @click.command(help='Run a published function.') @click.argument('function') def run(function): result = lmbda.invoke(FunctionName=function, LogType='Tail') log = b64decode(result['LogResult']).rstrip().split("\n") for line in log: say(line) print result['Payload'].read() @click.command(help='Remove a function from Lambda.') @click.argument('function') def unpublish(function): lmbda.delete_function(FunctionName=function) say('%s unpublished' % (function)) @click.command(help='Schedule a function to run regularly, like "cron".') @click.argument('function') @click.option('--rate', required=True, help='Execution rate. Like "6 minutes", or "1 day".') def cron(function, rate): events = boto3.client('events') try: lmbda.add_permission( FunctionName=function, StatementId='lambkin-allow-cloudwatch-invoke-%s' % function, Action='lambda:InvokeFunction', Principal='events.amazonaws.com', SourceArn=get_event_rule_arn('lambkin-cron-%s' % function), ) except ClientError as e: if e.response['Error']['Code'] == 'ResourceConflictException': # The rule is already there. Carry on! pass else: raise e events.put_rule( Name='lambkin-cron-%s' % function, ScheduleExpression='rate(%s)' % rate, State='ENABLED', Description='Lambkin cron for %s Lambda function' % function, ) response = events.put_targets( Rule='lambkin-cron-%s' % function, Targets=[{ 'Id': function, 'Arn': get_function_arn(function), }] ) print json.dumps(response, sort_keys=True, indent=2) def main(): @click.group() def cli(): pass for cmd in [create, cron, list_published, make, publish, run, unpublish]: cli.add_command(cmd) cli() if __name__ == '__main__': main() PKKIlambkin/__init__.pyPK8I^- 'lambkin-0.0.1.dist-info/DESCRIPTION.rstUNKNOWN PK8I~22(lambkin-0.0.1.dist-info/entry_points.txt[console_scripts] lambkin = lambkin.lambkin:main PK8I_ss%lambkin-0.0.1.dist-info/metadata.json{"classifiers": ["Programming Language :: Python", "Programming Language :: Python :: 2", "License :: OSI Approved :: Apache Software License", "Operating System :: OS Independent", "Development Status :: 3 - Alpha", "Environment :: Console"], "extensions": {"python.commands": {"wrap_console": {"lambkin": "lambkin.lambkin:main"}}, "python.details": {"contacts": [{"email": "toby@jarpy.net", "name": "Toby McLaughlin", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst"}, "project_urls": {"Home": "https://github.com/jarpy/lambkin"}}, "python.exports": {"console_scripts": {"lambkin": "lambkin.lambkin:main"}}}, "extras": [], "generator": "bdist_wheel (0.26.0)", "metadata_version": "2.0", "name": "lambkin", "run_requires": [{"requires": ["boto3", "click (>5,<7)", "pystache"]}], "summary": "CLI tool for managing functions in AWS Lambda.", "version": "0.0.1"}PK8I:(%lambkin-0.0.1.dist-info/top_level.txtlambkin PK8I''\\lambkin-0.0.1.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any PK8I;$\\ lambkin-0.0.1.dist-info/METADATAMetadata-Version: 2.0 Name: lambkin Version: 0.0.1 Summary: CLI tool for managing functions in AWS Lambda. Home-page: https://github.com/jarpy/lambkin Author: Toby McLaughlin Author-email: toby@jarpy.net License: UNKNOWN Platform: UNKNOWN Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Development Status :: 3 - Alpha Classifier: Environment :: Console Requires-Dist: boto3 Requires-Dist: click (>5,<7) Requires-Dist: pystache UNKNOWN PK8I[vlambkin-0.0.1.dist-info/RECORDlambkin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 lambkin/aws.py,sha256=SBAPUGP0ox8OqWPOYYfKihkBhS1oHxQJX-mh6q7mOTM,1186 lambkin/exceptions.py,sha256=G84lKHGZ_CBwuL7R1GYYIUOOmdE_61pMg9Qtpn-xWss,33 lambkin/lambkin.py,sha256=9-tp31-fn14-pFWB59cj72StspEoaWjkGj8yC1Pn87I,6391 lambkin/metadata.py,sha256=Koba8PfZP_q5a6yjRGhFSXJC2UTaEMV6wWenw9xAWEs,644 lambkin/runtime.py,sha256=rs4F_XIdfUdB4LUdC3vkgbnqxBVkZ_9iML0GcSPNVD8,838 lambkin/template.py,sha256=28-JOQba1NNKwkM-jD5IBTHnizxDvu1u58m0jI8EH78,492 lambkin/ux.py,sha256=ixVCW9vpYd8V5P4vcX_CRvYLb3OujFIhtK6dX62aKbY,317 lambkin-0.0.1.dist-info/DESCRIPTION.rst,sha256=OCTuuN6LcWulhHS3d5rfjdsQtW22n7HENFRh6jC6ego,10 lambkin-0.0.1.dist-info/METADATA,sha256=zmOViU_UFcZ5bGPEtL8fY5sxDcgNtXryGGcMjKgTWQM,604 lambkin-0.0.1.dist-info/RECORD,, lambkin-0.0.1.dist-info/WHEEL,sha256=JTb7YztR8fkPg6aSjc571Q4eiVHCwmUDlX8PhuuqIIE,92 lambkin-0.0.1.dist-info/entry_points.txt,sha256=hLu9vZ-8zKPY_inSGNJVnqALxDA6GqUhOiu79ddEWcQ,50 lambkin-0.0.1.dist-info/metadata.json,sha256=p-78GyWcN9xUiAWGyg25DgjbeSEnJ6ee8Gu3jJd3D24,883 lambkin-0.0.1.dist-info/top_level.txt,sha256=cB4mfm6Bk7rjpMZxaRbFN3VQsb-iMkDfy2SNCEPi-R0,8 PKI^lambkin/metadata.pyPK IՄFFlambkin/runtime.pyPK.IY։Ӣ+lambkin/aws.pyPK I|Y== lambkin/ux.pyPK IQ5a lambkin/template.pyPK IFh!!~lambkin/exceptions.pyPKIlambkin/lambkin.pyPKKI'lambkin/__init__.pyPK8I^- '*(lambkin-0.0.1.dist-info/DESCRIPTION.rstPK8I~22(y(lambkin-0.0.1.dist-info/entry_points.txtPK8I_ss%(lambkin-0.0.1.dist-info/metadata.jsonPK8I:(%,lambkin-0.0.1.dist-info/top_level.txtPK8I''\\,lambkin-0.0.1.dist-info/WHEELPK8I;$\\ -lambkin-0.0.1.dist-info/METADATAPK8I[v#0lambkin-0.0.1.dist-info/RECORDPK34