Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the --cert-name flag to select a lineage by its name. #3785

Merged
merged 26 commits into from Dec 8, 2016

Conversation

ohemorange
Copy link
Contributor

Step 3 of #3615.

@ohemorange ohemorange added this to the 0.10.0 milestone Nov 11, 2016
@ohemorange
Copy link
Contributor Author

Pushing to github to see missing comment updates and test coverage, not ready for review.

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.2%) to 98.585% when pulling c23d31c on certname-flag into 1dd1afd on master.

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.1%) to 98.644% when pulling 22642bc on certname-flag into 1dd1afd on master.

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.1%) to 98.699% when pulling ebbeaf5 on certname-flag into 908e8a8 on master.

@ohemorange
Copy link
Contributor Author

Update: this also contains a --new-cert-name flag to go along with a rename command. I'm starting to think that it may be appropriate to move verbs like rename, certificates, update_symlinks, and revoke to an interactive cert manager, but that is definitely not appropriate for this PR.

Once coverage and tests are happy I expect this to be ready for review.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.07%) to 98.921% when pulling da6a477 on certname-flag into 908e8a8 on master.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.07%) to 98.921% when pulling a63004a on certname-flag into 7951ba7 on master.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.07%) to 98.914% when pulling da05230 on certname-flag into 83966cd on master.

@schoen
Copy link
Contributor

schoen commented Nov 30, 2016

Test 1: I have an --apache lineage called tomarbanhoeruim.loyalty.org and I ran

certbot certonly --cert-name tomarbanhoeruim.loyalty.org --force-renew

But I was interactively prompted for an authenticator, which I didn't expect.

I then tried

certbot run --cert-name tomarbanhoeruim.loyalty.org --force-renew

In this case the renewal appeared to work, but I got mysterious behavior on the install attempt:

Performing the following challenges:
tls-sni-01 challenge for tomarbanhoeruim.loyalty.org
Waiting for verification...
Cleaning up challenges
Generating key (2048 bits): /etc/letsencrypt/keys/0016_key-certbot.pem
Creating CSR: /etc/letsencrypt/csr/0016_csr-certbot.pem
An unexpected error occurred:
TypeError: 'NoneType' object is not iterable
Please see the logfiles in /var/log/letsencrypt for more details.

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/tomarbanhoeruim.loyalty.org/fullchain.pem.

(Also not sure why I can get "Congratulations" with the error: I guess because auth worked but install didn't?)

2016-11-30 19:40:39,374:DEBUG:certbot.main:Exiting abnormally:
Traceback (most recent call last):
  File "/tmp/certbot/venv/bin/certbot", line 11, in <module>
    load_entry_point('certbot', 'console_scripts', 'certbot')()
  File "/tmp/certbot/certbot/main.py", line 806, in main
    return config.func(config, plugins)
  File "/tmp/certbot/certbot/main.py", line 555, in run
    lineage.chain, lineage.fullchain)
  File "/tmp/certbot/certbot/client.py", line 371, in deploy_certificate
    for dom in domains:
TypeError: 'NoneType' object is not iterable

Can any of these behaviors be attributed to this PR? I can try to run the same commands with an older branch for comparison.

Copy link
Contributor

@schoen schoen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are all textual/documentation related but I also wonder if you can repeat my tests in an environment of your own and see if you get the same problems, or if it succeeds for you.


