newACMEClientWithAccount creates an ACME client ready to use with an account, including loading one from storage or registering a new account with the CA if necessary. If useTestCA is true, am.TestCA will be used if set; otherwise, the primary CA will be used.
(ctx context.Context, useTestCA, interactive bool)
| 46 | // loading one from storage or registering a new account with the CA if necessary. If |
| 47 | // useTestCA is true, am.TestCA will be used if set; otherwise, the primary CA will be used. |
| 48 | func (iss *ACMEIssuer) newACMEClientWithAccount(ctx context.Context, useTestCA, interactive bool) (*acmeClient, error) { |
| 49 | // first, get underlying ACME client |
| 50 | client, err := iss.newACMEClient(useTestCA) |
| 51 | if err != nil { |
| 52 | return nil, err |
| 53 | } |
| 54 | |
| 55 | // we try loading the account from storage before a potential |
| 56 | // lock, and after obtaining the lock as well, to ensure we don't |
| 57 | // repeat work done by another instance or goroutine |
| 58 | account, err := iss.getAccountToUse(ctx, client.Directory) |
| 59 | if err != nil { |
| 60 | return nil, err |
| 61 | } |
| 62 | |
| 63 | // register account if it is new |
| 64 | if account.Status == "" { |
| 65 | iss.Logger.Info("ACME account has empty status; registering account with ACME server", |
| 66 | zap.Strings("contact", account.Contact), |
| 67 | zap.String("location", account.Location)) |
| 68 | |
| 69 | // synchronize this so the account is only created once |
| 70 | acctLockKey := accountRegLockKey(account) |
| 71 | err = acquireLock(ctx, iss.config.Storage, acctLockKey) |
| 72 | if err != nil { |
| 73 | return nil, fmt.Errorf("locking account registration: %v", err) |
| 74 | } |
| 75 | defer func() { |
| 76 | if err := releaseLock(ctx, iss.config.Storage, acctLockKey); err != nil { |
| 77 | iss.Logger.Error("failed to unlock account registration lock", zap.Error(err)) |
| 78 | } |
| 79 | }() |
| 80 | |
| 81 | // if we're not the only one waiting for this account, then by this point it should already be registered and in storage; reload it |
| 82 | account, err = iss.getAccountToUse(ctx, client.Directory) |
| 83 | if err != nil { |
| 84 | return nil, err |
| 85 | } |
| 86 | |
| 87 | // if we are the only or first one waiting for this account, then proceed to register it while we have the lock |
| 88 | if account.Status == "" { |
| 89 | if iss.NewAccountFunc != nil { |
| 90 | // obtain lock here, since NewAccountFunc calls happen concurrently and they typically read and change the issuer |
| 91 | iss.mu.Lock() |
| 92 | account, err = iss.NewAccountFunc(ctx, iss, account) |
| 93 | iss.mu.Unlock() |
| 94 | if err != nil { |
| 95 | return nil, fmt.Errorf("account pre-registration callback: %v", err) |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | // agree to terms |
| 100 | if interactive { |
| 101 | if !iss.isAgreed() { |
| 102 | var termsURL string |
| 103 | dir, err := client.GetDirectory(ctx) |
| 104 | if err != nil { |
| 105 | return nil, fmt.Errorf("getting directory: %w", err) |
no test coverage detected