Beispiel #1
0
def sync_requirements(args):
    if not os.path.exists(os.path.join(WORKING_DIR, 'Pipfile')) and \
            os.path.exists(os.path.join(WORKING_DIR, 'Pipfile.lock')):
        logger.error(
            "This feature requires a root level 'Pipfile' and 'Pipfile.lock'")
        sys.exit(1)

    template = SAMTemplate(args.template)
    pipfile_packages = get_pipfile_packages()

    logger.info('Evaluating Lambda function dependencies...\n')
    for k, v in template.lambda_resources.items():
        handler_file = template.get_lambda_handler(k)
        if not handler_file:
            logger.error(f"{k}: There was no 'Handler' found for the Lambda "
                         f"function and it is being skipped!\n")
            continue

        lambda_code_dir = v['Properties']['CodeUri']
        requirements = parse_requirements(lambda_code_dir)

        if not requirements:
            logger.info(f"{k}: No requirements.txt file for this Lambda")
            continue

        results = write_requirements(pipfile_packages, requirements,
                                     lambda_code_dir)

        if results:
            logger.info(
                f"{k}: A requirements.txt file has been generated with "
                "the following packages:")
            logger.info(f"{k}: {', '.join(results)}\n")
        else:
            logger.info(f"{k}: No requirements.txt file generated\n")
Beispiel #2
0
def get_possum_path(user_dir):
    possum_dir = os.path.join(user_dir, '.possum')
    if not os.path.exists(possum_dir):
        logger.info('Creating Possum directory...')
        os.mkdir(possum_dir)
    elif not os.path.isdir(possum_dir):
        logger.error(f"'{possum_dir}' is not a directory! Delete to "
                     "allow Possum to recreate the directory")
        sys.exit(1)

    return os.path.join(possum_dir, _possum_name())
Beispiel #3
0
def build_docker_image(version):
    logger.info(f"Building 'possum:{version}' Docker image (this may take "
                "several minutes)...")
    client = docker.from_env()
    try:
        image = client.images.build(
            fileobj=dockerfile(version),
            tag=f'possum:{version}',
            quiet=False,
            rm=True,
            pull=True
        )

        logger.info("Tagging as 'latest'...")
        if image[0].tag('possum', 'latest'):
            image[0].reload()
        else:
            logger.info('Unable to add additional tag')

    except APIError as err:
        logger.error(f'Unable to build the Docker image: {err.explanation}')
        sys.exit(1)

    except BuildError as err:
        logger.error(f'Unable to build the Docker image: {err.msg}')
        sys.exit(1)

    image_id = image[0].short_id.split(':')[-1]
    logger.info("Image successfully created:\n"
                f"  ID: {image_id}\n"
                f"  Tags: {', '.join(image[0].tags)}")
Beispiel #4
0
def upload_packages(package_directory, bucket_name,
                    bucket_dir, profile_name=None):
    session = boto3.Session(profile_name=profile_name)
    s3_client = session.resource('s3')

    os.chdir(package_directory)
    logger.info(f'Uploading all Lambda packages to: {bucket_dir}')

    for artifact in os.listdir('.'):
        logger.info(f'Uploading package: {artifact}')
        try:
            s3_client.Bucket(bucket_name).upload_file(
                artifact, os.path.join(bucket_dir, artifact))
        except NoCredentialsError:
            logger.error('Unable to upload packages to the S3 bucket. Boto3 '
                         'was unable to locate credentials!')
            sys.exit(1)
        except (S3UploadFailedError, ClientError)as err:
            logger.error('Failed to upload the package to the S3 bucket! '
                         f'Encountered:\n{err}')
            sys.exit(1)
