Refactor Issuer interface to allow returning updated Status
This commit is contained in:
parent
8e39ddfc2e
commit
00389b6da3
@ -6,17 +6,14 @@ import (
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/acme"
|
||||
api "k8s.io/api/core/v1"
|
||||
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
corev1listers "k8s.io/client-go/listers/core/v1"
|
||||
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/log"
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/util"
|
||||
)
|
||||
|
||||
@ -24,43 +21,11 @@ const (
|
||||
acmeAccountPrivateKeyKey = "key.pem"
|
||||
)
|
||||
|
||||
type account struct {
|
||||
issuer *v1alpha1.Issuer
|
||||
|
||||
client kubernetes.Interface
|
||||
secretsLister corev1listers.SecretLister
|
||||
}
|
||||
|
||||
func newAccount(issuer *v1alpha1.Issuer, client kubernetes.Interface, secretsLister corev1listers.SecretLister) *account {
|
||||
return &account{issuer, client, secretsLister}
|
||||
}
|
||||
|
||||
func (a *account) uri() string {
|
||||
if a.issuer.Status.ACME == nil {
|
||||
return ""
|
||||
}
|
||||
return a.issuer.Status.ACME.URI
|
||||
}
|
||||
|
||||
func (a *account) email() string {
|
||||
if a.issuer.Spec.ACME == nil {
|
||||
return ""
|
||||
}
|
||||
return a.issuer.Spec.ACME.Email
|
||||
}
|
||||
|
||||
func (a *account) server() string {
|
||||
if a.issuer.Spec.ACME == nil {
|
||||
return ""
|
||||
}
|
||||
return a.issuer.Spec.ACME.Server
|
||||
}
|
||||
|
||||
// privateKey returns the private key for this account from the given context,
|
||||
// or an error
|
||||
// TODO (@munnerz): how can we support different types of private keys other
|
||||
// than rsa?
|
||||
func (a *account) privateKey() (*rsa.PrivateKey, error) {
|
||||
func (a *Acme) accountPrivateKey() (*rsa.PrivateKey, error) {
|
||||
if a.issuer.Spec.ACME == nil {
|
||||
return nil, fmt.Errorf("acme spec block cannot be empty")
|
||||
}
|
||||
@ -99,7 +64,7 @@ func (a *account) privateKey() (*rsa.PrivateKey, error) {
|
||||
}
|
||||
|
||||
// verify verifies an acme account is valid with the acme server
|
||||
func (a *account) verify() error {
|
||||
func (a *Acme) verifyAccount() error {
|
||||
if a.issuer.Spec.ACME.Server == "" {
|
||||
return fmt.Errorf("acme server url must be set")
|
||||
}
|
||||
@ -107,10 +72,9 @@ func (a *account) verify() error {
|
||||
return fmt.Errorf("acme account uri must be set")
|
||||
}
|
||||
|
||||
privateKey, err := a.privateKey()
|
||||
privateKey, err := a.accountPrivateKey()
|
||||
|
||||
if err != nil {
|
||||
a.issuer.Status.Ready = false
|
||||
return err
|
||||
}
|
||||
|
||||
@ -133,25 +97,25 @@ func (a *account) verify() error {
|
||||
}
|
||||
|
||||
// register will register an account with the acme server and store the account
|
||||
// details in the context
|
||||
// details in the API server. It returns the registered account URI, or an error
|
||||
// TODO: break this function down
|
||||
func (a *account) register() error {
|
||||
func (a *Acme) registerAccount() (string, error) {
|
||||
if a.issuer.Spec.ACME.Server == "" {
|
||||
return fmt.Errorf("acme server url must be set")
|
||||
return "", fmt.Errorf("acme server url must be set")
|
||||
}
|
||||
|
||||
privateKey, err := a.privateKey()
|
||||
privateKey, err := a.accountPrivateKey()
|
||||
var privateKeyPem []byte
|
||||
if err != nil {
|
||||
if !k8sErrors.IsNotFound(err) {
|
||||
return fmt.Errorf("error getting private key: %s", err.Error())
|
||||
return "", fmt.Errorf("error getting private key: %s", err.Error())
|
||||
}
|
||||
|
||||
// TODO (@munnerz): allow changing the keysize
|
||||
privateKeyPem, privateKey, err = generatePrivateKey(2048)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error generating private key: %s", err.Error())
|
||||
return "", fmt.Errorf("error generating private key: %s", err.Error())
|
||||
}
|
||||
|
||||
_, err = util.EnsureSecret(a.client, &api.Secret{
|
||||
@ -165,7 +129,7 @@ func (a *account) register() error {
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error saving private key: %s", err.Error())
|
||||
return "", fmt.Errorf("error saving private key: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,19 +150,17 @@ func (a *account) register() error {
|
||||
var acmeErr *acme.Error
|
||||
var ok bool
|
||||
if acmeErr, ok = err.(*acme.Error); !ok || (acmeErr.StatusCode != 409) {
|
||||
return fmt.Errorf("error registering acme account: %s", err.Error())
|
||||
return "", fmt.Errorf("error registering acme account: %s", err.Error())
|
||||
}
|
||||
|
||||
if a.issuer.Status.ACME == nil || a.issuer.Status.ACME.URI == "" {
|
||||
return fmt.Errorf("private key already registered but user URI not found. delete existing private key or set acme account URI")
|
||||
return "", fmt.Errorf("private key already registered but user URI not found. delete existing private key or set acme account URI")
|
||||
}
|
||||
|
||||
if account, err = cl.UpdateReg(context.Background(), acc); err != nil {
|
||||
return fmt.Errorf("error updating acme account registration: %s", err.Error())
|
||||
return "", fmt.Errorf("error updating acme account registration: %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
a.issuer.Status.ACMEStatus().URI = account.URI
|
||||
|
||||
return nil
|
||||
return account.URI, nil
|
||||
}
|
||||
|
||||
@ -3,25 +3,28 @@ package acme
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/client-go/informers"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
coreinformers "k8s.io/client-go/informers/core/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
corelisters "k8s.io/client-go/listers/core/v1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/client"
|
||||
cminformers "github.com/jetstack-experimental/cert-manager/pkg/informers"
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/issuer"
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/issuer/acme/dns"
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/issuer/acme/http"
|
||||
)
|
||||
|
||||
type Acme struct {
|
||||
account *account
|
||||
issuer *v1alpha1.Issuer
|
||||
|
||||
client kubernetes.Interface
|
||||
cmClient client.Interface
|
||||
factory informers.SharedInformerFactory
|
||||
cmFactory cminformers.SharedInformerFactory
|
||||
client kubernetes.Interface
|
||||
cmClient client.Interface
|
||||
|
||||
secretsLister corelisters.SecretLister
|
||||
|
||||
dnsSolver solver
|
||||
httpSolver solver
|
||||
@ -30,16 +33,19 @@ type Acme struct {
|
||||
func New(issuer *v1alpha1.Issuer,
|
||||
client kubernetes.Interface,
|
||||
cmClient client.Interface,
|
||||
factory informers.SharedInformerFactory,
|
||||
cmFactory cminformers.SharedInformerFactory) (issuer.Interface, error) {
|
||||
secretsInformer cache.SharedIndexInformer) (issuer.Interface, error) {
|
||||
if issuer.Spec.ACME == nil {
|
||||
return nil, fmt.Errorf("acme config may not be empty")
|
||||
}
|
||||
|
||||
secretsLister := corelisters.NewSecretLister(secretsInformer.GetIndexer())
|
||||
return &Acme{
|
||||
account: newAccount(issuer, client, factory.Core().V1().Secrets().Lister()),
|
||||
client: client,
|
||||
cmClient: cmClient,
|
||||
factory: factory,
|
||||
cmFactory: cmFactory,
|
||||
dnsSolver: dns.NewSolver(issuer, client, factory.Core().V1().Secrets().Lister()),
|
||||
httpSolver: http.NewSolver(issuer, client, factory.Core().V1().Secrets().Lister()),
|
||||
issuer: issuer,
|
||||
client: client,
|
||||
cmClient: cmClient,
|
||||
secretsLister: secretsLister,
|
||||
dnsSolver: dns.NewSolver(issuer, client, secretsLister),
|
||||
httpSolver: http.NewSolver(issuer, client, secretsLister),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -60,5 +66,15 @@ func (a *Acme) solverFor(challengeType string) (solver, error) {
|
||||
}
|
||||
|
||||
func init() {
|
||||
issuer.SharedFactory().Register(issuer.IssuerACME, New)
|
||||
issuer.Register(issuer.IssuerACME, func(i *v1alpha1.Issuer, ctx *issuer.Context) (issuer.Interface, error) {
|
||||
return New(
|
||||
i,
|
||||
ctx.Client,
|
||||
ctx.CMClient,
|
||||
ctx.SharedInformerFactory.InformerFor(
|
||||
ctx.Namespace,
|
||||
metav1.GroupVersionKind{Version: "v1", Kind: "Secret"},
|
||||
coreinformers.NewSecretInformer(ctx.Client, ctx.Namespace, time.Second*30, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
@ -23,7 +24,6 @@ import (
|
||||
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/issuer/acme/http/solver"
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/log"
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/util"
|
||||
)
|
||||
|
||||
|
||||
@ -9,17 +9,17 @@ import (
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"golang.org/x/crypto/acme"
|
||||
api "k8s.io/api/core/v1"
|
||||
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/log"
|
||||
)
|
||||
|
||||
func (a *Acme) getCertificatePrivateKey(crt *v1alpha1.Certificate) ([]byte, crypto.Signer, error) {
|
||||
crtSecret, err := a.factory.Core().V1().Secrets().Lister().Secrets(crt.Namespace).Get(crt.Spec.SecretName)
|
||||
crtSecret, err := a.secretsLister.Secrets(crt.Namespace).Get(crt.Spec.SecretName)
|
||||
if err != nil {
|
||||
if !k8sErrors.IsNotFound(err) {
|
||||
return nil, nil, fmt.Errorf("error reading certificate private key for certificate '%s': %s", crt.Name, err.Error())
|
||||
@ -53,7 +53,7 @@ func (a *Acme) obtainCertificate(crt *v1alpha1.Certificate) (privateKeyPem []byt
|
||||
return nil, nil, fmt.Errorf("no domains specified")
|
||||
}
|
||||
|
||||
privKey, err := a.account.privateKey()
|
||||
privKey, err := a.accountPrivateKey()
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error getting acme account private key: %s", err.Error())
|
||||
@ -61,7 +61,7 @@ func (a *Acme) obtainCertificate(crt *v1alpha1.Certificate) (privateKeyPem []byt
|
||||
|
||||
cl := &acme.Client{
|
||||
Key: privKey,
|
||||
DirectoryURL: a.account.server(),
|
||||
DirectoryURL: a.issuer.Spec.ACME.Server,
|
||||
}
|
||||
|
||||
template := x509.CertificateRequest{
|
||||
|
||||
@ -3,12 +3,12 @@ package acme
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
|
||||
"golang.org/x/crypto/acme"
|
||||
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/log"
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/util"
|
||||
)
|
||||
|
||||
@ -71,8 +71,8 @@ func (a *Acme) prepare(crt *v1alpha1.Certificate) error {
|
||||
return fmt.Errorf("acme config must be specified")
|
||||
}
|
||||
|
||||
log.Printf("getting private key for acme issuer %s/%s", a.account.issuer.Namespace, a.account.issuer.Name)
|
||||
privKey, err := a.account.privateKey()
|
||||
log.Printf("getting private key for acme issuer %s/%s", a.issuer.Namespace, a.issuer.Name)
|
||||
privKey, err := a.accountPrivateKey()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting acme account private key: %s", err.Error())
|
||||
@ -80,7 +80,7 @@ func (a *Acme) prepare(crt *v1alpha1.Certificate) error {
|
||||
|
||||
cl := &acme.Client{
|
||||
Key: privKey,
|
||||
DirectoryURL: a.account.server(),
|
||||
DirectoryURL: a.issuer.Spec.ACME.Server,
|
||||
}
|
||||
|
||||
// step one: check issuer to see if we already have authorizations
|
||||
|
||||
@ -2,49 +2,28 @@ package acme
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
)
|
||||
|
||||
func (a *Acme) Setup() (err error) {
|
||||
issuerBefore := a.account.issuer.DeepCopy()
|
||||
func (a *Acme) Setup() (v1alpha1.IssuerStatus, error) {
|
||||
updateStatus := a.issuer.Status.DeepCopy()
|
||||
|
||||
defer func() {
|
||||
if !reflect.DeepEqual(issuerBefore, a.account.issuer) {
|
||||
if err == nil {
|
||||
_, err = a.cmClient.CertmanagerV1alpha1().Issuers(a.account.issuer.Namespace).Update(a.account.issuer)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
err = a.ensureSetup()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureSetup will ensure that this issuer is ready to issue certificates.
|
||||
// it
|
||||
func (a *Acme) ensureSetup() error {
|
||||
err := a.account.verify()
|
||||
err := a.verifyAccount()
|
||||
|
||||
if err == nil {
|
||||
a.account.issuer.Status.Ready = true
|
||||
return nil
|
||||
updateStatus.Ready = true
|
||||
return *updateStatus, nil
|
||||
}
|
||||
|
||||
a.account.issuer.Status.Ready = false
|
||||
|
||||
err = a.account.register()
|
||||
uri, err := a.registerAccount()
|
||||
|
||||
if err != nil {
|
||||
// don't write updated state as an actual error occurred
|
||||
return fmt.Errorf("error registering acme account: %s", err.Error())
|
||||
updateStatus.Ready = false
|
||||
return *updateStatus, fmt.Errorf("error registering acme account: %s", err.Error())
|
||||
}
|
||||
|
||||
a.account.issuer.Status.Ready = true
|
||||
updateStatus.ACMEStatus().URI = uri
|
||||
|
||||
return nil
|
||||
return *updateStatus, nil
|
||||
}
|
||||
|
||||
17
pkg/issuer/context.go
Normal file
17
pkg/issuer/context.go
Normal file
@ -0,0 +1,17 @@
|
||||
package issuer
|
||||
|
||||
import (
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/client"
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/kube"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
Client kubernetes.Interface
|
||||
CMClient client.Interface
|
||||
|
||||
SharedInformerFactory kube.SharedInformerFactory
|
||||
|
||||
Namespace string
|
||||
}
|
||||
@ -2,47 +2,32 @@ package issuer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/client"
|
||||
cminformers "github.com/jetstack-experimental/cert-manager/pkg/informers"
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/log"
|
||||
)
|
||||
|
||||
type Factory struct {
|
||||
constructors map[string]Constructor
|
||||
constructorsLock sync.RWMutex
|
||||
|
||||
client kubernetes.Interface
|
||||
cmClient client.Interface
|
||||
factory informers.SharedInformerFactory
|
||||
cmFactory cminformers.SharedInformerFactory
|
||||
type Factory interface {
|
||||
IssuerFor(*v1alpha1.Issuer) (Interface, error)
|
||||
}
|
||||
|
||||
func (f *Factory) Setup(client kubernetes.Interface,
|
||||
cmClient client.Interface,
|
||||
factory informers.SharedInformerFactory,
|
||||
cmFactory cminformers.SharedInformerFactory) {
|
||||
f.client = client
|
||||
f.cmClient = cmClient
|
||||
f.factory = factory
|
||||
f.cmFactory = cmFactory
|
||||
func NewFactory(ctx *Context) Factory {
|
||||
return &factory{ctx: ctx}
|
||||
}
|
||||
|
||||
func (f *Factory) IssuerFor(issuer *v1alpha1.Issuer) (Interface, error) {
|
||||
type factory struct {
|
||||
ctx *Context
|
||||
}
|
||||
|
||||
func (f *factory) IssuerFor(issuer *v1alpha1.Issuer) (Interface, error) {
|
||||
issuerType, err := nameForIssuer(issuer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get issuer type: %s", err.Error())
|
||||
}
|
||||
|
||||
f.constructorsLock.RLock()
|
||||
defer f.constructorsLock.RUnlock()
|
||||
if constructor, ok := f.constructors[issuerType]; ok {
|
||||
return constructor(issuer, f.client, f.cmClient, f.factory, f.cmFactory)
|
||||
constructorsLock.RLock()
|
||||
defer constructorsLock.RUnlock()
|
||||
if constructor, ok := constructors[issuerType]; ok {
|
||||
return constructor(issuer, f.ctx)
|
||||
}
|
||||
return nil, fmt.Errorf("issuer '%s' not registered", issuerType)
|
||||
}
|
||||
@ -50,9 +35,8 @@ func (f *Factory) IssuerFor(issuer *v1alpha1.Issuer) (Interface, error) {
|
||||
// Register will register an issuer constructor so it can be used within the
|
||||
// application. 'name' should be unique, and should be used to identify this
|
||||
// issuer.
|
||||
func (f *Factory) Register(name string, c Constructor) {
|
||||
f.constructorsLock.Lock()
|
||||
defer f.constructorsLock.Unlock()
|
||||
log.Printf("registered issuer '%s'", name)
|
||||
f.constructors[name] = c
|
||||
func Register(name string, c Constructor) {
|
||||
constructorsLock.Lock()
|
||||
defer constructorsLock.Unlock()
|
||||
constructors[name] = c
|
||||
}
|
||||
|
||||
@ -1,39 +0,0 @@
|
||||
package issuer
|
||||
|
||||
import (
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/client"
|
||||
cminformers "github.com/jetstack-experimental/cert-manager/pkg/informers"
|
||||
)
|
||||
|
||||
var sharedFactory = &Factory{
|
||||
constructors: make(map[string]Constructor),
|
||||
}
|
||||
|
||||
func SharedFactory() *Factory {
|
||||
return sharedFactory
|
||||
}
|
||||
|
||||
type Constructor func(issuer *v1alpha1.Issuer,
|
||||
client kubernetes.Interface,
|
||||
cmClient client.Interface,
|
||||
factory informers.SharedInformerFactory,
|
||||
cmFactory cminformers.SharedInformerFactory) (Interface, error)
|
||||
|
||||
type Interface interface {
|
||||
// Setup initialises the issuer. This may include registering accounts with
|
||||
// a service, creating a CA and storing it somewhere, or verifying
|
||||
// credentials and authorization with a remote server.
|
||||
Setup() error
|
||||
// Prepare
|
||||
Prepare(*v1alpha1.Certificate) error
|
||||
// Issue attempts to issue a certificate as described by the certificate
|
||||
// resource given
|
||||
Issue(*v1alpha1.Certificate) ([]byte, []byte, error)
|
||||
// Renew attempts to renew the certificate describe by the certificate
|
||||
// resource given. If no certificate exists, an error is returned.
|
||||
Renew(*v1alpha1.Certificate) ([]byte, []byte, error)
|
||||
}
|
||||
18
pkg/issuer/issuer.go
Normal file
18
pkg/issuer/issuer.go
Normal file
@ -0,0 +1,18 @@
|
||||
package issuer
|
||||
|
||||
import "github.com/jetstack-experimental/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
|
||||
type Interface interface {
|
||||
// Setup initialises the issuer. This may include registering accounts with
|
||||
// a service, creating a CA and storing it somewhere, or verifying
|
||||
// credentials and authorization with a remote server.
|
||||
Setup() (v1alpha1.IssuerStatus, error)
|
||||
// Prepare
|
||||
Prepare(*v1alpha1.Certificate) error
|
||||
// Issue attempts to issue a certificate as described by the certificate
|
||||
// resource given
|
||||
Issue(*v1alpha1.Certificate) ([]byte, []byte, error)
|
||||
// Renew attempts to renew the certificate describe by the certificate
|
||||
// resource given. If no certificate exists, an error is returned.
|
||||
Renew(*v1alpha1.Certificate) ([]byte, []byte, error)
|
||||
}
|
||||
14
pkg/issuer/register.go
Normal file
14
pkg/issuer/register.go
Normal file
@ -0,0 +1,14 @@
|
||||
package issuer
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/jetstack-experimental/cert-manager/pkg/apis/certmanager/v1alpha1"
|
||||
)
|
||||
|
||||
type Constructor func(*v1alpha1.Issuer, *Context) (Interface, error)
|
||||
|
||||
var (
|
||||
constructors = make(map[string]Constructor)
|
||||
constructorsLock sync.RWMutex
|
||||
)
|
||||
Loading…
Reference in New Issue
Block a user