PK£I«ÚÚŒwwlambkin/metadata.pyimport json from os.path import join class Metadata(): def __init__(self, function_name): self.function_name = function_name self.metadata_file = join(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ºçÇÒîîlambkin/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_language_name_for_runtime(runtime): if runtime.startswith('python'): return 'python' elif runtime.startswith('node'): return 'nodejs' 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àIÇæŒS  lambkin/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 output = join(function_name, output_filename) expansions = { 'function_name': function_name } with open(output, 'w') as dst: dst.write(pystache.render(templates[template_name], expansions)) templates = { 'python': """ # {{function_name}}.py: An AWS Lambda function. # # To run this function in Lambda, you need to bundle all the libraries you use. # Even something as mundane as: # import requests # won't work by default. # # To install a library (like "requests") into your Lambda function, edit # the Makefile in your function's directory, then run: # # lambkin make {{function_name}} # # from the top-level lambkin directory. The Makefile skeleton provided # has examples for installing pip packages. # # The files installed by pip are all "gitignored" by default, so they don't # get commited. In fact, almost everything is ignored, so if you want to add # and commit more than just your script, you'll need to explicitly list things # in "functions/{{function_name}}/.gitignore". # # # # Here is the entry point for Lambda. Execution starts here when an # event triggers us to run. # # Lambda passes us two objects, the event that triggered us, and a 'context' # object for this execution. For details see: # http://docs.aws.amazon.com/lambda/latest/dg/python-programming-model-handler-types.html def handler(event, context): # To log to Cloudwatch, just: print "anything you like." optional_return_value = { "moon_landing": True, "color": "#3a3a3a" } return optional_return_value # which often becomes a JSON response.""", 'makefile': """ {{function_name}}: @echo "make" called for {{function_name}} # Install some Python libraries. # pip install requests -t . # pip install elasticsearch -t . # Or maybe we're hacking NodeJS. Here's how to bundle npm packages. # npm install --prefix=. aws-sdk """, 'nodejs': """ // {{function_name}}.js: An AWS Lambda function. exports.handler = function(event, context, callback) { console.log("Running in Lambda..."); // An abitrary object. result = { "size": "large", "with_cheese": true }; // This example uses an NPM package. Look in the "Makefile" to see how to // install packages for your Lambda function. // // var yaml = require('js-yaml'); // console.log(yaml.safeDump(result)); // Lambda will JSON encode the "result" object as the output of our function. callback(null, result); } """, 'gitignore': """ # If this Lambda function uses any libraries, they will be here in the # source directory. # # We don't want to commit copies of 3rd party libraries to our repository, # so by default we will ignore _everything_ in the source dir: * # ...except for some things we want to commit: !{{function_name}}.py !{{function_name}}.js !metadata.json !Makefile !.gitignore """ } PK£IFhø!!lambkin/exceptions.pyclass Fatal(Exception): pass PK£I3·ÕKããlambkin/lambkin.py#!/usr/bin/env python from __future__ import absolute_import 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.runtime import get_language_name_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 = 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_name = get_language_name_for_runtime(runtime) render_template(template_name, 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(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() PK£Ilambkin/__init__.pyPKI^-Ò 'lambkin-0.0.2.dist-info/DESCRIPTION.rstUNKNOWN PKI³~¯22(lambkin-0.0.2.dist-info/entry_points.txt[console_scripts] lambkin = lambkin.lambkin:main PKIZ‘ss%lambkin-0.0.2.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.2"}PKIÿ:µ(%lambkin-0.0.2.dist-info/top_level.txtlambkin PKIŒ''\\lambkin-0.0.2.dist-info/WHEELWheel-Version: 1.0 Generator: bdist_wheel (0.26.0) Root-Is-Purelib: true Tag: py2-none-any PKI£;‘i\\ lambkin-0.0.2.dist-info/METADATAMetadata-Version: 2.0 Name: lambkin Version: 0.0.2 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 PKITj¿®ŸŸlambkin-0.0.2.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=yUJRxUFhcr6iiKIvsFFRM3w7NL9ztki115qRTNcv9nA,6371 lambkin/metadata.py,sha256=5YHfINPe5YzCbTBRf--Z9pPo4ZrjdzvwZWSaNAFaIH8,631 lambkin/runtime.py,sha256=-NMSjSnsOzHOjd66Bc9Z4RGm99ul8DRh38iTzxutneg,1006 lambkin/template.py,sha256=GruMyBujQcayiTA1PQLftrh4ADBa0GsJj7gNhzAoRlE,3087 lambkin/ux.py,sha256=ixVCW9vpYd8V5P4vcX_CRvYLb3OujFIhtK6dX62aKbY,317 lambkin-0.0.2.dist-info/DESCRIPTION.rst,sha256=OCTuuN6LcWulhHS3d5rfjdsQtW22n7HENFRh6jC6ego,10 lambkin-0.0.2.dist-info/METADATA,sha256=GkqiTJFNoP_sUSsa7-f8AuLZ-mlVjuhGtYWmCoPDWdQ,604 lambkin-0.0.2.dist-info/RECORD,, lambkin-0.0.2.dist-info/WHEEL,sha256=JTb7YztR8fkPg6aSjc571Q4eiVHCwmUDlX8PhuuqIIE,92 lambkin-0.0.2.dist-info/entry_points.txt,sha256=hLu9vZ-8zKPY_inSGNJVnqALxDA6GqUhOiu79ddEWcQ,50 lambkin-0.0.2.dist-info/metadata.json,sha256=zvjkd87Qch_a9JKOqhyRQg6Ba8eKXNHB0zeuHaW3Yo0,883 lambkin-0.0.2.dist-info/top_level.txt,sha256=cB4mfm6Bk7rjpMZxaRbFN3VQsb-iMkDfy2SNCEPi-R0,8 PK£I«ÚÚŒwwlambkin/metadata.pyPK£IºçÇÒîî¨lambkin/runtime.pyPK£IYÖ‰Ó¢¢Ælambkin/aws.pyPK£I|µÖY== ” lambkin/ux.pyPKàIÇæŒS  ü lambkin/template.pyPK£IFhø!!<lambkin/exceptions.pyPK£I3·ÕKããlambkin/lambkin.pyPK£I£2lambkin/__init__.pyPKI^-Ò 'Ô2lambkin-0.0.2.dist-info/DESCRIPTION.rstPKI³~¯22(#3lambkin-0.0.2.dist-info/entry_points.txtPKIZ‘ss%›3lambkin-0.0.2.dist-info/metadata.jsonPKIÿ:µ(%Q7lambkin-0.0.2.dist-info/top_level.txtPKIŒ''\\œ7lambkin-0.0.2.dist-info/WHEELPKI£;‘i\\ 38lambkin-0.0.2.dist-info/METADATAPKITj¿®ŸŸÍ:lambkin-0.0.2.dist-info/RECORDPK3¨?