Beispiel #5
0
def main_legacy(args):
    try:
        possum_file = PossumFile(USER_DIR)
    except scanner.ScannerError:
        logger.error(f"The Possum file could not be loaded!")
        sys.exit(1)

    if args.docker:
        if not args.docker_image:
            logger.error('A Docker image must be specified')
            sys.exit(1)

        run_in_docker(USER_DIR, possum_file.path, args.docker_image)
        sys.exit()

    try:
        pipenvw = PipenvWrapper()
    except PipenvPathNotFound:
        logger.error("'pipenv' could not be found!")
        sys.exit(1)

    global S3_BUCKET_NAME
    global S3_ARTIFACT_DIR

    S3_BUCKET_NAME, S3_ARTIFACT_DIR = get_s3_bucket_and_dir(args.s3_bucket)

    try:
        with open(args.template) as fobj:
            template_file = YAML().load(fobj)
    except Exception as error:
        logger.error('Failed to load template file! Encountered: '
                     f'{type(error).__name__}\n')
        sys.exit(1)

    api_resources = dict()
    lambda_functions = dict()

    for resource in template_file['Resources']:
        if template_file['Resources'][resource]['Type'] == \
                'AWS::Serverless::Function':
            runtime = \
                    template_file['Resources'][resource]['Properties'].get(
                        'Runtime',
                        get_global(template_file, 'Function', 'Runtime')
                    )

            if not runtime.lower().startswith('python'):
                logger.warning(
                    'Possum only packages Python based Lambda '
                    f' functions! Found runtime "{runtime}" for  '
                    f'function "{resource}" This Lambda function '
                    'will be skipped. You will need to package this '
                    'function separately before deploying.')
                continue

            else:
                lambda_functions[resource] = \
                    template_file['Resources'][resource]

        elif template_file['Resources'][resource]['Type'] == \
                'AWS::Serverless::Api':
            # Server::Api resource. If it has a 'DefinitionUri' parameter
            # that DOES NOT start with s3://, this swagger file must be
            # shipped and the template updated.
            if 'DefinitionUri' in \
                    template_file['Resources'][resource]['Properties'] \
                    and \
                    not (
                        template_file['Resources'][resource]['Properties']
                        ['DefinitionUri'].startswith('s3://')
                    ):
                api_resources[resource] = template_file['Resources'][resource]

    logger.info("\nThe following functions will be packaged and deployed:")
    for func in lambda_functions.keys():
        logger.info(f"  - {func}")

    logger.info("\nThe following swagger files will be deployed:")
    for api in api_resources.keys():
        logger.info(f"  - {api}")

    build_directory = tempfile.mkdtemp(suffix='-build', prefix='possum-')
    logger.info(f'\nBuild directory: {build_directory}\n')

    build_artifact_directory = os.path.join(build_directory, 's3_artifacts')
    os.mkdir(build_artifact_directory)

    for logical_id, api_resource in api_resources.items():
        swagger_src = os.path.join(WORKING_DIR,
                                   api_resource['Properties']['DefinitionUri'])

        swagger_dst = os.path.join(build_artifact_directory,
                                   f'{logical_id}.swagger')

        shutil.copyfile(swagger_src, swagger_dst)

        update_template_resource(template_file,
                                 logical_id,
                                 S3_BUCKET_NAME,
                                 S3_ARTIFACT_DIR,
                                 s3_object=os.path.basename(swagger_dst),
                                 resource_param='DefinitionUri')

    for func, values in lambda_functions.items():
        func_source_dir = os.path.join(WORKING_DIR,
                                       values['Properties']['CodeUri'])

        func_build_dir = os.path.join(build_directory, func)

        if possum_file.check_hash(func, func_source_dir) and not args.clean:
            last_s3_uri = possum_file.get_last_s3_uri(func)
            if last_s3_uri:
                logger.info(f'{func}: No changes detected')
                logger.info(f'{func}: Using S3 artifact: {last_s3_uri}\n')
                update_template_resource(template_file,
                                         func,
                                         S3_BUCKET_NAME,
                                         S3_ARTIFACT_DIR,
                                         s3_uri=last_s3_uri)
                continue

        shutil.copytree(func_source_dir, func_build_dir)
        os.chdir(func_build_dir)
        logger.info(f'{func}: Working dir: {func_build_dir}')

        requirements_files = ('Pipfile', 'Pipfile.lock', 'requirements.txt')
        if [i for i in os.listdir('.') if i in requirements_files]:
            logger.info(f'{func}: Creating virtual environment...')
            pipenvw.create_virtual_environment()

            pipenvw.get_virtual_environment_path()

            logger.info(f'{func}: Environment created at {pipenvw.venv_path}')

            do_not_copy = get_existing_site_packages(pipenvw.venv_path)

            logger.info(f'{func}: Installing requirements...')
            pipenvw.install_packages()

            logger.info(f'{func}: Copying installed packages...')
            move_installed_packages(pipenvw.venv_path, do_not_copy)

            logger.info(f'{func}: Removing Lambda build environment...')
            pipenvw.remove_virtualenv()

        logger.info(f'{func}: Creating Lambda package...')

        artifact = create_lambda_package(func_build_dir,
                                         build_artifact_directory)

        update_template_resource(template_file,
                                 func,
                                 S3_BUCKET_NAME,
                                 S3_ARTIFACT_DIR,
                                 s3_object=artifact)
        possum_file.set_s3_uri(
            func, template_file['Resources'][func]['Properties']['CodeUri'])
        logger.info('')

    upload_packages(build_artifact_directory, S3_BUCKET_NAME, S3_ARTIFACT_DIR,
                    args.profile)

    logger.info('\nRemoving build directory...')
    shutil.rmtree(build_directory)

    stream = io.StringIO()
    YAML().dump(template_file, stream)
    deployment_template = stream.getvalue()

    if not args.output_template:
        logger.info('\nUpdated SAM deployment template:\n')
        print(deployment_template)
    else:
        logger.info("Writing deployment template to "
                    f"'{args.output_template}'...\n")
        with open(os.path.join(WORKING_DIR, args.output_template),
                  'wt') as fobj:
            fobj.write(deployment_template)

    possum_file.save()
