From 39878cc02aea0f2b803791cf629adf43bf9939ba Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Thu, 16 Oct 2025 15:15:33 +0200 Subject: [PATCH 1/2] Toggle transitive entities for machine get and list --- go.mod | 2 +- go.sum | 4 +- pkg/repository/filesystemlayout.go | 4 +- pkg/repository/image.go | 4 +- pkg/repository/ip.go | 4 +- pkg/repository/machine.go | 348 ++++++++++-------- pkg/repository/network.go | 4 +- pkg/repository/partition.go | 4 +- pkg/repository/project-member.go | 4 +- pkg/repository/project.go | 4 +- pkg/repository/repository.go | 11 +- pkg/repository/size.go | 4 +- pkg/repository/store.go | 18 +- pkg/repository/switch.go | 4 +- pkg/repository/tenant-member.go | 4 +- pkg/repository/tenant.go | 4 +- pkg/service/machine/admin/machine-service.go | 2 +- .../machine/admin/machine-service_test.go | 6 +- pkg/service/machine/machine-service.go | 4 +- pkg/service/machine/machine-service_test.go | 37 +- 20 files changed, 273 insertions(+), 203 deletions(-) diff --git a/go.mod b/go.mod index 94a9841b..3bcd99c0 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ require ( github.com/klauspost/connect-compress/v2 v2.0.0 github.com/lestrrat-go/jwx/v2 v2.1.6 github.com/markbates/goth v1.82.0 - github.com/metal-stack/api v0.0.31 + github.com/metal-stack/api v0.0.32-0.20251016121234-e738c13c2fc3 github.com/metal-stack/go-ipam v1.14.13 github.com/metal-stack/masterdata-api v0.13.0 github.com/metal-stack/metal-lib v0.23.5 diff --git a/go.sum b/go.sum index 8c9375ec..1d8c3bb8 100644 --- a/go.sum +++ b/go.sum @@ -309,8 +309,8 @@ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mdelapenya/tlscert v0.2.0 h1:7H81W6Z/4weDvZBNOfQte5GpIMo0lGYEeWbkGp5LJHI= github.com/mdelapenya/tlscert v0.2.0/go.mod h1:O4njj3ELLnJjGdkN7M/vIVCpZ+Cf0L6muqOG4tLSl8o= -github.com/metal-stack/api v0.0.31 h1:ivgaqYuWFrGO4IZS/9ViwkuV1AzytZlji6zgDnANo+s= -github.com/metal-stack/api v0.0.31/go.mod h1:LcJkrJPJRdLPFaeCiWy5tL0dJXU74kq+vFe5uc/Cm9U= +github.com/metal-stack/api v0.0.32-0.20251016121234-e738c13c2fc3 h1:59fexk9TiEAiThNpEGRrLkrH5cMDzGhO/W1Gq4HbgRs= +github.com/metal-stack/api v0.0.32-0.20251016121234-e738c13c2fc3/go.mod h1:LcJkrJPJRdLPFaeCiWy5tL0dJXU74kq+vFe5uc/Cm9U= github.com/metal-stack/go-ipam v1.14.13 h1:/W5/MDBX5EU18xNDjlBvV6JjQ1Ot12dO2WxLvV6S8vc= github.com/metal-stack/go-ipam v1.14.13/go.mod h1:eif3UGUFP7CWJdrgLIOjhVM3G2K19GN8lhCgPVfvLDs= github.com/metal-stack/masterdata-api v0.13.0 h1:1AxnsiWiTMstjMsphZ0wMFT7aW3QQhAMumXnQ81PtHk= diff --git a/pkg/repository/filesystemlayout.go b/pkg/repository/filesystemlayout.go index 79163a5e..15e790e6 100644 --- a/pkg/repository/filesystemlayout.go +++ b/pkg/repository/filesystemlayout.go @@ -214,7 +214,7 @@ func (r *filesystemLayoutRepository) list(ctx context.Context, rq *apiv2.Filesys return fsls, nil } -func (r *filesystemLayoutRepository) convertToInternal(ctx context.Context, f *apiv2.FilesystemLayout) (*metal.FilesystemLayout, error) { +func (r *filesystemLayoutRepository) convertToInternal(ctx context.Context, f *apiv2.FilesystemLayout, opts ...Option) (*metal.FilesystemLayout, error) { var ( fss = []metal.Filesystem{} ds = []metal.Disk{} @@ -350,7 +350,7 @@ func (r *filesystemLayoutRepository) convertToInternal(ctx context.Context, f *a return fl, nil } -func (r *filesystemLayoutRepository) convertToProto(ctx context.Context, in *metal.FilesystemLayout) (*apiv2.FilesystemLayout, error) { +func (r *filesystemLayoutRepository) convertToProto(ctx context.Context, in *metal.FilesystemLayout, opts ...Option) (*apiv2.FilesystemLayout, error) { var filesystems []*apiv2.Filesystem for _, fs := range in.Filesystems { f, err := enum.GetEnum[apiv2.Format](string(fs.Format)) diff --git a/pkg/repository/image.go b/pkg/repository/image.go index 8f72d531..8efe63b3 100644 --- a/pkg/repository/image.go +++ b/pkg/repository/image.go @@ -195,7 +195,7 @@ func (r *imageRepository) list(ctx context.Context, rq *apiv2.ImageQuery) ([]*me return r.SortImages(images), nil } -func (r *imageRepository) convertToInternal(ctx context.Context, msg *apiv2.Image) (*metal.Image, error) { +func (r *imageRepository) convertToInternal(ctx context.Context, msg *apiv2.Image, opts ...Option) (*metal.Image, error) { features, err := metal.ImageFeaturesFrom(msg.Features) if err != nil { return nil, err @@ -228,7 +228,7 @@ func (r *imageRepository) convertToInternal(ctx context.Context, msg *apiv2.Imag } return image, nil } -func (r *imageRepository) convertToProto(ctx context.Context, in *metal.Image) (*apiv2.Image, error) { +func (r *imageRepository) convertToProto(ctx context.Context, in *metal.Image, opts ...Option) (*apiv2.Image, error) { var features []apiv2.ImageFeature for feature := range in.Features { switch feature { diff --git a/pkg/repository/ip.go b/pkg/repository/ip.go index c862ea5c..465b0cf1 100644 --- a/pkg/repository/ip.go +++ b/pkg/repository/ip.go @@ -279,11 +279,11 @@ func (r *ipRepository) allocateRandomIP(ctx context.Context, parent *metal.Netwo return "", "", errorutil.InvalidArgument("cannot allocate random free ip in ipam, no ips left in network:%s af:%s parent afs:%#v", parent.ID, addressfamily, parent.Prefixes.AddressFamilies()) } -func (r *ipRepository) convertToInternal(ctx context.Context, ip *apiv2.IP) (*metal.IP, error) { +func (r *ipRepository) convertToInternal(ctx context.Context, ip *apiv2.IP, opts ...Option) (*metal.IP, error) { panic("unimplemented") } -func (r *ipRepository) convertToProto(ctx context.Context, metalIP *metal.IP) (*apiv2.IP, error) { +func (r *ipRepository) convertToProto(ctx context.Context, metalIP *metal.IP, opts ...Option) (*apiv2.IP, error) { t := apiv2.IPType_IP_TYPE_UNSPECIFIED switch metalIP.Type { case metal.Ephemeral: diff --git a/pkg/repository/machine.go b/pkg/repository/machine.go index 6d1d2285..8946640a 100644 --- a/pkg/repository/machine.go +++ b/pkg/repository/machine.go @@ -111,26 +111,30 @@ func (r *machineRepository) list(ctx context.Context, rq *apiv2.MachineQuery) ([ return machines, nil } -func (r *machineRepository) convertToInternal(ctx context.Context, machine *apiv2.Machine) (*metal.Machine, error) { +func (r *machineRepository) convertToInternal(ctx context.Context, machine *apiv2.Machine, opts ...Option) (*metal.Machine, error) { panic("unimplemented") } -func (r *machineRepository) convertToProto(ctx context.Context, m *metal.Machine) (*apiv2.Machine, error) { +func (r *machineRepository) convertToProto(ctx context.Context, m *metal.Machine, opts ...Option) (*apiv2.Machine, error) { var ( - labels *apiv2.Labels - allocationLabels *apiv2.Labels - bios *apiv2.MachineBios - allocation *apiv2.MachineAllocation - condition *apiv2.MachineCondition - status *apiv2.MachineStatus - size *apiv2.Size - vpn *apiv2.MachineVPN - dnsServers []*apiv2.DNSServer - ntpServers []*apiv2.NTPServer - firewallRules *apiv2.FirewallRules - machineNetworks []*apiv2.MachineNetwork - filesystemLayout *apiv2.FilesystemLayout + labels *apiv2.Labels + bios *apiv2.MachineBios + allocation *apiv2.MachineAllocation + condition *apiv2.MachineCondition + status *apiv2.MachineStatus + size *apiv2.Size + partition *apiv2.Partition + err error + withTransitive bool ) + for _, opt := range opts { + switch o := opt.(type) { + case *convertOptWithTransitive: + withTransitive = o.withTransitive + default: + errorutil.Internal("unsupported test option: %T", o) + } + } if len(m.Tags) > 0 { labels = &apiv2.Labels{ @@ -138,13 +142,25 @@ func (r *machineRepository) convertToProto(ctx context.Context, m *metal.Machine } } - partition, err := r.s.Partition().Get(ctx, m.PartitionID) - if err != nil { - return nil, err - } - size, err = r.s.Size().Get(ctx, m.SizeID) - if err != nil { - return nil, err + if withTransitive { + partition, err = r.s.Partition().Get(ctx, m.PartitionID) + if err != nil { + return nil, err + } + size, err = r.s.Size().Get(ctx, m.SizeID) + if err != nil { + return nil, err + } + allocation, err = r.convertAllocationToProto(ctx, m) + if err != nil { + return nil, err + } + } else { + partition = &apiv2.Partition{Id: m.PartitionID} + size = &apiv2.Size{Id: m.SizeID} + if m.Allocation != nil { + allocation = &apiv2.MachineAllocation{Uuid: m.Allocation.UUID} + } } var ( @@ -208,143 +224,6 @@ func (r *machineRepository) convertToProto(ctx context.Context, m *metal.Machine Date: m.BIOS.Date, } - if m.Allocation != nil { - alloc := m.Allocation - - image, err := r.s.Image().Get(ctx, alloc.ImageID) - if err != nil { - return nil, err - } - - if alloc.FilesystemLayout != nil { - filesystemLayout, err = r.s.FilesystemLayout().Get(ctx, alloc.FilesystemLayout.ID) - if err != nil { - return nil, err - } - } - - if alloc.VPN != nil { - vpn = &apiv2.MachineVPN{ - ControlPlaneAddress: alloc.VPN.ControlPlaneAddress, - AuthKey: alloc.VPN.AuthKey, - Connected: alloc.VPN.Connected, - } - } - for _, dns := range alloc.DNSServers { - dnsServers = append(dnsServers, &apiv2.DNSServer{ - Ip: dns.IP, - }) - } - for _, ntp := range alloc.NTPServers { - ntpServers = append(ntpServers, &apiv2.NTPServer{ - Address: ntp.Address, - }) - } - if alloc.FirewallRules != nil { - var ( - egress []*apiv2.FirewallEgressRule - ingress []*apiv2.FirewallIngressRule - ) - for _, e := range alloc.FirewallRules.Egress { - protocol, err := enum.GetEnum[apiv2.IPProtocol](strings.ToLower(string(e.Protocol))) - if err != nil { - return nil, err - } - var ports []uint32 - for _, p := range e.Ports { - ports = append(ports, uint32(p)) - } - egress = append(egress, &apiv2.FirewallEgressRule{ - Protocol: protocol, - Ports: ports, - To: e.To, - Comment: e.Comment, - }) - } - for _, i := range alloc.FirewallRules.Ingress { - protocol, err := enum.GetEnum[apiv2.IPProtocol](strings.ToLower(string(i.Protocol))) - if err != nil { - return nil, err - } - var ports []uint32 - for _, p := range i.Ports { - ports = append(ports, uint32(p)) - } - ingress = append(ingress, &apiv2.FirewallIngressRule{ - Protocol: protocol, - Ports: ports, - To: i.To, - From: i.From, - Comment: i.Comment, - }) - } - firewallRules = &apiv2.FirewallRules{ - Egress: egress, - Ingress: ingress, - } - } - - for _, nw := range alloc.MachineNetworks { - metalNetwork, err := r.s.ds.Network().Get(ctx, nw.NetworkID) - if err != nil { - return nil, err - } - networkType, err := metal.FromNetworkType(*metalNetwork.NetworkType) - if err != nil { - return nil, err - } - natType, err := metal.FromNATType(*metalNetwork.NATType) - if err != nil { - return nil, err - } - - machineNetworks = append(machineNetworks, &apiv2.MachineNetwork{ - Network: nw.NetworkID, - Ips: nw.IPs, - Prefixes: nw.Prefixes, // TODO would be better if we fetch from metalNetwork - DestinationPrefixes: nw.DestinationPrefixes, // TODO would be better if we fetch from metalNetwork - NetworkType: networkType, - NatType: natType, - Vrf: uint64(nw.Vrf), - Asn: nw.ASN, - }) - } - - allocationType, err := enum.GetEnum[apiv2.MachineAllocationType](strings.ToLower(string(alloc.Role))) - if err != nil { - return nil, err - } - - if m.Allocation.Labels != nil { - allocationLabels = &apiv2.Labels{ - Labels: m.Allocation.Labels, - } - } - - allocation = &apiv2.MachineAllocation{ - Uuid: alloc.UUID, - Meta: &apiv2.Meta{ - CreatedAt: timestamppb.New(alloc.Created), - Labels: allocationLabels, - }, - Name: alloc.Name, - Description: alloc.Description, - CreatedBy: alloc.Creator, - Project: alloc.Project, - Image: image, - FilesystemLayout: filesystemLayout, - Networks: machineNetworks, - Hostname: alloc.Hostname, - SshPublicKeys: alloc.SSHPubKeys, - Userdata: alloc.UserData, - AllocationType: allocationType, - FirewallRules: firewallRules, - DnsServer: dnsServers, - NtpServer: ntpServers, - Vpn: vpn, - } - } - stateString, err := enum.GetEnum[apiv2.MachineState](strings.ToLower(string(m.State.Value))) if err != nil { return nil, err @@ -436,6 +315,157 @@ func (r *machineRepository) convertToProto(ctx context.Context, m *metal.Machine return result, nil } +func (r *machineRepository) convertAllocationToProto(ctx context.Context, m *metal.Machine) (*apiv2.MachineAllocation, error) { + if m.Allocation == nil { + return nil, nil + } + + var ( + allocationLabels *apiv2.Labels + vpn *apiv2.MachineVPN + dnsServers []*apiv2.DNSServer + ntpServers []*apiv2.NTPServer + firewallRules *apiv2.FirewallRules + machineNetworks []*apiv2.MachineNetwork + filesystemLayout *apiv2.FilesystemLayout + ) + + alloc := m.Allocation + + image, err := r.s.Image().Get(ctx, alloc.ImageID) + if err != nil { + return nil, err + } + + if alloc.FilesystemLayout != nil { + filesystemLayout, err = r.s.FilesystemLayout().Get(ctx, alloc.FilesystemLayout.ID) + if err != nil { + return nil, err + } + } + + if alloc.VPN != nil { + vpn = &apiv2.MachineVPN{ + ControlPlaneAddress: alloc.VPN.ControlPlaneAddress, + AuthKey: alloc.VPN.AuthKey, + Connected: alloc.VPN.Connected, + } + } + for _, dns := range alloc.DNSServers { + dnsServers = append(dnsServers, &apiv2.DNSServer{ + Ip: dns.IP, + }) + } + for _, ntp := range alloc.NTPServers { + ntpServers = append(ntpServers, &apiv2.NTPServer{ + Address: ntp.Address, + }) + } + if alloc.FirewallRules != nil { + var ( + egress []*apiv2.FirewallEgressRule + ingress []*apiv2.FirewallIngressRule + ) + for _, e := range alloc.FirewallRules.Egress { + protocol, err := enum.GetEnum[apiv2.IPProtocol](strings.ToLower(string(e.Protocol))) + if err != nil { + return nil, err + } + var ports []uint32 + for _, p := range e.Ports { + ports = append(ports, uint32(p)) + } + egress = append(egress, &apiv2.FirewallEgressRule{ + Protocol: protocol, + Ports: ports, + To: e.To, + Comment: e.Comment, + }) + } + for _, i := range alloc.FirewallRules.Ingress { + protocol, err := enum.GetEnum[apiv2.IPProtocol](strings.ToLower(string(i.Protocol))) + if err != nil { + return nil, err + } + var ports []uint32 + for _, p := range i.Ports { + ports = append(ports, uint32(p)) + } + ingress = append(ingress, &apiv2.FirewallIngressRule{ + Protocol: protocol, + Ports: ports, + To: i.To, + From: i.From, + Comment: i.Comment, + }) + } + firewallRules = &apiv2.FirewallRules{ + Egress: egress, + Ingress: ingress, + } + } + + for _, nw := range alloc.MachineNetworks { + metalNetwork, err := r.s.ds.Network().Get(ctx, nw.NetworkID) + if err != nil { + return nil, err + } + networkType, err := metal.FromNetworkType(*metalNetwork.NetworkType) + if err != nil { + return nil, err + } + natType, err := metal.FromNATType(*metalNetwork.NATType) + if err != nil { + return nil, err + } + + machineNetworks = append(machineNetworks, &apiv2.MachineNetwork{ + Network: nw.NetworkID, + Ips: nw.IPs, + Prefixes: nw.Prefixes, // TODO would be better if we fetch from metalNetwork + DestinationPrefixes: nw.DestinationPrefixes, // TODO would be better if we fetch from metalNetwork + NetworkType: networkType, + NatType: natType, + Vrf: uint64(nw.Vrf), + Asn: nw.ASN, + }) + } + + allocationType, err := enum.GetEnum[apiv2.MachineAllocationType](strings.ToLower(string(alloc.Role))) + if err != nil { + return nil, err + } + + if m.Allocation.Labels != nil { + allocationLabels = &apiv2.Labels{ + Labels: m.Allocation.Labels, + } + } + + return &apiv2.MachineAllocation{ + Uuid: alloc.UUID, + Meta: &apiv2.Meta{ + CreatedAt: timestamppb.New(alloc.Created), + Labels: allocationLabels, + }, + Name: alloc.Name, + Description: alloc.Description, + CreatedBy: alloc.Creator, + Project: alloc.Project, + Image: image, + FilesystemLayout: filesystemLayout, + Networks: machineNetworks, + Hostname: alloc.Hostname, + SshPublicKeys: alloc.SSHPubKeys, + Userdata: alloc.UserData, + AllocationType: allocationType, + FirewallRules: firewallRules, + DnsServer: dnsServers, + NtpServer: ntpServers, + Vpn: vpn, + }, nil +} + //--------------------------------------------------------------- // Write a function HandleXXXTask to handle the input task. // Note that it satisfies the asynq.HandlerFunc interface. diff --git a/pkg/repository/network.go b/pkg/repository/network.go index d97f12ed..06412658 100644 --- a/pkg/repository/network.go +++ b/pkg/repository/network.go @@ -389,11 +389,11 @@ func (r *networkRepository) list(ctx context.Context, query *apiv2.NetworkQuery) return nws, nil } -func (r *networkRepository) convertToInternal(ctx context.Context, msg *apiv2.Network) (*metal.Network, error) { +func (r *networkRepository) convertToInternal(ctx context.Context, msg *apiv2.Network, opts ...Option) (*metal.Network, error) { panic("unimplemented") } -func (r *networkRepository) convertToProto(ctx context.Context, e *metal.Network) (*apiv2.Network, error) { +func (r *networkRepository) convertToProto(ctx context.Context, e *metal.Network, opts ...Option) (*apiv2.Network, error) { var ( consumption *apiv2.NetworkConsumption labels *apiv2.Labels diff --git a/pkg/repository/partition.go b/pkg/repository/partition.go index 2dc581f8..3415c72b 100644 --- a/pkg/repository/partition.go +++ b/pkg/repository/partition.go @@ -147,7 +147,7 @@ func (p *partitionRepository) matchScope(e *metal.Partition) bool { } // ConvertToInternal implements Partition. -func (p *partitionRepository) convertToInternal(ctx context.Context, msg *apiv2.Partition) (*metal.Partition, error) { +func (p *partitionRepository) convertToInternal(ctx context.Context, msg *apiv2.Partition, opts ...Option) (*metal.Partition, error) { mgm := "" if len(msg.MgmtServiceAddresses) > 0 { // FIXME migrate metal model to slice as well @@ -207,7 +207,7 @@ func (p *partitionRepository) convertToInternal(ctx context.Context, msg *apiv2. } // ConvertToProto implements Partition. -func (p *partitionRepository) convertToProto(ctx context.Context, e *metal.Partition) (*apiv2.Partition, error) { +func (p *partitionRepository) convertToProto(ctx context.Context, e *metal.Partition, opts ...Option) (*apiv2.Partition, error) { var ( dnsServers []*apiv2.DNSServer ntpServers []*apiv2.NTPServer diff --git a/pkg/repository/project-member.go b/pkg/repository/project-member.go index f7523e94..7a3fdafc 100644 --- a/pkg/repository/project-member.go +++ b/pkg/repository/project-member.go @@ -35,7 +35,7 @@ type ( func (t *projectMemberEntity) SetChanged(time time.Time) {} -func (t *projectMemberRepository) convertToInternal(ctx context.Context, msg *apiv2.ProjectMember) (*projectMemberEntity, error) { +func (t *projectMemberRepository) convertToInternal(ctx context.Context, msg *apiv2.ProjectMember, opts ...Option) (*projectMemberEntity, error) { return &projectMemberEntity{ ProjectMember: &mdcv1.ProjectMember{ Meta: &mdcv1.Meta{ @@ -48,7 +48,7 @@ func (t *projectMemberRepository) convertToInternal(ctx context.Context, msg *ap }, nil } -func (t *projectMemberRepository) convertToProto(ctx context.Context, e *projectMemberEntity) (*apiv2.ProjectMember, error) { +func (t *projectMemberRepository) convertToProto(ctx context.Context, e *projectMemberEntity, opts ...Option) (*apiv2.ProjectMember, error) { if e.Meta.Annotations == nil { e.Meta.Annotations = map[string]string{} } diff --git a/pkg/repository/project.go b/pkg/repository/project.go index c1b18b2b..1c666b43 100644 --- a/pkg/repository/project.go +++ b/pkg/repository/project.go @@ -188,7 +188,7 @@ func (r *projectRepository) list(ctx context.Context, query *apiv2.ProjectServic return ps, nil } -func (r *projectRepository) convertToInternal(ctx context.Context, p *apiv2.Project) (*projectEntity, error) { +func (r *projectRepository) convertToInternal(ctx context.Context, p *apiv2.Project, opts ...Option) (*projectEntity, error) { var labels []string if p.Meta != nil && p.Meta.Labels != nil && len(p.Meta.Labels.Labels) > 0 { labels = tag.TagMap(p.Meta.Labels.Labels).Slice() @@ -215,7 +215,7 @@ func (r *projectRepository) convertToInternal(ctx context.Context, p *apiv2.Proj }, nil } -func (r *projectRepository) convertToProto(ctx context.Context, p *projectEntity) (*apiv2.Project, error) { +func (r *projectRepository) convertToProto(ctx context.Context, p *projectEntity, opts ...Option) (*apiv2.Project, error) { if p.Meta == nil { return nil, errorutil.Internal("project meta is nil") } diff --git a/pkg/repository/repository.go b/pkg/repository/repository.go index 142feb3b..0183c211 100644 --- a/pkg/repository/repository.go +++ b/pkg/repository/repository.go @@ -10,12 +10,12 @@ import ( type ( Repository[R Repo, M Message, C CreateMessage, U UpdateMessage, Q Query] interface { - Get(ctx context.Context, id string) (M, error) + Get(ctx context.Context, id string, opts ...Option) (M, error) Create(ctx context.Context, c C) (M, error) Update(ctx context.Context, id string, u U) (M, error) Delete(ctx context.Context, id string) (M, error) Find(ctx context.Context, query Q) (M, error) - List(ctx context.Context, query Q) ([]M, error) + List(ctx context.Context, query Q, opts ...Option) ([]M, error) AdditionalMethods() R } @@ -34,12 +34,15 @@ type ( find(ctx context.Context, query Q) (E, error) list(ctx context.Context, query Q) ([]E, error) - convertToInternal(ctx context.Context, msg M) (E, error) - convertToProto(ctx context.Context, e E) (M, error) + convertToInternal(ctx context.Context, msg M, opts ...Option) (E, error) + convertToProto(ctx context.Context, e E, opts ...Option) (M, error) matchScope(e E) bool } + // Option can be set to modify the behavior of funcs, e.g. WithoutReferencedEntities. + Option any + // Repo is the typed repository in order to expose public functions on the repository to the consumers. Repo any // Entity is the internal representation of an api resource, which is stored in the backend. diff --git a/pkg/repository/size.go b/pkg/repository/size.go index b5349b41..c9b955c5 100644 --- a/pkg/repository/size.go +++ b/pkg/repository/size.go @@ -109,7 +109,7 @@ func (r *sizeRepository) list(ctx context.Context, rq *apiv2.SizeQuery) ([]*meta return sizes, nil } -func (r *sizeRepository) convertToInternal(ctx context.Context, e *apiv2.Size) (*metal.Size, error) { +func (r *sizeRepository) convertToInternal(ctx context.Context, e *apiv2.Size, opts ...Option) (*metal.Size, error) { if e == nil { return nil, nil } @@ -140,7 +140,7 @@ func (r *sizeRepository) convertToInternal(ctx context.Context, e *apiv2.Size) ( return size, nil } -func (r *sizeRepository) convertToProto(ctx context.Context, e *metal.Size) (*apiv2.Size, error) { +func (r *sizeRepository) convertToProto(ctx context.Context, e *metal.Size, opts ...Option) (*apiv2.Size, error) { if e == nil { return nil, nil } diff --git a/pkg/repository/store.go b/pkg/repository/store.go index fcce7d8a..d88f8f71 100644 --- a/pkg/repository/store.go +++ b/pkg/repository/store.go @@ -257,7 +257,7 @@ func (s *store[R, E, M, C, U, Q]) Find(ctx context.Context, query Q) (M, error) return converted, nil } -func (s *store[R, E, M, C, U, Q]) Get(ctx context.Context, id string) (M, error) { +func (s *store[R, E, M, C, U, Q]) Get(ctx context.Context, id string, opts ...Option) (M, error) { var zero M e, err := s.get(ctx, id) @@ -270,7 +270,7 @@ func (s *store[R, E, M, C, U, Q]) Get(ctx context.Context, id string) (M, error) return zero, errorutil.NotFound("%T with id %q not found", e, id) } - converted, err := s.convertToProto(ctx, e) + converted, err := s.convertToProto(ctx, e, opts...) if err != nil { return zero, err } @@ -278,7 +278,7 @@ func (s *store[R, E, M, C, U, Q]) Get(ctx context.Context, id string) (M, error) return converted, nil } -func (s *store[R, E, M, C, U, Q]) List(ctx context.Context, query Q) ([]M, error) { +func (s *store[R, E, M, C, U, Q]) List(ctx context.Context, query Q, opts ...Option) ([]M, error) { es, err := s.list(ctx, query) if err != nil { return nil, err @@ -286,7 +286,7 @@ func (s *store[R, E, M, C, U, Q]) List(ctx context.Context, query Q) ([]M, error var res []M for _, e := range es { - converted, err := s.convertToProto(ctx, e) + converted, err := s.convertToProto(ctx, e, opts...) if err != nil { return nil, err } @@ -354,3 +354,13 @@ func setUpdateMeta(u UpdateMessage, e Entity) error { return nil } + +type convertOptWithTransitive struct { + withTransitive bool +} + +func WithTransitive(withTransitive bool) *convertOptWithTransitive { + return &convertOptWithTransitive{ + withTransitive: withTransitive, + } +} diff --git a/pkg/repository/switch.go b/pkg/repository/switch.go index 63271c78..b0a29b4e 100644 --- a/pkg/repository/switch.go +++ b/pkg/repository/switch.go @@ -182,7 +182,7 @@ func (r *switchRepository) list(ctx context.Context, query *apiv2.SwitchQuery) ( return switches, err } -func (r *switchRepository) convertToInternal(ctx context.Context, sw *apiv2.Switch) (*metal.Switch, error) { +func (r *switchRepository) convertToInternal(ctx context.Context, sw *apiv2.Switch, opts ...Option) (*metal.Switch, error) { if sw == nil { return nil, nil } @@ -228,7 +228,7 @@ func (r *switchRepository) convertToInternal(ctx context.Context, sw *apiv2.Swit }, nil } -func (r *switchRepository) convertToProto(ctx context.Context, sw *metal.Switch) (*apiv2.Switch, error) { +func (r *switchRepository) convertToProto(ctx context.Context, sw *metal.Switch, opts ...Option) (*apiv2.Switch, error) { if sw == nil { return nil, nil } diff --git a/pkg/repository/tenant-member.go b/pkg/repository/tenant-member.go index 583f9268..dfc28861 100644 --- a/pkg/repository/tenant-member.go +++ b/pkg/repository/tenant-member.go @@ -56,12 +56,12 @@ func (t *tenantMemberRepository) checkIfMemberIsLastOwner(ctx context.Context, r return len(members) < 2, nil } -func (t *tenantMemberRepository) convertToInternal(ctx context.Context, msg *apiv2.TenantMember) (*tenantMemberEntity, error) { +func (t *tenantMemberRepository) convertToInternal(ctx context.Context, msg *apiv2.TenantMember, opts ...Option) (*tenantMemberEntity, error) { // this is an internal interface, so no implementation here panic("unimplemented") } -func (t *tenantMemberRepository) convertToProto(ctx context.Context, e *tenantMemberEntity) (*apiv2.TenantMember, error) { +func (t *tenantMemberRepository) convertToProto(ctx context.Context, e *tenantMemberEntity, opts ...Option) (*apiv2.TenantMember, error) { return &apiv2.TenantMember{ Id: e.TenantId, Role: TenantRoleFromMap(e.Meta.Annotations), diff --git a/pkg/repository/tenant.go b/pkg/repository/tenant.go index 546af9a8..4195a2c9 100644 --- a/pkg/repository/tenant.go +++ b/pkg/repository/tenant.go @@ -218,7 +218,7 @@ func (t *tenantRepository) update(ctx context.Context, tenant *tenantEntity, rq return &tenantEntity{Tenant: resp.Tenant}, nil } -func (t *tenantRepository) convertToInternal(ctx context.Context, tenant *apiv2.Tenant) (*tenantEntity, error) { +func (t *tenantRepository) convertToInternal(ctx context.Context, tenant *apiv2.Tenant, opts ...Option) (*tenantEntity, error) { ann := map[string]string{ TenantTagEmail: tenant.Email, TenantTagAvatarURL: tenant.AvatarUrl, @@ -242,7 +242,7 @@ func (t *tenantRepository) convertToInternal(ctx context.Context, tenant *apiv2. }}, nil } -func (t *tenantRepository) convertToProto(ctx context.Context, tenant *tenantEntity) (*apiv2.Tenant, error) { +func (t *tenantRepository) convertToProto(ctx context.Context, tenant *tenantEntity, opts ...Option) (*apiv2.Tenant, error) { var labels *apiv2.Labels if tenant.Meta != nil && tenant.Meta.Labels != nil && len(tenant.Meta.Labels) > 0 { labels = &apiv2.Labels{ diff --git a/pkg/service/machine/admin/machine-service.go b/pkg/service/machine/admin/machine-service.go index 3cc7cb97..b8896b1a 100644 --- a/pkg/service/machine/admin/machine-service.go +++ b/pkg/service/machine/admin/machine-service.go @@ -60,7 +60,7 @@ func (m *machineServiceServer) List(ctx context.Context, rq *adminv2.MachineServ q := rq.Query q.Partition = partition - machines, err := m.repo.UnscopedMachine().List(ctx, q) + machines, err := m.repo.UnscopedMachine().List(ctx, q, repository.WithTransitive(rq.Complete)) if err != nil { return nil, errorutil.Convert(err) } diff --git a/pkg/service/machine/admin/machine-service_test.go b/pkg/service/machine/admin/machine-service_test.go index 5e8e3e2f..c3ec9bb6 100644 --- a/pkg/service/machine/admin/machine-service_test.go +++ b/pkg/service/machine/admin/machine-service_test.go @@ -70,7 +70,7 @@ func Test_machineServiceServer_Get(t *testing.T) { }{ { name: "get existing", - rq: &adminv2.MachineServiceGetRequest{Uuid: m1}, + rq: &adminv2.MachineServiceGetRequest{Uuid: m1, Complete: true}, want: &adminv2.MachineServiceGetResponse{ Machine: &apiv2.Machine{ Uuid: m1, @@ -178,7 +178,7 @@ func Test_machineServiceServer_List(t *testing.T) { }{ { name: "List from p1", - rq: &adminv2.MachineServiceListRequest{Query: &apiv2.MachineQuery{Allocation: &apiv2.MachineAllocationQuery{Project: pointer.Pointer(p1)}}}, + rq: &adminv2.MachineServiceListRequest{Query: &apiv2.MachineQuery{Allocation: &apiv2.MachineAllocationQuery{Project: pointer.Pointer(p1)}}, Complete: true}, want: &adminv2.MachineServiceListResponse{ Machines: []*apiv2.Machine{ { @@ -216,7 +216,7 @@ func Test_machineServiceServer_List(t *testing.T) { }, { name: "list from p2", - rq: &adminv2.MachineServiceListRequest{Query: &apiv2.MachineQuery{Uuid: pointer.Pointer(m4), Allocation: &apiv2.MachineAllocationQuery{Project: pointer.Pointer(p2)}}}, + rq: &adminv2.MachineServiceListRequest{Query: &apiv2.MachineQuery{Uuid: pointer.Pointer(m4), Allocation: &apiv2.MachineAllocationQuery{Project: pointer.Pointer(p2)}}, Complete: true}, want: &adminv2.MachineServiceListResponse{ Machines: []*apiv2.Machine{ { diff --git a/pkg/service/machine/machine-service.go b/pkg/service/machine/machine-service.go index 3781927d..de6c5ca9 100644 --- a/pkg/service/machine/machine-service.go +++ b/pkg/service/machine/machine-service.go @@ -34,7 +34,7 @@ func (m *machineServiceServer) Create(context.Context, *apiv2.MachineServiceCrea // Get implements apiv2connect.MachineServiceHandler. func (m *machineServiceServer) Get(ctx context.Context, req *apiv2.MachineServiceGetRequest) (*apiv2.MachineServiceGetResponse, error) { - machine, err := m.repo.Machine(req.Project).Get(ctx, req.Uuid) + machine, err := m.repo.Machine(req.Project).Get(ctx, req.Uuid, repository.WithTransitive(req.Complete)) if err != nil { return nil, errorutil.Convert(err) } @@ -46,7 +46,7 @@ func (m *machineServiceServer) Get(ctx context.Context, req *apiv2.MachineServic // List implements apiv2connect.MachineServiceHandler. func (m *machineServiceServer) List(ctx context.Context, rq *apiv2.MachineServiceListRequest) (*apiv2.MachineServiceListResponse, error) { - machines, err := m.repo.Machine(rq.Project).List(ctx, rq.Query) + machines, err := m.repo.Machine(rq.Project).List(ctx, rq.Query, repository.WithTransitive(rq.Complete)) if err != nil { return nil, errorutil.Convert(err) } diff --git a/pkg/service/machine/machine-service_test.go b/pkg/service/machine/machine-service_test.go index d02ed7f7..e643e86e 100644 --- a/pkg/service/machine/machine-service_test.go +++ b/pkg/service/machine/machine-service_test.go @@ -67,7 +67,7 @@ func Test_machineServiceServer_Get(t *testing.T) { // We need to create machines directly on the database because there is no MachineCreateRequest available and never will. // Once the boot-service is available we can simulate a pxe booting machine the actually create a machine from the api level. test.CreateMachines(t, testStore, []*metal.Machine{ - {Base: metal.Base{ID: m1}, PartitionID: "partition-1", SizeID: "c1-large-x86", Allocation: &metal.MachineAllocation{Project: p1, ImageID: "debian-12"}}, + {Base: metal.Base{ID: m1}, PartitionID: "partition-1", SizeID: "c1-large-x86", Allocation: &metal.MachineAllocation{Project: p1, ImageID: "debian-12", UUID: "alloc-uuid-1"}}, }) tests := []struct { @@ -77,8 +77,8 @@ func Test_machineServiceServer_Get(t *testing.T) { wantErr error }{ { - name: "get existing", - rq: &apiv2.MachineServiceGetRequest{Uuid: m1, Project: p1}, + name: "get existing complete", + rq: &apiv2.MachineServiceGetRequest{Uuid: m1, Project: p1, Complete: true}, want: &apiv2.MachineServiceGetResponse{ Machine: &apiv2.Machine{ Uuid: m1, @@ -107,6 +107,33 @@ func Test_machineServiceServer_Get(t *testing.T) { Description: pointer.Pointer(""), Name: pointer.Pointer(""), }, + Uuid: "alloc-uuid-1", + }, + }, + }, + wantErr: nil, + }, + { + name: "get existing not complete", + rq: &apiv2.MachineServiceGetRequest{Uuid: m1, Project: p1, Complete: false}, + want: &apiv2.MachineServiceGetResponse{ + Machine: &apiv2.Machine{ + Uuid: m1, + Meta: &apiv2.Meta{}, + Partition: &apiv2.Partition{Id: "partition-1"}, + Bios: &apiv2.MachineBios{}, + Hardware: &apiv2.MachineHardware{}, + Size: &apiv2.Size{Id: "c1-large-x86"}, + RecentProvisioningEvents: &apiv2.MachineRecentProvisioningEvents{ + Events: []*apiv2.MachineProvisioningEvent{{Event: apiv2.MachineProvisioningEventType_MACHINE_PROVISIONING_EVENT_TYPE_ALIVE, Message: "machine created for test"}}, + }, + Status: &apiv2.MachineStatus{ + Condition: &apiv2.MachineCondition{}, + LedState: &apiv2.MachineChassisIdentifyLEDState{}, + Liveliness: apiv2.MachineLiveliness_MACHINE_LIVELINESS_ALIVE, + }, + Allocation: &apiv2.MachineAllocation{ + Uuid: "alloc-uuid-1", }, }, }, @@ -210,7 +237,7 @@ func Test_machineServiceServer_List(t *testing.T) { }{ { name: "List from p1", - rq: &apiv2.MachineServiceListRequest{Project: p1}, + rq: &apiv2.MachineServiceListRequest{Project: p1, Complete: true}, want: &apiv2.MachineServiceListResponse{ Machines: []*apiv2.Machine{ { @@ -248,7 +275,7 @@ func Test_machineServiceServer_List(t *testing.T) { }, { name: "list from p2", - rq: &apiv2.MachineServiceListRequest{Project: p2, Query: &apiv2.MachineQuery{Uuid: pointer.Pointer(m4)}}, + rq: &apiv2.MachineServiceListRequest{Project: p2, Query: &apiv2.MachineQuery{Uuid: pointer.Pointer(m4)}, Complete: true}, want: &apiv2.MachineServiceListResponse{ Machines: []*apiv2.Machine{ { From bab9a950430e0f518f50accf32ec55b0484b60ff Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Thu, 16 Oct 2025 15:26:24 +0200 Subject: [PATCH 2/2] Fix tests --- pkg/repository/machine.go | 2 +- pkg/repository/store.go | 2 +- pkg/service/machine/admin/machine-service.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/repository/machine.go b/pkg/repository/machine.go index 8946640a..1909c472 100644 --- a/pkg/repository/machine.go +++ b/pkg/repository/machine.go @@ -132,7 +132,7 @@ func (r *machineRepository) convertToProto(ctx context.Context, m *metal.Machine case *convertOptWithTransitive: withTransitive = o.withTransitive default: - errorutil.Internal("unsupported test option: %T", o) + return nil, errorutil.Internal("unsupported test option: %T", o) } } diff --git a/pkg/repository/store.go b/pkg/repository/store.go index d88f8f71..3959ab27 100644 --- a/pkg/repository/store.go +++ b/pkg/repository/store.go @@ -324,7 +324,7 @@ func (s *store[R, E, M, C, U, Q]) Update(ctx context.Context, id string, u U) (M return zero, err } - converted, err := s.convertToProto(ctx, e) + converted, err := s.convertToProto(ctx, e, WithTransitive(true)) if err != nil { return zero, err } diff --git a/pkg/service/machine/admin/machine-service.go b/pkg/service/machine/admin/machine-service.go index b8896b1a..6fcc5661 100644 --- a/pkg/service/machine/admin/machine-service.go +++ b/pkg/service/machine/admin/machine-service.go @@ -30,7 +30,7 @@ func New(c Config) adminv2connect.MachineServiceHandler { // Get implements apiv2connect.MachineServiceHandler. func (m *machineServiceServer) Get(ctx context.Context, req *adminv2.MachineServiceGetRequest) (*adminv2.MachineServiceGetResponse, error) { - machine, err := m.repo.UnscopedMachine().Get(ctx, req.Uuid) + machine, err := m.repo.UnscopedMachine().Get(ctx, req.Uuid, repository.WithTransitive(req.Complete)) if err != nil { return nil, errorutil.Convert(err) }