Obtain/renew a certificate from an ACME CA, probably Let's Encrypt.

    :param name: Common Name of the certificate (DNS name of certificate)
    :param aliases: subjectAltNames (Additional DNS names on certificate)
    :param email: e-mail address for interaction with ACME provider
    :param webroot: True or a full path to use to use webroot. Otherwise use standalone mode
    :param test_cert: Request a certificate from the Happy Hacker Fake CA (mutually exclusive with 'server')
    :param renew: True/'force' to force a renewal, or a window of renewal before expiry in days
    :param keysize: RSA key bits
    :param server: API endpoint to talk to
    :param owner: owner of the private key file
    :param group: group of the private key file
    :param mode: mode of the private key file
    :param certname: Name of the certificate to save
    :param preferred_challenges: A sorted, comma delimited list of the preferred
                                 challenge to use during authorization with the
                                 most preferred challenge listed first.
    :param tls_sni_01_port: Port used during tls-sni-01 challenge. This only affects
                            the port Certbot listens on. A conforming ACME server
                            will still attempt to connect on port 443.
    :param tls_sni_01_address: The address the server listens to during tls-sni-01
                               challenge.
    :param http_01_port: Port used in the http-01 challenge. This only affects
                         the port Certbot listens on. A conforming ACME server
                         will still attempt to connect on port 80.
    :param https_01_address: The address the server listens to during http-01 challenge.
    :param dns_plugin: Name of a DNS plugin to use (currently only 'cloudflare' or 'digitalocean')
    :param dns_plugin_credentials: Path to the credentials file if required by the specified DNS plugin
    :param dns_plugin_propagate_seconds: Number of seconds to wait for DNS propogations before
                                asking ACME servers to verify the DNS record. (default 10)
    :return: dict with 'result' True/False/None, 'comment' and certificate's expiry date ('not_after')

    CLI example:

    .. code-block:: bash

        salt 'gitlab.example.com' acme.cert dev.example.com "[gitlab.example.com]" test_cert=True renew=14 webroot=/opt/gitlab/embedded/service/gitlab-rails/public