Beispiel #6
0
def run_in_docker(user_dir, possum_path, image_name):
    command = sys.argv

    command[0] = 'possum'
    command.pop(command.index('--docker'))

    try:
        command.pop(command.index('--docker-image') + 1)
        command.pop(command.index('--docker-image'))
    except ValueError:
        pass

    # docker_directory = tempfile.mkdtemp(
    #     suffix='-docker', prefix='possum-', dir='/tmp')
    # logger.info(f'Working Docker directory: {docker_directory}')

    client = docker.from_env()

    # Copy invocation environment parameters into Docker for use-cases where
    # AWS credentials are not available via the ~/.aws directory.
    container_env = {
        k: v
        for k, v in os.environ.items()
        if k in [
            'AWS_ACCESS_KEY_ID',
            'AWS_SECRET_ACCESS_KEY',
            'AWS_SESSION_TOKEN',
        ]
    }

    logger.info('Running Docker container...')

    try:
        container = client.containers.create(
            image=image_name,
            command=command,
            environment=container_env,
            volumes={
                os.path.join(user_dir, '.aws'): {
                    'bind': '/root/.aws',
                    'mode': 'ro'
                },
                os.path.dirname(possum_path): {
                    'bind': '/root/.possum',
                    'mode': 'rw'
                },
                os.getcwd(): {
                    'bind': '/var/task',
                    'mode': 'rw'
                }
            },
            detach=True
        )
    except ImageNotFound:
        logger.error(f"The Docker image '{image_name}' could not be found")
        sys.exit(1)

    try:
        container.start()
    except APIError as err:
        logger.error(f'Unable to start the Docker container: {err.explanation}')
        sys.exit(1)

    for event in container.logs(stream=True):
        logger.info(event.decode().strip())
        time.sleep(.01)

    container.remove()