Ensure function exists.

    name
        The name of the state definition

    FunctionName
        Name of the Function.

    Runtime
        The Runtime environment for the function. One of
        'nodejs', 'java8', or 'python2.7'

    Role
        The name or ARN of the IAM role that the function assumes when it executes your
        function to access any other AWS resources.

    Handler
        The function within your code that Lambda calls to begin execution. For Node.js it is the
        module-name.*export* value in your function. For Java, it can be package.classname::handler or
        package.class-name.

    ZipFile
        A path to a .zip file containing your deployment package. If this is
        specified, S3Bucket and S3Key must not be specified.

    S3Bucket
        Amazon S3 bucket name where the .zip file containing your package is
        stored. If this is specified, S3Key must be specified and ZipFile must
        NOT be specified.

    S3Key
        The Amazon S3 object (the deployment package) key name you want to
        upload. If this is specified, S3Key must be specified and ZipFile must
        NOT be specified.

    S3ObjectVersion
        The version of S3 object to use. Optional, should only be specified if
        S3Bucket and S3Key are specified.

    Description
        A short, user-defined function description. Lambda does not use this value. Assign a meaningful
        description as you see fit.

    Timeout
        The function execution time at which Lambda should terminate this function. Because the execution
        time has cost implications, we recommend you set this value based on your expected execution time.
        The default is 3 seconds.

    MemorySize
        The amount of memory, in MB, your function is given. Lambda uses this memory size to infer
        the amount of CPU and memory allocated to your function. Your function use-case determines your
        CPU and memory requirements. For example, a database operation might need less memory compared
        to an image processing function. The default value is 128 MB. The value must be a multiple of
        64 MB.

    VpcConfig
        If your Lambda function accesses resources in a VPC, you must provide this parameter
        identifying the list of security group IDs/Names and subnet IDs/Name.  These must all belong
        to the same VPC.  This is a dict of the form:

        .. code-block:: yaml

            VpcConfig:
              SecurityGroupNames:
                - mysecgroup1
                - mysecgroup2
              SecurityGroupIds:
                - sg-abcdef1234
              SubnetNames:
                - mysubnet1
              SubnetIds:
                - subnet-1234abcd
                - subnet-abcd1234

        If VpcConfig is provided at all, you MUST pass at least one security group and one subnet.

    Permissions
        A list of permission definitions to be added to the function's policy

    RoleRetries
        IAM Roles may take some time to propagate to all regions once created.
        During that time function creation may fail; this state will
        atuomatically retry this number of times. The default is 5.

    Environment
        The parent object that contains your environment's configuration
        settings.  This is a dictionary of the form:

        .. code-block:: python

            {
                'Variables': {
                    'VariableName': 'VariableValue'
                }
            }

        .. versionadded:: 2017.7.0

    region
        Region to connect to.

    key
        Secret key to be used.

    keyid
        Access key to be used.

    profile
        A dict with region, key and keyid, or a pillar key (string) that
        contains a dict with region, key and keyid.

