package certmanager import ( "context" "encoding/json" "errors" "fmt" "regexp" "strings" cmv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1" cmmetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" ) const ( certGroup = "cert-manager.io" certVersion = "v1" certKind = "Certificate" certResource = "certificates" ) var ( ErrCertificateAlreadyExist = errors.New("certificate already exists") ErrCertificateToUnstructured = errors.New("certificate cannot get converted to unstructured") ErrCertificateCreation = errors.New("certificate creation error") ErrCertificateToJSON = errors.New("certificate cannot get converted to JSON") ) type certificateClient struct { client *Client gvr schema.GroupVersionResource } func newCertificateClient(client *Client) certificateClient { return certificateClient{ client: client, gvr: schema.GroupVersionResource{ Group: certGroup, Version: certVersion, Resource: certResource, }, } } func (c *certificateClient) Create( ctx context.Context, namespace, secretName string, routes []map[string]interface{}, ) error { _, err := c.client.crdClient.Resource(c.gvr).Namespace(namespace).Get(ctx, secretName, metav1.GetOptions{}) if err == nil { return ErrCertificateAlreadyExist } hosts := extractHosts(routes) cert := cmv1.Certificate{ TypeMeta: metav1.TypeMeta{ Kind: certKind, APIVersion: fmt.Sprintf("%s/%s", certGroup, certVersion), }, ObjectMeta: metav1.ObjectMeta{ Name: secretName, }, Spec: cmv1.CertificateSpec{ DNSNames: hosts, SecretName: secretName, IssuerRef: cmmetav1.ObjectReference{ Name: c.client.certIssuerName, Kind: c.client.certIssuerKind, }, }, } obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(cert) if err != nil { return errors.Join(ErrCertificateToUnstructured, err) } _, err = c.client.crdClient.Resource(c.gvr).Namespace(namespace).Create( ctx, &unstructured.Unstructured{Object: obj}, metav1.CreateOptions{}, ) if err != nil { return errors.Join(ErrCertificateCreation, err) } return nil } func (c *certificateClient) Delete(ctx context.Context, namespace, name string) error { return c.client.crdClient.Resource(c.gvr).Namespace(namespace).Delete(ctx, name, metav1.DeleteOptions{}) } func (c *certificateClient) Patch(ctx context.Context, namespace, name string, cert cmv1.Certificate) error { data, err := json.Marshal(cert) if err != nil { return errors.Join(ErrCertificateToJSON, err) } _, err = c.client.crdClient.Resource(c.gvr).Namespace(namespace).Patch( ctx, name, types.JSONPatchType, data, metav1.PatchOptions{}, ) return err } func (c *certificateClient) PatchSecretName(ctx context.Context, namespace, name, secretName string) error { cert := cmv1.Certificate{ Spec: cmv1.CertificateSpec{ SecretName: secretName, }, } return c.Patch(ctx, namespace, name, cert) } func extractHosts(routes []map[string]interface{}) []string { var hosts []string re := regexp.MustCompile(`Host\(([^)]*)\)`) for _, route := range routes { var ( kind string match string ok bool ) kind, ok = route["kind"].(string) if !ok || kind != "Rule" { continue } if match, ok = route["match"].(string); ok { hostMatches := re.FindAllStringSubmatch(match, -1) for _, match := range hostMatches { if len(match) > 1 { hosts = append(hosts, strings.Split(match[1], ",")...) } } } } return hosts }