package keaapi import ( "context" "errors" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" v1alpha1 "git.unkin.net/unkin/kea-operator/api/v1alpha1" ) // ErrNotFound is the sentinel the HTTP layer maps to 404. var ErrNotFound = errors.New("not found") // Store is the persistence seam. The HTTP handlers depend only on this; the // k8s implementation below CRUDs CRs, but it could be swapped for any backend. type Store interface { UpsertSubnet(ctx context.Context, a SubnetAPI) (SubnetAPI, error) GetSubnet(ctx context.Context, name string) (SubnetAPI, error) ListSubnets(ctx context.Context) ([]SubnetAPI, error) DeleteSubnet(ctx context.Context, name string) error UpsertClass(ctx context.Context, a ClientClassAPI) (ClientClassAPI, error) GetClass(ctx context.Context, name string) (ClientClassAPI, error) ListClasses(ctx context.Context) ([]ClientClassAPI, error) DeleteClass(ctx context.Context, name string) error } // K8sStore backs the API with KeaSubnet / KeaClientClass CRs in a namespace. type K8sStore struct { Client client.Client Namespace string } func (s *K8sStore) key(name string) types.NamespacedName { return types.NamespacedName{Namespace: s.Namespace, Name: name} } func (s *K8sStore) UpsertSubnet(ctx context.Context, a SubnetAPI) (SubnetAPI, error) { obj := &v1alpha1.KeaSubnet{ObjectMeta: metav1.ObjectMeta{Name: a.Name, Namespace: s.Namespace}} if _, err := controllerutil.CreateOrUpdate(ctx, s.Client, obj, func() error { obj.Spec = subnetSpecFromAPI(a) return nil }); err != nil { return SubnetAPI{}, err } return subnetToAPI(obj), nil } func (s *K8sStore) GetSubnet(ctx context.Context, name string) (SubnetAPI, error) { var obj v1alpha1.KeaSubnet if err := s.Client.Get(ctx, s.key(name), &obj); err != nil { return SubnetAPI{}, mapGet(err) } return subnetToAPI(&obj), nil } func (s *K8sStore) ListSubnets(ctx context.Context) ([]SubnetAPI, error) { var list v1alpha1.KeaSubnetList if err := s.Client.List(ctx, &list, client.InNamespace(s.Namespace)); err != nil { return nil, err } out := make([]SubnetAPI, 0, len(list.Items)) for i := range list.Items { out = append(out, subnetToAPI(&list.Items[i])) } return out, nil } func (s *K8sStore) DeleteSubnet(ctx context.Context, name string) error { obj := &v1alpha1.KeaSubnet{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: s.Namespace}} return mapGet(s.Client.Delete(ctx, obj)) } func (s *K8sStore) UpsertClass(ctx context.Context, a ClientClassAPI) (ClientClassAPI, error) { obj := &v1alpha1.KeaClientClass{ObjectMeta: metav1.ObjectMeta{Name: a.Name, Namespace: s.Namespace}} if _, err := controllerutil.CreateOrUpdate(ctx, s.Client, obj, func() error { obj.Spec = classSpecFromAPI(a) return nil }); err != nil { return ClientClassAPI{}, err } return classToAPI(obj), nil } func (s *K8sStore) GetClass(ctx context.Context, name string) (ClientClassAPI, error) { var obj v1alpha1.KeaClientClass if err := s.Client.Get(ctx, s.key(name), &obj); err != nil { return ClientClassAPI{}, mapGet(err) } return classToAPI(&obj), nil } func (s *K8sStore) ListClasses(ctx context.Context) ([]ClientClassAPI, error) { var list v1alpha1.KeaClientClassList if err := s.Client.List(ctx, &list, client.InNamespace(s.Namespace)); err != nil { return nil, err } out := make([]ClientClassAPI, 0, len(list.Items)) for i := range list.Items { out = append(out, classToAPI(&list.Items[i])) } return out, nil } func (s *K8sStore) DeleteClass(ctx context.Context, name string) error { obj := &v1alpha1.KeaClientClass{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: s.Namespace}} return mapGet(s.Client.Delete(ctx, obj)) } func mapGet(err error) error { if apierrors.IsNotFound(err) { return ErrNotFound } return err } var _ Store = (*K8sStore)(nil)