"""
if not config.certname:
raise errors.ConfigurationError("Specify a certificate name with "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might be clearer as "Specify an existing certificate name with" (for parallelism with the other message).

"--cert-name", dest="certname",
metavar="CERTNAME", default=None,
help="Certificate name to apply. Only one certificate name can be used "
"per Certbot run. Show certificate names by running certificates "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the switch between addressees of the imperative verbs here ("Show certificate names" and "create a new certificate") will confuse readers, because one is saying "[you can] show certificate names by..." and the other is saying "[the program will] create a new certificate...".

Could we change the text for the "Show certificate names" sentence to something like

(You can see certificate names by running certificates command.)

or

(To see certificate names, run certificates command.)

or

(Names can be found with the certificates command.)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes and maybe also better to say something like by running "certbot certificates" or by running cerbot with the "certificates" subcommand?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call. I like To see certificate names, run "certbot certificates".

logger.debug("Traceback was:\n%s", traceback.format_exc())
continue
rv = func(candidate_lineage, rv)
return rv
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was originally wondering about the case where there's more than one matching lineage and whether we'd simply get func() of the last one, but now I see that since the intermediate rv is an argument to func, someone writing a func could also choose to have it accumulate values as it goes, like in a list, if they want information about multiple lineages. So, nice, flexible design there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flexibility is my goal! I'll put this in the docstring to make it clear.

"rename",
"--new-cert-name", dest="new_certname",
metavar="NEW_CERTNAME", default=None,
help="New name for the certificate. Must be a valid filename.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it valid to use only --new-cert-name with a new lineage, instead of --cert-name? (Are they synonyms when there is no existing lineage with the specified name?) I can definitely imagine some users mentally parsing this as (new-cert)-name instead of new-(cert-name) and so trying to use it in that case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh good point. Maybe --updated-cert-name is better? Or just --new-certname and then also call it --certname in other places? I don't want to make this flag for anything other than setting a new cert name.

"automation", "--renew-with-new-domains",
action="store_true", dest="renew_with_new_domains", help="If a "
"certificate already exists for the requested certificate name "
"but does not match the request domains, renew it now, "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should probably be "requested domains" here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it should.

@@ -172,7 +172,7 @@ def _handle_identical_cert_request(config, lineage):
return "reinstall", lineage
question = (
"You have an existing certificate that contains exactly the same "
"domains you requested and isn't close to expiry."
"domains or certificate name you requested and isn't close to expiry."
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this would be clearer as "has exactly the same domains or certificate name", since people might not think of the certificate as "containing" its own certificate name? (At least knowing the implementation details, I don't.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're feeling fancy, provide a question whose wording varies depending on whether the user actually provided domains or a certificate name (or if you agree that's a good idea but don't want to do it in this PR, open a new ticket for it).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll put it to has for now.

"""
if config.renew_with_new_domains:
return
msg = ("Confirm that you intend to update certificate {0} "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be helpful or unhelpful to put quotes around the lineage name when displaying it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no opinion about this. Let me know if you want me to change it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess I'm also not sure, so I won't suggest a change.

@@ -96,6 +96,25 @@ def write_renewal_config(o_filename, n_filename, archive_dir, target, relevant_d
config.write(outfile=f)
return config

def rename_renewal_config(prev_name, new_name, cli_config):
"""Rename's cli_config.certname's config to cli_config.new_certname.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be "Renames"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol

Copy link
Member

@pde pde left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of changes requested to use the IDisplay mechanisms for [non]interactivity in standard ways, plus a small change in storage, plus some nits/optional suggestions.

new_domains,
old_domains))
obj = zope.component.getUtility(interfaces.IDisplay)
if not obj.yesno(msg, "Update cert", "Cancel"):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks to @PaulSD for noticing this: you should add default=True to this obj.yesno() call, so that this path can be run non-interactively.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had added a cli flag because I didn't know about this default=True thing. It seems like we usually have both a noninteractive option and a cli flag, is that right? Or should I remove the cli flag given that it can be run noninteractively?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. In most cases the CLI flags of that sort (like --keep-until-expiring) predated the -n flag, so we've never asked ourselves that question!

Argument 1: -n does the job, so let's just make everyone use it if they want non-interactive execution

Argument 2: the user might want to be asked about --domains interactively, but not this update confirm thing, so they might want to provide the flag for "Update" on the command line (or put it in /etc/letsencrypt/cli.ini) while still running interactively.

Which do we find more persuasive?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep both, because a user might not know about non-interactive mode and just look for the specific command-line flag they want.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I don't feel strongly, I think it is the user's responsibility to look at the documentation of the available flags more closely if (s)he doesn't immediately find what (s)he needs.

Furthermore, if you're running in interactive mode, I don't think it is so bad to get an extra question; in other words, I don't think there needs to be a switch to mute specific aspects of the interactive mode. (The number of questions is low anyways.) It's a trade-off with code simplicity, which I also consider valuable.

raise errors.ConfigurationError("Specify a certificate name with "
"with flag --cert-name.")
if not config.new_certname:
raise errors.ConfigurationError("Specify a new name for certificate {0} "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be errors.MissingCommandlineFlag.

But even better would be to do something like calling disp.input("Enter the new name for certificate {0}".format(config.certname), flag="--new-cert-name"). That will raise a nicely formatted errors.MissingCommandlineFlag for you.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah! I didn't know we had that but I like it!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition, the previous check can probably provide a list of cert names.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok it now does that.

def lineage_for_certname(config, certname):
"""Find a lineage object with name certname.
"""
def func(candidate_lineage, rv):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style nit: Maybe nicer to call this matches_certname or certname_match_fn or something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function returns a lineage object for a given certname. Is it that the name is too long?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My nit was suggesting something slightly more descriptive instead of func. But feel free to ignore this suggestion!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh that makes more sense! I can do that.

"--cert-name", dest="certname",
metavar="CERTNAME", default=None,
help="Certificate name to apply. Only one certificate name can be used "
"per Certbot run. Show certificate names by running certificates "
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes and maybe also better to say something like by running "certbot certificates" or by running cerbot with the "certificates" subcommand?

@@ -68,17 +70,17 @@ def _report_successful_dry_run(config):
reporter_util.HIGH_PRIORITY, on_crash=False)


def _auth_from_domains(le_client, config, domains, lineage=None):
def _auth_from_available(le_client, config, domains=None, certname=None, lineage=None):
"""Authenticate and enroll certificate.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should document what is going on here a little bit more?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ha yes. Especially given that this is the primary main authentication method, as far as I can tell.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup; it was already mysterious, and is becoming more so :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you feel about the current documentation?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@@ -172,7 +172,7 @@ def _handle_identical_cert_request(config, lineage):
return "reinstall", lineage
question = (
"You have an existing certificate that contains exactly the same "
"domains you requested and isn't close to expiry."
"domains or certificate name you requested and isn't close to expiry."
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're feeling fancy, provide a question whose wording varies depending on whether the user actually provided domains or a certificate name (or if you agree that's a good idea but don't want to do it in this PR, open a new ticket for it).

cli_config.renewal_configs_dir, prev_name) + ".conf"
new_filename = os.path.join(
cli_config.renewal_configs_dir, new_name) + ".conf"
if os.path.isfile(new_filename):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should probably be os.path.exists(new_filename), so that it errors on directories, links, etc.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.05%) to 98.894% when pulling 4374876 on certname-flag into 83966cd on master.

@schoen
Copy link
Contributor

schoen commented Dec 1, 2016

@ohemorange, yes, I'll try getting a new lineage with master, then running the closest equivalents to those, then running those with this branch, and see if the problem recurs or not.

self.assertTrue(mock_renewer_config)


class RenameLineageTest(storage_test.BaseRenewableCertTest):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a BaseCertManagerTest that will work here, why not use that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What a good idea, I should do that!

@coveralls
Copy link

Coverage Status

Coverage increased (+0.07%) to 98.912% when pulling 78d9cf8 on certname-flag into 83966cd on master.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.07%) to 98.912% when pulling 4675186 on certname-flag into 83966cd on master.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.07%) to 98.912% when pulling 2c87cbb on certname-flag into 83966cd on master.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.07%) to 98.923% when pulling e6e3261 on certname-flag into 65d9e99 on master.

@ohemorange
Copy link
Contributor Author

Make coveralls report back?

@ohemorange ohemorange closed this Dec 6, 2016
@ohemorange ohemorange reopened this Dec 6, 2016
@coveralls
Copy link

Coverage Status

Coverage increased (+0.07%) to 98.925% when pulling f56a11d on certname-flag into 184d673 on master.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.07%) to 98.925% when pulling f56a11d on certname-flag into 184d673 on master.

@pde pde assigned ohemorange and unassigned schoen and bmw Dec 8, 2016
@ohemorange ohemorange merged commit 0a7ca2f into master Dec 8, 2016
@ohemorange ohemorange deleted the certname-flag branch December 8, 2016 19:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants