go-traefik-certmanager/pkg/certmanager/certificate.go

153 lines
3.6 KiB
Go

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
}