cloudfoundry/cf-k8s-controllers

View on GitHub
api/repositories/domain_repository.go

Summary

Maintainability
A
0 mins
Test Coverage
B
88%
package repositories

import (
    "context"
    "fmt"
    "sort"
    "time"

    "code.cloudfoundry.org/korifi/api/authorization"
    apierrors "code.cloudfoundry.org/korifi/api/errors"
    korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1"
    "code.cloudfoundry.org/korifi/tools/k8s"
    "github.com/google/uuid"

    k8serrors "k8s.io/apimachinery/pkg/api/errors"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    "sigs.k8s.io/controller-runtime/pkg/client"
)

const (
    DomainResourceType = "Domain"
)

type DomainRepo struct {
    userClientFactory  authorization.UserK8sClientFactory
    namespaceRetriever NamespaceRetriever
    rootNamespace      string
}

func NewDomainRepo(
    userClientFactory authorization.UserK8sClientFactory,
    namespaceRetriever NamespaceRetriever,
    rootNamespace string,
) *DomainRepo {
    return &DomainRepo{
        userClientFactory:  userClientFactory,
        namespaceRetriever: namespaceRetriever,
        rootNamespace:      rootNamespace,
    }
}

type DomainRecord struct {
    Name        string
    GUID        string
    Labels      map[string]string
    Annotations map[string]string
    Namespace   string
    CreatedAt   time.Time
    UpdatedAt   *time.Time
    DeletedAt   *time.Time
}

type CreateDomainMessage struct {
    Name     string
    Metadata Metadata
}

type UpdateDomainMessage struct {
    GUID          string
    MetadataPatch MetadataPatch
}

type ListDomainsMessage struct {
    Names []string
}

func (r *DomainRepo) GetDomain(ctx context.Context, authInfo authorization.Info, domainGUID string) (DomainRecord, error) {
    ns, err := r.namespaceRetriever.NamespaceFor(ctx, domainGUID, DomainResourceType)
    if err != nil {
        return DomainRecord{}, err
    }

    userClient, err := r.userClientFactory.BuildClient(authInfo)
    if err != nil {
        return DomainRecord{}, fmt.Errorf("get-domain failed to create user client: %w", err)
    }

    domain := &korifiv1alpha1.CFDomain{}
    err = userClient.Get(ctx, client.ObjectKey{Namespace: ns, Name: domainGUID}, domain)
    if err != nil {
        return DomainRecord{}, apierrors.NewForbiddenError(err, DomainResourceType)
    }

    return cfDomainToDomainRecord(domain), nil
}

func (r *DomainRepo) CreateDomain(ctx context.Context, authInfo authorization.Info, message CreateDomainMessage) (DomainRecord, error) {
    userClient, err := r.userClientFactory.BuildClient(authInfo)
    if err != nil {
        return DomainRecord{}, fmt.Errorf("create-domain failed to create user client: %w", err)
    }

    cfDomain := &korifiv1alpha1.CFDomain{
        ObjectMeta: metav1.ObjectMeta{
            Name:        uuid.NewString(),
            Namespace:   r.rootNamespace,
            Labels:      message.Metadata.Labels,
            Annotations: message.Metadata.Annotations,
        },
        Spec: korifiv1alpha1.CFDomainSpec{
            Name: message.Name,
        },
    }

    err = userClient.Create(ctx, cfDomain)
    if err != nil {
        return DomainRecord{}, fmt.Errorf("create-domain failed: %w", apierrors.FromK8sError(err, DomainResourceType))
    }

    return cfDomainToDomainRecord(cfDomain), nil
}

func (r *DomainRepo) UpdateDomain(ctx context.Context, authInfo authorization.Info, message UpdateDomainMessage) (DomainRecord, error) {
    userClient, err := r.userClientFactory.BuildClient(authInfo)
    if err != nil {
        return DomainRecord{}, fmt.Errorf("create-domain failed to create user client: %w", err)
    }

    domain := &korifiv1alpha1.CFDomain{
        ObjectMeta: metav1.ObjectMeta{
            Name:      message.GUID,
            Namespace: r.rootNamespace,
        },
    }

    err = userClient.Get(ctx, client.ObjectKeyFromObject(domain), domain)
    if err != nil {
        return DomainRecord{}, fmt.Errorf("update-domain failed: %w", apierrors.FromK8sError(err, DomainResourceType))
    }

    err = k8s.PatchResource(ctx, userClient, domain, func() {
        message.MetadataPatch.Apply(domain)
    })
    if err != nil {
        return DomainRecord{}, fmt.Errorf("failed to patch domain metadata: %w", apierrors.FromK8sError(err, DomainResourceType))
    }

    return cfDomainToDomainRecord(domain), nil
}

func (r *DomainRepo) ListDomains(ctx context.Context, authInfo authorization.Info, message ListDomainsMessage) ([]DomainRecord, error) {
    userClient, err := r.userClientFactory.BuildClient(authInfo)
    if err != nil {
        return []DomainRecord{}, fmt.Errorf("list-domain failed to create user client: %w", err)
    }

    cfdomainList := &korifiv1alpha1.CFDomainList{}
    err = userClient.List(ctx, cfdomainList, client.InNamespace(r.rootNamespace))
    if err != nil {
        if k8serrors.IsForbidden(err) {
            return []DomainRecord{}, nil
        }
        // untested
        return []DomainRecord{}, fmt.Errorf("failed to list domains in namespace %s: %w", r.rootNamespace, apierrors.FromK8sError(err, DomainResourceType))
    }

    filtered := Filter(cfdomainList.Items, SetPredicate(message.Names, func(s korifiv1alpha1.CFDomain) string { return s.Spec.Name }))

    sort.Slice(filtered, func(i, j int) bool {
        return filtered[i].CreationTimestamp.Before(&filtered[j].CreationTimestamp)
    })

    return returnDomainList(filtered), nil
}

func (r *DomainRepo) GetDomainByName(ctx context.Context, authInfo authorization.Info, domainName string) (DomainRecord, error) {
    domainRecords, err := r.ListDomains(ctx, authInfo, ListDomainsMessage{
        Names: []string{domainName},
    })
    if err != nil {
        return DomainRecord{}, err
    }

    if len(domainRecords) == 0 {
        return DomainRecord{}, apierrors.NewNotFoundError(fmt.Errorf("domain %q not found", domainName), DomainResourceType)
    }

    return domainRecords[0], nil
}

func (r *DomainRepo) DeleteDomain(ctx context.Context, authInfo authorization.Info, domainGUID string) error {
    userClient, err := r.userClientFactory.BuildClient(authInfo)
    if err != nil {
        return fmt.Errorf("delete-domain failed to create user client: %w", err)
    }

    cfDomain := &korifiv1alpha1.CFDomain{
        ObjectMeta: metav1.ObjectMeta{
            Namespace: r.rootNamespace,
            Name:      domainGUID,
        },
    }

    err = userClient.Delete(ctx, cfDomain)
    if err != nil {
        return apierrors.FromK8sError(err, DomainResourceType)
    }

    return nil
}

func (r *DomainRepo) GetDeletedAt(ctx context.Context, authInfo authorization.Info, domainGUID string) (*time.Time, error) {
    domain, err := r.GetDomain(ctx, authInfo, domainGUID)
    return domain.DeletedAt, err
}

func returnDomainList(domainList []korifiv1alpha1.CFDomain) []DomainRecord {
    domainRecords := make([]DomainRecord, 0, len(domainList))

    for i := range domainList {
        domainRecords = append(domainRecords, cfDomainToDomainRecord(&domainList[i]))
    }
    return domainRecords
}

func cfDomainToDomainRecord(cfDomain *korifiv1alpha1.CFDomain) DomainRecord {
    return DomainRecord{
        Name:        cfDomain.Spec.Name,
        GUID:        cfDomain.Name,
        Namespace:   cfDomain.Namespace,
        CreatedAt:   cfDomain.CreationTimestamp.Time,
        UpdatedAt:   getLastUpdatedTime(cfDomain),
        DeletedAt:   golangTime(cfDomain.DeletionTimestamp),
        Labels:      cfDomain.Labels,
        Annotations: cfDomain.Annotations,
    }
}