def cert(name,
         aliases=None,
         email=None,
         webroot=None,
         test_cert=False,
         renew=None,
         keysize=None,
         server=None,
         owner='root',
         group='root',
         mode='0640',
         certname=None,
         preferred_challenges=None,
         tls_sni_01_port=None,
         tls_sni_01_address=None,
         http_01_port=None,
         http_01_address=None,
         dns_plugin=None,
         dns_plugin_credentials=None,
         dns_plugin_propagate_seconds=10):
    '''
    Obtain/renew a certificate from an ACME CA, probably Let's Encrypt.

    :param name: Common Name of the certificate (DNS name of certificate)
    :param aliases: subjectAltNames (Additional DNS names on certificate)
    :param email: e-mail address for interaction with ACME provider
    :param webroot: True or a full path to use to use webroot. Otherwise use standalone mode
    :param test_cert: Request a certificate from the Happy Hacker Fake CA (mutually exclusive with 'server')
    :param renew: True/'force' to force a renewal, or a window of renewal before expiry in days
    :param keysize: RSA key bits
    :param server: API endpoint to talk to
    :param owner: owner of the private key file
    :param group: group of the private key file
    :param mode: mode of the private key file
    :param certname: Name of the certificate to save
    :param preferred_challenges: A sorted, comma delimited list of the preferred
                                 challenge to use during authorization with the
                                 most preferred challenge listed first.
    :param tls_sni_01_port: Port used during tls-sni-01 challenge. This only affects
                            the port Certbot listens on. A conforming ACME server
                            will still attempt to connect on port 443.
    :param tls_sni_01_address: The address the server listens to during tls-sni-01
                               challenge.
    :param http_01_port: Port used in the http-01 challenge. This only affects
                         the port Certbot listens on. A conforming ACME server
                         will still attempt to connect on port 80.
    :param https_01_address: The address the server listens to during http-01 challenge.
    :param dns_plugin: Name of a DNS plugin to use (currently only 'cloudflare' or 'digitalocean')
    :param dns_plugin_credentials: Path to the credentials file if required by the specified DNS plugin
    :param dns_plugin_propagate_seconds: Number of seconds to wait for DNS propogations before
                                asking ACME servers to verify the DNS record. (default 10)
    :return: dict with 'result' True/False/None, 'comment' and certificate's expiry date ('not_after')

    CLI example:

    .. code-block:: bash

        salt 'gitlab.example.com' acme.cert dev.example.com "[gitlab.example.com]" test_cert=True renew=14 webroot=/opt/gitlab/embedded/service/gitlab-rails/public
    '''

    cmd = [LEA, 'certonly', '--non-interactive', '--agree-tos']

    supported_dns_plugins = ['cloudflare', 'digitalocean']

    cert_file = _cert_file(name, 'cert')
    if not __salt__['file.file_exists'](cert_file):
        log.debug('Certificate %s does not exist (yet)', cert_file)
        renew = False
    elif needs_renewal(name, renew):
        log.debug('Certificate %s will be renewed', cert_file)
        cmd.append('--renew-by-default')
        renew = True
    if server:
        cmd.append('--server {0}'.format(server))

    if certname:
        cmd.append('--cert-name {0}'.format(certname))

    if test_cert:
        if server:
            return {'result': False, 'comment': 'Use either server or test_cert, not both'}
        cmd.append('--test-cert')

    if webroot:
        cmd.append('--authenticator webroot')
        if webroot is not True:
            cmd.append('--webroot-path {0}'.format(webroot))
    elif dns_plugin in supported_dns_plugins:
        if dns_plugin == 'cloudflare':
            cmd.append('--dns-cloudflare')
            cmd.append('--dns-cloudflare-credentials {0}'.format(dns_plugin_credentials))
            cmd.append('--dns-cloudflare-propagation-seconds {0}'.format(dns_plugin_propagate_seconds))
        elif dns_plugin == 'digitalocean':
            cmd.append('--dns-digitalocean')
            cmd.append('--dns-digitalocean-credentials {0}'.format(dns_plugin_credentials))
            cmd.append('--dns-digitalocean-propagation-seconds {0}'.format(dns_plugin_propagate_seconds))
        else:
            return {'result': False, 'comment': 'DNS plugin \'{0}\' is not supported'.format(dns_plugin)}
    else:
        cmd.append('--authenticator standalone')

    if email:
        cmd.append('--email {0}'.format(email))

    if keysize:
        cmd.append('--rsa-key-size {0}'.format(keysize))

    cmd.append('--domains {0}'.format(name))
    if aliases is not None:
        for dns in aliases:
            cmd.append('--domains {0}'.format(dns))

    if preferred_challenges:
        cmd.append('--preferred-challenges {}'.format(preferred_challenges))

    if tls_sni_01_port:
        cmd.append('--tls-sni-01-port {}'.format(tls_sni_01_port))
    if tls_sni_01_address:
        cmd.append('--tls-sni-01-address {}'.format(tls_sni_01_address))
    if http_01_port:
        cmd.append('--http-01-port {}'.format(http_01_port))
    if http_01_address:
        cmd.append('--http-01-address {}'.format(http_01_address))

    res = __salt__['cmd.run_all'](' '.join(cmd))

    if res['retcode'] != 0:
        if 'expand' in res['stderr']:
            cmd.append('--expand')
            res = __salt__['cmd.run_all'](' '.join(cmd))
            if res['retcode'] != 0:
                return {'result': False, 'comment': 'Certificate {0} renewal failed with:\n{1}'.format(name, res['stderr'])}
        else:
            return {'result': False, 'comment': 'Certificate {0} renewal failed with:\n{1}'.format(name, res['stderr'])}

    if 'no action taken' in res['stdout']:
        comment = 'Certificate {0} unchanged'.format(cert_file)
        result = None
    elif renew:
        comment = 'Certificate {0} renewed'.format(name)
        result = True
    else:
        comment = 'Certificate {0} obtained'.format(name)
        result = True

    ret = {'comment': comment, 'not_after': expires(name), 'changes': {}, 'result': result}
    ret, _ = __salt__['file.check_perms'](_cert_file(name, 'privkey'),
                                          ret,
                                          owner, group, mode,
                                          follow_symlinks=True)

    return ret