def function_present(name, FunctionName, Runtime, Role, Handler, ZipFile=None,
                     S3Bucket=None, S3Key=None, S3ObjectVersion=None,
                     Description='', Timeout=3, MemorySize=128,
                     Permissions=None, RoleRetries=5, region=None, key=None,
                     keyid=None, profile=None, VpcConfig=None,
                     Environment=None):
    '''
    Ensure function exists.

    name
        The name of the state definition

    FunctionName
        Name of the Function.

    Runtime
        The Runtime environment for the function. One of
        'nodejs', 'java8', or 'python2.7'

    Role
        The name or ARN of the IAM role that the function assumes when it executes your
        function to access any other AWS resources.

    Handler
        The function within your code that Lambda calls to begin execution. For Node.js it is the
        module-name.*export* value in your function. For Java, it can be package.classname::handler or
        package.class-name.

    ZipFile
        A path to a .zip file containing your deployment package. If this is
        specified, S3Bucket and S3Key must not be specified.

    S3Bucket
        Amazon S3 bucket name where the .zip file containing your package is
        stored. If this is specified, S3Key must be specified and ZipFile must
        NOT be specified.

    S3Key
        The Amazon S3 object (the deployment package) key name you want to
        upload. If this is specified, S3Key must be specified and ZipFile must
        NOT be specified.

    S3ObjectVersion
        The version of S3 object to use. Optional, should only be specified if
        S3Bucket and S3Key are specified.

    Description
        A short, user-defined function description. Lambda does not use this value. Assign a meaningful
        description as you see fit.

    Timeout
        The function execution time at which Lambda should terminate this function. Because the execution
        time has cost implications, we recommend you set this value based on your expected execution time.
        The default is 3 seconds.

    MemorySize
        The amount of memory, in MB, your function is given. Lambda uses this memory size to infer
        the amount of CPU and memory allocated to your function. Your function use-case determines your
        CPU and memory requirements. For example, a database operation might need less memory compared
        to an image processing function. The default value is 128 MB. The value must be a multiple of
        64 MB.

    VpcConfig
        If your Lambda function accesses resources in a VPC, you must provide this parameter
        identifying the list of security group IDs/Names and subnet IDs/Name.  These must all belong
        to the same VPC.  This is a dict of the form:

        .. code-block:: yaml

            VpcConfig:
              SecurityGroupNames:
                - mysecgroup1
                - mysecgroup2
              SecurityGroupIds:
                - sg-abcdef1234
              SubnetNames:
                - mysubnet1
              SubnetIds:
                - subnet-1234abcd
                - subnet-abcd1234

        If VpcConfig is provided at all, you MUST pass at least one security group and one subnet.

    Permissions
        A list of permission definitions to be added to the function's policy

    RoleRetries
        IAM Roles may take some time to propagate to all regions once created.
        During that time function creation may fail; this state will
        atuomatically retry this number of times. The default is 5.

    Environment
        The parent object that contains your environment's configuration
        settings.  This is a dictionary of the form:

        .. code-block:: python

            {
                'Variables': {
                    'VariableName': 'VariableValue'
                }
            }

        .. versionadded:: 2017.7.0

    region
        Region to connect to.

    key
        Secret key to be used.

    keyid
        Access key to be used.

    profile
        A dict with region, key and keyid, or a pillar key (string) that
        contains a dict with region, key and keyid.
    '''
    ret = {'name': FunctionName,
           'result': True,
           'comment': '',
           'changes': {}
           }

    if Permissions is not None:
        if isinstance(Permissions, six.string_types):
            Permissions = salt.utils.json.loads(Permissions)
        required_keys = set(('Action', 'Principal'))
        optional_keys = set(('SourceArn', 'SourceAccount', 'Qualifier'))
        for sid, permission in six.iteritems(Permissions):
            keyset = set(permission.keys())
            if not keyset.issuperset(required_keys):
                raise SaltInvocationError('{0} are required for each permission '
                                          'specification'.format(', '.join(required_keys)))
            keyset = keyset - required_keys
            keyset = keyset - optional_keys
            if bool(keyset):
                raise SaltInvocationError(
                    'Invalid permission value {0}'.format(', '.join(keyset)))

    r = __salt__['boto_lambda.function_exists'](
        FunctionName=FunctionName, region=region,
        key=key, keyid=keyid, profile=profile)

    if 'error' in r:
        ret['result'] = False
        ret['comment'] = ('Failed to create function: '
                          '{0}.'.format(r['error']['message']))
        return ret

    if not r.get('exists'):
        if __opts__['test']:
            ret['comment'] = 'Function {0} is set to be created.'.format(
                FunctionName)
            ret['result'] = None
            return ret
        r = __salt__['boto_lambda.create_function'](
            FunctionName=FunctionName, Runtime=Runtime, Role=Role,
            Handler=Handler, ZipFile=ZipFile, S3Bucket=S3Bucket, S3Key=S3Key,
            S3ObjectVersion=S3ObjectVersion, Description=Description,
            Timeout=Timeout, MemorySize=MemorySize, VpcConfig=VpcConfig,
            Environment=Environment, WaitForRole=True, RoleRetries=RoleRetries,
            region=region, key=key, keyid=keyid, profile=profile)
        if not r.get('created'):
            ret['result'] = False
            ret['comment'] = ('Failed to create function: '
                              '{0}.'.format(r['error']['message']))
            return ret

        if Permissions:
            for sid, permission in six.iteritems(Permissions):
                r = __salt__['boto_lambda.add_permission'](
                    FunctionName=FunctionName, StatementId=sid,
                    region=region, key=key, keyid=keyid, profile=profile,
                    **permission)
                if not r.get('updated'):
                    ret['result'] = False
                    ret['comment'] = ('Failed to create function: '
                                      '{0}.'.format(r['error']['message']))

        _describe = __salt__['boto_lambda.describe_function'](
            FunctionName, region=region, key=key, keyid=keyid, profile=profile)
        _describe['function']['Permissions'] = (
            __salt__['boto_lambda.get_permissions'](
                FunctionName, region=region, key=key, keyid=keyid,
                profile=profile)['permissions'])
        ret['changes']['old'] = {'function': None}
        ret['changes']['new'] = _describe
        ret['comment'] = 'Function {0} created.'.format(FunctionName)
        return ret

    ret['comment'] = os.linesep.join(
        [ret['comment'], 'Function {0} is present.'.format(FunctionName)])
    ret['changes'] = {}
    # function exists, ensure config matches
    _ret = _function_config_present(FunctionName, Role, Handler, Description,
                                    Timeout, MemorySize, VpcConfig,
                                    Environment, region, key, keyid,
                                    profile, RoleRetries)
    if not _ret.get('result'):
        ret['result'] = _ret.get('result', False)
        ret['comment'] = _ret['comment']
        ret['changes'] = {}
        return ret
    ret['changes'] = dictupdate.update(ret['changes'], _ret['changes'])
    ret['comment'] = ' '.join([ret['comment'], _ret['comment']])
    _ret = _function_code_present(FunctionName, ZipFile, S3Bucket, S3Key, S3ObjectVersion,
                                  region, key, keyid, profile)
    if not _ret.get('result'):
        ret['result'] = _ret.get('result', False)
        ret['comment'] = _ret['comment']
        ret['changes'] = {}
        return ret
    ret['changes'] = dictupdate.update(ret['changes'], _ret['changes'])
    ret['comment'] = ' '.join([ret['comment'], _ret['comment']])
    _ret = _function_permissions_present(FunctionName, Permissions,
                                         region, key, keyid, profile)
    if not _ret.get('result'):
        ret['result'] = _ret.get('result', False)
        ret['comment'] = _ret['comment']
        ret['changes'] = {}
        return ret
    ret['changes'] = dictupdate.update(ret['changes'], _ret['changes'])
    ret['comment'] = ' '.join([ret['comment'], _ret['comment']])
    return ret