mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-24 14:29:07 +02:00
[management] network map from nmap data type (#6919)
Signed-off-by: Dmitri Dolguikh <dmitri.external@netbird.io> Co-authored-by: Dmitri Dolguikh <dmitri.external@netbird.io>
This commit is contained in:
co-authored by
Dmitri Dolguikh
parent
473392a935
commit
e06c17cf59
@@ -0,0 +1,76 @@
|
||||
package networkmapdbfactory
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
networkmap_pgsql "github.com/netbirdio/netbird/management/internals/network_map_db/pgsql"
|
||||
networkmap_sqlite "github.com/netbirdio/netbird/management/internals/network_map_db/sqlite"
|
||||
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator"
|
||||
"github.com/netbirdio/netbird/management/server/settings"
|
||||
"github.com/netbirdio/netbird/management/server/store"
|
||||
"github.com/netbirdio/netbird/management/server/types"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const storeSqliteFileName = "store.db"
|
||||
|
||||
var ErrNotSupportedStoreEngine = errors.New("unsupported store engine")
|
||||
|
||||
func NewNetworkMapDBStore(
|
||||
ctx context.Context,
|
||||
kind types.Engine,
|
||||
dataDir string,
|
||||
integratedPeerValidator integrated_validator.IntegratedValidator,
|
||||
extraSettingsManager settings.Manager) (*networkmapdb.NetworkMapDBStoreImpl, error) {
|
||||
switch kind {
|
||||
case types.SqliteStoreEngine:
|
||||
log.WithContext(ctx).Info("networkmap store is using SQLite")
|
||||
storeFile := storeSqliteFileName
|
||||
if envFile, ok := os.LookupEnv("NB_STORE_ENGINE_SQLITE_FILE"); ok && envFile != "" {
|
||||
storeFile = envFile
|
||||
}
|
||||
store, err := networkmap_sqlite.NewSqliteStore(storeFile, dataDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &networkmapdb.NetworkMapDBStoreImpl{
|
||||
Store: store,
|
||||
IntegratedPeerValidator: integratedPeerValidator,
|
||||
ExtraSettingsManager: extraSettingsManager,
|
||||
}, nil
|
||||
case types.PostgresStoreEngine:
|
||||
log.WithContext(ctx).Info("using Postgres store engine")
|
||||
dsn, err := mustLookupDsnEnv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
store, err := networkmap_pgsql.NewPostgresqlStore(ctx, dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &networkmapdb.NetworkMapDBStoreImpl{
|
||||
Store: store,
|
||||
IntegratedPeerValidator: integratedPeerValidator,
|
||||
ExtraSettingsManager: extraSettingsManager,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("networkmap store doesn't support engine %s, %w", kind, ErrNotSupportedStoreEngine)
|
||||
}
|
||||
|
||||
func mustLookupDsnEnv() (string, error) {
|
||||
if v, ok := os.LookupEnv(store.PostgresDsnEnv); ok {
|
||||
return v, nil
|
||||
}
|
||||
if v, ok := os.LookupEnv(store.PostgresDsnEnvLegacy); ok {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("%s env var must be set when using postgres networkmap store", store.PostgresDsnEnv)
|
||||
}
|
||||
@@ -0,0 +1,277 @@
|
||||
package networkmapdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"strings"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
func (s *NetworkMapDBStoreImpl) GetNetworkMapData(ctx context.Context, accountId string) (*networkmap.NetworkMapData, error) {
|
||||
tx, err := s.Store.BeginTx(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
acctSettings, err := tx.GetAccountSettings(ctx, accountId)
|
||||
if err != nil {
|
||||
return rollbackAndReturnError(ctx, tx, fmt.Errorf("failed to get account settings: %w", err))
|
||||
}
|
||||
dnsZones, err := tx.GetAppliedZoneCandidates(ctx, accountId)
|
||||
if err != nil {
|
||||
return rollbackAndReturnError(ctx, tx, fmt.Errorf("failed to get applied zone candidates: %w", err))
|
||||
}
|
||||
groups, resourceToGroupIdx, err := tx.GetGroups(ctx, accountId)
|
||||
if err != nil {
|
||||
return rollbackAndReturnError(ctx, tx, fmt.Errorf("failed to get groups: %w", err))
|
||||
}
|
||||
nsGroups, err := tx.GetNameServerGroups(ctx, accountId)
|
||||
if err != nil {
|
||||
return rollbackAndReturnError(ctx, tx, fmt.Errorf("failed to get nameserver groups: %w", err))
|
||||
}
|
||||
networkResources, err := tx.GetNetworkResources(ctx, accountId)
|
||||
if err != nil {
|
||||
return rollbackAndReturnError(ctx, tx, fmt.Errorf("failed to get network resources: %w", err))
|
||||
}
|
||||
routers, err := tx.GetNetworkRouters(ctx, accountId)
|
||||
if err != nil {
|
||||
return rollbackAndReturnError(ctx, tx, fmt.Errorf("failed to get network routers: %w", err))
|
||||
}
|
||||
network, err := tx.GetNetwork(ctx, accountId)
|
||||
if err != nil {
|
||||
return rollbackAndReturnError(ctx, tx, fmt.Errorf("failed to get network: %w", err))
|
||||
}
|
||||
peers, proxyPeers, err := tx.GetPeers(ctx, accountId)
|
||||
if err != nil {
|
||||
return rollbackAndReturnError(ctx, tx, fmt.Errorf("failed to get peers: %w", err))
|
||||
}
|
||||
policies, policyToDestinationResourceIdx, policyToDestinationGroupIdx, err := tx.GetPolicies(ctx, accountId)
|
||||
if err != nil {
|
||||
return rollbackAndReturnError(ctx, tx, fmt.Errorf("failed to get policies: %w", err))
|
||||
}
|
||||
postureChecks, postureCheckXIDToPublicID, err := tx.GetPostureChecks(ctx, accountId)
|
||||
if err != nil {
|
||||
return rollbackAndReturnError(ctx, tx, fmt.Errorf("failed to get posture checks: %w", err))
|
||||
}
|
||||
routes, err := tx.GetRoutes(ctx, accountId)
|
||||
if err != nil {
|
||||
return rollbackAndReturnError(ctx, tx, fmt.Errorf("failed to get routes: %w", err))
|
||||
}
|
||||
networkXIDToPublicID, err := tx.GetNetworkXIDToPublicIdMap(ctx, accountId)
|
||||
if err != nil {
|
||||
return rollbackAndReturnError(ctx, tx, fmt.Errorf("failed to get network xid to public id map: %w", err))
|
||||
}
|
||||
allowedUserIds, groupsToUserIds, err := tx.GetAllowedUsers(ctx, accountId)
|
||||
if err != nil {
|
||||
return rollbackAndReturnError(ctx, tx, fmt.Errorf("failed to get allowed users: %w", err))
|
||||
}
|
||||
dnsSettings, err := tx.GetDnsSettings(ctx, accountId)
|
||||
if err != nil {
|
||||
return rollbackAndReturnError(ctx, tx, fmt.Errorf("failed to get dns settings: %w", err))
|
||||
}
|
||||
domains, err := tx.GetDomains(ctx, accountId)
|
||||
if err != nil {
|
||||
return rollbackAndReturnError(ctx, tx, err)
|
||||
}
|
||||
services, err := tx.GetPrivateServices(ctx, accountId)
|
||||
if err != nil {
|
||||
return rollbackAndReturnError(ctx, tx, err)
|
||||
}
|
||||
proxyTargetedDomainResourceIDs, err := tx.GetProxyTargetedDomainResourceIDs(ctx, accountId)
|
||||
if err != nil {
|
||||
return rollbackAndReturnError(ctx, tx, fmt.Errorf("failed to get proxy targeted domain resources: %w", err))
|
||||
}
|
||||
|
||||
if err = tx.CommitTx(ctx); err != nil {
|
||||
log.WithContext(ctx).Warnf("failed to commit network map read transaction: %v", err)
|
||||
}
|
||||
|
||||
resourcePolicies := buildResourcePolicies(
|
||||
networkResources, policies, resourceToGroupIdx, policyToDestinationResourceIdx, policyToDestinationGroupIdx)
|
||||
|
||||
toret := networkmap.NetworkMapData{
|
||||
AccountSettings: &acctSettings,
|
||||
DNSSettings: &dnsSettings,
|
||||
Network: &network,
|
||||
Peers: toMap(peers, func(p nmdata.Peer) string { return p.ID }),
|
||||
Groups: toMap(groups, func(g nmdata.Group) string { return g.ID }),
|
||||
Policies: toSliceOfPtrs(policies),
|
||||
ResourcePolicies: resourcePolicies,
|
||||
Routes: toSliceOfPtrs(routes),
|
||||
Routers: routers,
|
||||
NameServerGroups: toSliceOfPtrs(nsGroups),
|
||||
NetworkResources: toSliceOfPtrs(networkResources),
|
||||
PostureChecks: toMap(postureChecks, func(pc nmdata.PostureChecks) string { return pc.ID }),
|
||||
AllowedUserIDs: allowedUserIds,
|
||||
GroupIDToUserIDs: groupsToUserIds,
|
||||
NetworkXIDToPublicID: networkXIDToPublicID, // TODO (dmitri) maybe we can switch to public ids everywhere?
|
||||
AppliedZoneCandidates: dnsZones,
|
||||
PrivateServiceCandidates: buildPrivateServiceCandidates(services, domains, proxyPeers),
|
||||
PostureCheckXIDToPublicID: postureCheckXIDToPublicID,
|
||||
ProxyTargetedDomainResourceIDs: proxyTargetedDomainResourceIDs,
|
||||
}
|
||||
|
||||
extraSettings, err := s.ExtraSettingsManager.GetExtraSettings(ctx, accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
toret.ValidatedPeers, err = s.IntegratedPeerValidator.GetValidatedPeers(ctx, accountId, maps.Values(toret.Groups), maps.Values(toret.Peers), extraSettings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &toret, nil
|
||||
}
|
||||
|
||||
func rollbackAndReturnError(ctx context.Context, tx NetworkMapDBStoreConn, err error) (*networkmap.NetworkMapData, error) {
|
||||
if errr := tx.RollbackTx(ctx); errr != nil {
|
||||
log.WithContext(ctx).Warnf("failed to rollback network map read transaction: %v", errr)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func toMap[T any](all []T, id func(t T) string) map[string]*T {
|
||||
toret := make(map[string]*T, len(all))
|
||||
for _, t := range all {
|
||||
toret[id(t)] = &t
|
||||
}
|
||||
return toret
|
||||
}
|
||||
|
||||
func toSliceOfPtrs[T any](all []T) []*T {
|
||||
toret := make([]*T, 0, len(all))
|
||||
for _, t := range all {
|
||||
toret = append(toret, &t)
|
||||
}
|
||||
return toret
|
||||
}
|
||||
|
||||
func serviceDomainZone(svc Service, ds []Domain) string {
|
||||
if domainFromSuffix(svc.Domain.String, svc.ProxyCluster.String) {
|
||||
return svc.ProxyCluster.String
|
||||
}
|
||||
|
||||
var zoneName string
|
||||
for _, domain := range ds {
|
||||
if domain.TargetCluster.String != svc.ProxyCluster.String {
|
||||
continue
|
||||
}
|
||||
if domainFromSuffix(svc.Domain.String, domain.Domain.String) && len(domain.Domain.String) > len(zoneName) {
|
||||
zoneName = domain.Domain.String
|
||||
}
|
||||
}
|
||||
|
||||
return zoneName
|
||||
}
|
||||
|
||||
func domainFromSuffix(domain, suffix string) bool {
|
||||
if suffix == "" {
|
||||
return false
|
||||
}
|
||||
return domain == suffix || strings.HasSuffix(domain, "."+suffix)
|
||||
}
|
||||
|
||||
func buildPrivateServiceCandidates(svcs []Service, domains []Domain, proxyPeersByCluster map[string][]*nmdata.Peer) []networkmap.PrivateServiceCandidate {
|
||||
var out []networkmap.PrivateServiceCandidate
|
||||
|
||||
if len(proxyPeersByCluster) == 0 {
|
||||
return out
|
||||
}
|
||||
|
||||
for _, svc := range svcs {
|
||||
if !svc.Enabled.Bool || !svc.Private.Bool {
|
||||
continue
|
||||
}
|
||||
if len(svc.AccessGroups) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
domainZone := serviceDomainZone(svc, domains)
|
||||
if domainZone == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// this is implied when domainZone != "", but for maintainability's sake the check is explicit
|
||||
// TODO (dmitri) make this an invariant
|
||||
if svc.Domain.String == "" {
|
||||
continue
|
||||
}
|
||||
var records []nmdata.SimpleRecord
|
||||
for _, proxyPeer := range proxyPeersByCluster[svc.ProxyCluster.String] {
|
||||
if record, ok := recordForProxyPeer(svc.Domain.String, proxyPeer.IP); ok {
|
||||
records = append(records, record)
|
||||
}
|
||||
}
|
||||
if len(records) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
out = append(out, networkmap.PrivateServiceCandidate{
|
||||
AccessGroups: svc.AccessGroups,
|
||||
Zone: nmdata.CustomZone{
|
||||
Domain: dns.Fqdn(domainZone),
|
||||
Records: records,
|
||||
NonAuthoritative: true,
|
||||
SearchDomainDisabled: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func recordForProxyPeer(fqdn string, ip netip.Addr) (nmdata.SimpleRecord, bool) {
|
||||
if !ip.IsValid() {
|
||||
return nmdata.SimpleRecord{}, false
|
||||
}
|
||||
|
||||
return nmdata.SimpleRecord{
|
||||
Name: dns.Fqdn(fqdn),
|
||||
Type: int(dns.TypeA),
|
||||
Class: "IN",
|
||||
TTL: 5,
|
||||
RData: ip.String(),
|
||||
}, true
|
||||
}
|
||||
|
||||
func buildResourcePolicies(networkResources []nmdata.NetworkResource,
|
||||
policies []nmdata.Policy,
|
||||
resourceToGroupIdx map[string]map[string]any,
|
||||
policyToDestinationResourceIdx map[string]map[string]any,
|
||||
policyToDestinationGroupIdx map[string]map[string]any) map[string][]*nmdata.Policy {
|
||||
|
||||
resourcePolicies := make(map[string][]*nmdata.Policy)
|
||||
for _, resource := range networkResources {
|
||||
if !resource.Enabled {
|
||||
continue
|
||||
}
|
||||
networkResourceGroups := resourceToGroupIdx[resource.ID]
|
||||
for _, policy := range policies {
|
||||
if !policy.Enabled {
|
||||
continue
|
||||
}
|
||||
if _, ok := policyToDestinationResourceIdx[policy.ID][resource.ID]; ok {
|
||||
resourcePolicies[resource.ID] = append(resourcePolicies[resource.ID], &policy) // TODO (dmitri) maybe use public id?
|
||||
continue
|
||||
}
|
||||
if groupIds, ok := policyToDestinationGroupIdx[policy.ID]; ok {
|
||||
for networkResourceGroup := range networkResourceGroups {
|
||||
if _, ok := groupIds[networkResourceGroup]; ok {
|
||||
resourcePolicies[resource.ID] = append(resourcePolicies[resource.ID], &policy)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resourcePolicies
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
package networkmapdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDomainFromSuffix(t *testing.T) {
|
||||
assert.False(t, domainFromSuffix("test", ""))
|
||||
assert.False(t, domainFromSuffix("test", "suffix")) // domain != suffix
|
||||
assert.True(t, domainFromSuffix("test", "test")) // domain == suffix
|
||||
assert.False(t, domainFromSuffix("test.anothersuffix", "suffix")) // domain doesn't contain suffix
|
||||
assert.True(t, domainFromSuffix("test.suffix", "suffix")) // domain contains suffix
|
||||
}
|
||||
|
||||
func TestServiceDomainZone(t *testing.T) {
|
||||
// shortcut -- service's domain is a subomain of proxy cluster
|
||||
assert.Equal(t, "cluster",
|
||||
serviceDomainZone(
|
||||
Service{
|
||||
Domain: sql.NullString{Valid: true, String: "test.cluster"},
|
||||
ProxyCluster: sql.NullString{Valid: true, String: "cluster"}},
|
||||
[]Domain{}))
|
||||
assert.Equal(t, "a.b", serviceDomainZone(
|
||||
Service{
|
||||
Domain: sql.NullString{Valid: true, String: "test.a.b"},
|
||||
ProxyCluster: sql.NullString{Valid: true, String: "cluster"}},
|
||||
[]Domain{
|
||||
{TargetCluster: sql.NullString{Valid: true, String: "a-cluster"}},
|
||||
{TargetCluster: sql.NullString{Valid: true, String: "cluster"},
|
||||
Domain: sql.NullString{Valid: true, String: "b"}},
|
||||
{TargetCluster: sql.NullString{Valid: true, String: "cluster"},
|
||||
Domain: sql.NullString{Valid: true, String: "a.b"}}, // should return this domain, as it's the longest match
|
||||
{TargetCluster: sql.NullString{Valid: true, String: "b-cluster"}},
|
||||
}))
|
||||
// service and domain clusters don't match
|
||||
assert.Empty(t, serviceDomainZone(
|
||||
Service{
|
||||
Domain: sql.NullString{Valid: true, String: "test.a.b"},
|
||||
ProxyCluster: sql.NullString{Valid: true, String: "c-cluster"}},
|
||||
[]Domain{
|
||||
{TargetCluster: sql.NullString{Valid: true, String: "cluster"},
|
||||
Domain: sql.NullString{Valid: true, String: "a.b"}},
|
||||
}))
|
||||
// service domain is empty
|
||||
assert.Empty(t, serviceDomainZone(
|
||||
Service{
|
||||
Domain: sql.NullString{Valid: false, String: ""},
|
||||
ProxyCluster: sql.NullString{Valid: true, String: "cluster"}},
|
||||
[]Domain{
|
||||
{TargetCluster: sql.NullString{Valid: true, String: "cluster"},
|
||||
Domain: sql.NullString{Valid: true, String: "a.b"}},
|
||||
}))
|
||||
}
|
||||
|
||||
func TestRecordForProxyPeer(t *testing.T) {
|
||||
record, ok := recordForProxyPeer("test.cluster", netip.MustParseAddr("127.0.0.1"))
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, nmdata.SimpleRecord{
|
||||
Name: "test.cluster.",
|
||||
Type: 1,
|
||||
Class: "IN",
|
||||
TTL: 5,
|
||||
RData: "127.0.0.1",
|
||||
}, record)
|
||||
|
||||
// invalid address
|
||||
var addr netip.Addr
|
||||
_, ok = recordForProxyPeer("test.cluster", addr)
|
||||
assert.False(t, ok)
|
||||
}
|
||||
|
||||
var empty []networkmap.PrivateServiceCandidate
|
||||
|
||||
// empty proxyPeersByCluster results in empty []PrivateServiceCandidates
|
||||
func TestBuildPrivateServiceCandidates_EmptyProxyPeers(t *testing.T) {
|
||||
assert.Equal(t, empty, buildPrivateServiceCandidates([]Service{}, []Domain{}, nil))
|
||||
}
|
||||
|
||||
// disabled service returns an empty result
|
||||
func TestBuildPrivateServiceCandidates_DisabledService(t *testing.T) {
|
||||
assert.Equal(t, empty,
|
||||
buildPrivateServiceCandidates([]Service{
|
||||
{Enabled: sql.NullBool{Valid: true, Bool: false},
|
||||
Private: sql.NullBool{Valid: true, Bool: true},
|
||||
AccessGroups: []string{"group-1", "group-2"},
|
||||
Domain: sql.NullString{Valid: true, String: "test.a.b"},
|
||||
ProxyCluster: sql.NullString{Valid: true, String: "cluster"}},
|
||||
}, []Domain{
|
||||
{TargetCluster: sql.NullString{Valid: true, String: "cluster"},
|
||||
Domain: sql.NullString{Valid: true, String: "a.b"}},
|
||||
},
|
||||
map[string][]*nmdata.Peer{
|
||||
"cluster": {&nmdata.Peer{IP: netip.MustParseAddr("127.0.0.1")}, &nmdata.Peer{IP: netip.MustParseAddr("127.0.0.2")}},
|
||||
"a-cluster": {&nmdata.Peer{IP: netip.MustParseAddr("127.0.0.3")}, &nmdata.Peer{IP: netip.MustParseAddr("127.0.0.4")}},
|
||||
}))
|
||||
}
|
||||
|
||||
// non-private service results in empty []PrivateServiceCandidates
|
||||
func TestBuildPrivateServiceCandidates_PublicService(t *testing.T) {
|
||||
assert.Equal(t, empty,
|
||||
buildPrivateServiceCandidates([]Service{
|
||||
{Enabled: sql.NullBool{Valid: true, Bool: true},
|
||||
Private: sql.NullBool{Valid: true, Bool: false},
|
||||
AccessGroups: []string{"group-1", "group-2"},
|
||||
Domain: sql.NullString{Valid: true, String: "test.a.b"},
|
||||
ProxyCluster: sql.NullString{Valid: true, String: "cluster"}},
|
||||
}, []Domain{
|
||||
{TargetCluster: sql.NullString{Valid: true, String: "cluster"},
|
||||
Domain: sql.NullString{Valid: true, String: "a.b"}},
|
||||
},
|
||||
map[string][]*nmdata.Peer{
|
||||
"cluster": {&nmdata.Peer{IP: netip.MustParseAddr("127.0.0.1")}, &nmdata.Peer{IP: netip.MustParseAddr("127.0.0.2")}},
|
||||
"a-cluster": {&nmdata.Peer{IP: netip.MustParseAddr("127.0.0.3")}, &nmdata.Peer{IP: netip.MustParseAddr("127.0.0.4")}},
|
||||
}))
|
||||
}
|
||||
|
||||
// empty AccessList results in empty []PrivateServiceCandidates
|
||||
func TestBuildPrivateServiceCandidates_EmptyAccessList(t *testing.T) {
|
||||
assert.Equal(t, empty,
|
||||
buildPrivateServiceCandidates([]Service{
|
||||
{Enabled: sql.NullBool{Valid: true, Bool: true},
|
||||
Private: sql.NullBool{Valid: true, Bool: true},
|
||||
Domain: sql.NullString{Valid: true, String: "test.a.b"},
|
||||
ProxyCluster: sql.NullString{Valid: true, String: "cluster"}},
|
||||
}, []Domain{
|
||||
{TargetCluster: sql.NullString{Valid: true, String: "cluster"},
|
||||
Domain: sql.NullString{Valid: true, String: "a.b"}},
|
||||
},
|
||||
map[string][]*nmdata.Peer{
|
||||
"cluster": {&nmdata.Peer{IP: netip.MustParseAddr("127.0.0.1")}, &nmdata.Peer{IP: netip.MustParseAddr("127.0.0.2")}},
|
||||
"a-cluster": {&nmdata.Peer{IP: netip.MustParseAddr("127.0.0.3")}, &nmdata.Peer{IP: netip.MustParseAddr("127.0.0.4")}},
|
||||
}))
|
||||
}
|
||||
|
||||
// empty TragetCluster results in empty []PrivateServiceCandidates
|
||||
func TestBuildPrivateServiceCandidates_EmptyTargetCluster(t *testing.T) {
|
||||
assert.Equal(t, empty,
|
||||
buildPrivateServiceCandidates([]Service{
|
||||
{Enabled: sql.NullBool{Valid: true, Bool: true},
|
||||
Private: sql.NullBool{Valid: true, Bool: true},
|
||||
AccessGroups: []string{"group-1", "group-2"},
|
||||
Domain: sql.NullString{Valid: true, String: "test.a.b"},
|
||||
ProxyCluster: sql.NullString{Valid: true, String: "cluster"}},
|
||||
}, []Domain{
|
||||
{TargetCluster: sql.NullString{Valid: true, String: ""},
|
||||
Domain: sql.NullString{Valid: true, String: "a.b"}},
|
||||
},
|
||||
map[string][]*nmdata.Peer{
|
||||
"cluster": {&nmdata.Peer{IP: netip.MustParseAddr("127.0.0.1")}, &nmdata.Peer{IP: netip.MustParseAddr("127.0.0.2")}},
|
||||
"a-cluster": {&nmdata.Peer{IP: netip.MustParseAddr("127.0.0.3")}, &nmdata.Peer{IP: netip.MustParseAddr("127.0.0.4")}},
|
||||
}))
|
||||
}
|
||||
|
||||
func TestBuildPrivateServiceCandidates_EmptyServiceDomain(t *testing.T) {
|
||||
assert.Equal(t, empty,
|
||||
buildPrivateServiceCandidates([]Service{
|
||||
{Enabled: sql.NullBool{Valid: true, Bool: true},
|
||||
Private: sql.NullBool{Valid: true, Bool: true},
|
||||
Domain: sql.NullString{Valid: true, String: ""},
|
||||
ProxyCluster: sql.NullString{Valid: true, String: "cluster"}},
|
||||
}, []Domain{
|
||||
{TargetCluster: sql.NullString{Valid: true, String: "cluster"},
|
||||
Domain: sql.NullString{Valid: true, String: "a.b"}},
|
||||
},
|
||||
map[string][]*nmdata.Peer{
|
||||
"cluster": {&nmdata.Peer{IP: netip.MustParseAddr("127.0.0.1")}, &nmdata.Peer{IP: netip.MustParseAddr("127.0.0.2")}},
|
||||
"a-cluster": {&nmdata.Peer{IP: netip.MustParseAddr("127.0.0.3")}, &nmdata.Peer{IP: netip.MustParseAddr("127.0.0.4")}},
|
||||
}))
|
||||
}
|
||||
|
||||
func TestBuildPrivateServiceCandidates_HappyPath(t *testing.T) {
|
||||
assert.Equal(t, []networkmap.PrivateServiceCandidate{
|
||||
{
|
||||
AccessGroups: []string{"group-1", "group-2"},
|
||||
Zone: nmdata.CustomZone{
|
||||
Domain: "a.b.",
|
||||
SearchDomainDisabled: true,
|
||||
NonAuthoritative: true,
|
||||
Records: []nmdata.SimpleRecord{
|
||||
{
|
||||
Name: "test.a.b.",
|
||||
Type: 1,
|
||||
Class: "IN",
|
||||
TTL: 5,
|
||||
RData: "127.0.0.1",
|
||||
},
|
||||
{
|
||||
Name: "test.a.b.",
|
||||
Type: 1,
|
||||
Class: "IN",
|
||||
TTL: 5,
|
||||
RData: "127.0.0.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
AccessGroups: []string{"group-1", "group-2"},
|
||||
Zone: nmdata.CustomZone{
|
||||
Domain: "c.d.",
|
||||
SearchDomainDisabled: true,
|
||||
NonAuthoritative: true,
|
||||
Records: []nmdata.SimpleRecord{
|
||||
{
|
||||
Name: "test.c.d.",
|
||||
Type: 1,
|
||||
Class: "IN",
|
||||
TTL: 5,
|
||||
RData: "127.0.0.3",
|
||||
},
|
||||
{
|
||||
Name: "test.c.d.",
|
||||
Type: 1,
|
||||
Class: "IN",
|
||||
TTL: 5,
|
||||
RData: "127.0.0.4",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
buildPrivateServiceCandidates([]Service{
|
||||
{Enabled: sql.NullBool{Valid: true, Bool: true},
|
||||
Private: sql.NullBool{Valid: true, Bool: true},
|
||||
AccessGroups: []string{"group-1", "group-2"},
|
||||
Domain: sql.NullString{Valid: true, String: "test.a.b"},
|
||||
ProxyCluster: sql.NullString{Valid: true, String: "cluster"}},
|
||||
{Enabled: sql.NullBool{Valid: true, Bool: true},
|
||||
Private: sql.NullBool{Valid: true, Bool: true},
|
||||
AccessGroups: []string{"group-1", "group-2"},
|
||||
Domain: sql.NullString{Valid: true, String: "test.c.d"},
|
||||
ProxyCluster: sql.NullString{Valid: true, String: "a-cluster"}},
|
||||
}, []Domain{
|
||||
{TargetCluster: sql.NullString{Valid: true, String: "cluster"},
|
||||
Domain: sql.NullString{Valid: true, String: "a.b"}},
|
||||
{TargetCluster: sql.NullString{Valid: true, String: "a-cluster"},
|
||||
Domain: sql.NullString{Valid: true, String: "c.d"}},
|
||||
},
|
||||
map[string][]*nmdata.Peer{
|
||||
"cluster": {&nmdata.Peer{IP: netip.MustParseAddr("127.0.0.1")}, &nmdata.Peer{IP: netip.MustParseAddr("127.0.0.2")}},
|
||||
"a-cluster": {&nmdata.Peer{IP: netip.MustParseAddr("127.0.0.3")}, &nmdata.Peer{IP: netip.MustParseAddr("127.0.0.4")}},
|
||||
}))
|
||||
}
|
||||
|
||||
// disabled network resource shouldn't be in the resulting map
|
||||
func TestBuildResourcePolicies_DisabledNetworkResource(t *testing.T) {
|
||||
networkResources := []nmdata.NetworkResource{
|
||||
{ID: "net-res-1", Enabled: false},
|
||||
}
|
||||
policies := []nmdata.Policy{
|
||||
{ID: "policy-1", Enabled: true},
|
||||
}
|
||||
resourceToGroupIdx := map[string]map[string]any{}
|
||||
policyToDestinationResourceIdx := map[string]map[string]any{
|
||||
"policy-1": {
|
||||
"net-res-1": struct{}{},
|
||||
"net-res-3": struct{}{},
|
||||
},
|
||||
}
|
||||
policyToDestinationGroupIdx := map[string]map[string]any{}
|
||||
|
||||
assert.Empty(t, buildResourcePolicies(
|
||||
networkResources, policies, resourceToGroupIdx, policyToDestinationResourceIdx, policyToDestinationGroupIdx))
|
||||
}
|
||||
|
||||
// disabled policy shouldn't be in the resulting map
|
||||
func TestBuildResourcePolicies_DisabledPolicy(t *testing.T) {
|
||||
networkResources := []nmdata.NetworkResource{
|
||||
{ID: "net-res-1", Enabled: true},
|
||||
}
|
||||
policies := []nmdata.Policy{
|
||||
{ID: "policy-1", Enabled: false},
|
||||
}
|
||||
resourceToGroupIdx := map[string]map[string]any{}
|
||||
policyToDestinationResourceIdx := map[string]map[string]any{
|
||||
"policy-1": {
|
||||
"net-res-1": struct{}{},
|
||||
"net-res-3": struct{}{},
|
||||
},
|
||||
}
|
||||
policyToDestinationGroupIdx := map[string]map[string]any{}
|
||||
|
||||
assert.Empty(t, buildResourcePolicies(
|
||||
networkResources, policies, resourceToGroupIdx, policyToDestinationResourceIdx, policyToDestinationGroupIdx))
|
||||
}
|
||||
|
||||
// build ResourcePolicies via PolicyToDestinationResourceIdx only
|
||||
func TestBuildResourcePolicies_ViaPolicyToDestinationResourceIdx(t *testing.T) {
|
||||
networkResources := []nmdata.NetworkResource{
|
||||
{ID: "net-res-1", Enabled: true},
|
||||
{ID: "net-res-2", Enabled: true},
|
||||
{ID: "net-res-3", Enabled: true},
|
||||
}
|
||||
policies := []nmdata.Policy{
|
||||
{ID: "policy-1", Enabled: true},
|
||||
{ID: "policy-2", Enabled: true},
|
||||
{ID: "policy-3", Enabled: true},
|
||||
}
|
||||
resourceToGroupIdx := map[string]map[string]any{}
|
||||
policyToDestinationResourceIdx := map[string]map[string]any{
|
||||
"policy-1": {
|
||||
"net-res-1": struct{}{},
|
||||
"net-res-3": struct{}{},
|
||||
},
|
||||
"policy-2": {
|
||||
"net-res-2": struct{}{},
|
||||
},
|
||||
"policy-3": {
|
||||
"net-res-1": struct{}{},
|
||||
"net-res-2": struct{}{},
|
||||
},
|
||||
}
|
||||
policyToDestinationGroupIdx := map[string]map[string]any{}
|
||||
|
||||
resourceToPolicies := buildResourcePolicies(
|
||||
networkResources, policies, resourceToGroupIdx, policyToDestinationResourceIdx, policyToDestinationGroupIdx)
|
||||
|
||||
assert.Equal(t, map[string][]*nmdata.Policy{
|
||||
"net-res-1": {
|
||||
{ID: "policy-1", Enabled: true},
|
||||
{ID: "policy-3", Enabled: true},
|
||||
},
|
||||
"net-res-2": {
|
||||
{ID: "policy-2", Enabled: true},
|
||||
{ID: "policy-3", Enabled: true},
|
||||
},
|
||||
"net-res-3": {
|
||||
{ID: "policy-1", Enabled: true},
|
||||
},
|
||||
}, resourceToPolicies)
|
||||
}
|
||||
|
||||
// build ResourcePolicies via PolicyToDestinationGroupIdx only
|
||||
func TestBuildResourcePolicies_ViaPolicyToDestinationGroupIdx(t *testing.T) {
|
||||
networkResources := []nmdata.NetworkResource{
|
||||
{ID: "net-res-1", Enabled: true},
|
||||
{ID: "net-res-2", Enabled: true},
|
||||
{ID: "net-res-3", Enabled: true},
|
||||
}
|
||||
policies := []nmdata.Policy{
|
||||
{ID: "policy-1", Enabled: true},
|
||||
{ID: "policy-2", Enabled: true},
|
||||
{ID: "policy-3", Enabled: true},
|
||||
}
|
||||
resourceToGroupIdx := map[string]map[string]any{
|
||||
"net-res-1": {
|
||||
"group-1": struct{}{},
|
||||
"group-2": struct{}{},
|
||||
},
|
||||
"net-res-2": {
|
||||
"group-2": struct{}{},
|
||||
"group-3": struct{}{},
|
||||
},
|
||||
"net-res-3": {
|
||||
"group-3": struct{}{},
|
||||
"group-4": struct{}{},
|
||||
},
|
||||
}
|
||||
policyToDestinationResourceIdx := map[string]map[string]any{}
|
||||
policyToDestinationGroupIdx := map[string]map[string]any{
|
||||
"policy-1": {
|
||||
"group-1": struct{}{},
|
||||
"group-2": struct{}{},
|
||||
},
|
||||
"policy-2": {
|
||||
"group-1": struct{}{},
|
||||
"group-4": struct{}{},
|
||||
},
|
||||
"policy-3": {
|
||||
"group-1": struct{}{},
|
||||
"group-3": struct{}{},
|
||||
},
|
||||
}
|
||||
|
||||
resourceToPolicies := buildResourcePolicies(
|
||||
networkResources, policies, resourceToGroupIdx, policyToDestinationResourceIdx, policyToDestinationGroupIdx)
|
||||
|
||||
assert.Equal(t, map[string][]*nmdata.Policy{
|
||||
"net-res-1": {
|
||||
{ID: "policy-1", Enabled: true},
|
||||
{ID: "policy-2", Enabled: true},
|
||||
{ID: "policy-3", Enabled: true},
|
||||
},
|
||||
"net-res-2": {
|
||||
{ID: "policy-1", Enabled: true},
|
||||
{ID: "policy-3", Enabled: true},
|
||||
},
|
||||
"net-res-3": {
|
||||
{ID: "policy-2", Enabled: true},
|
||||
{ID: "policy-3", Enabled: true},
|
||||
},
|
||||
}, resourceToPolicies)
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package networkmap_pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
const (
|
||||
GetAccountSettingsQuery = `
|
||||
select settings_peer_login_expiration_enabled as peer_login_expiration_enabled,
|
||||
settings_peer_login_expiration as peer_login_expiration,
|
||||
settings_peer_inactivity_expiration_enabled as peer_inactivity_expiration_enabled,
|
||||
settings_peer_inactivity_expiration as peer_inactivity_expiration,
|
||||
settings_dns_domain as dns_domain,
|
||||
settings_ipv6_enabled_groups as ipv6_enabled_groups,
|
||||
settings_routing_peer_dns_resolution_enabled as routing_peer_dns_resolution_enabled,
|
||||
settings_lazy_connection_enabled as lazy_connection_enabled,
|
||||
settings_auto_update_version as auto_update_version,
|
||||
settings_auto_update_always as auto_update_always,
|
||||
settings_metrics_push_enabled as metrics_push_enabled
|
||||
from accounts
|
||||
where id=$1
|
||||
`
|
||||
)
|
||||
|
||||
func (pgc *PgStoreConn) GetAccountSettings(ctx context.Context, accountId string) (nmdata.AccountSettingsInfo, error) {
|
||||
rows, err := pgc.Conn.Query(ctx, GetAccountSettingsQuery, accountId)
|
||||
if err != nil {
|
||||
return nmdata.AccountSettingsInfo{}, err
|
||||
}
|
||||
|
||||
settings, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[networkmapdb.Account])
|
||||
if err != nil {
|
||||
return nmdata.AccountSettingsInfo{}, err
|
||||
}
|
||||
|
||||
settingsInfo := nmdata.AccountSettingsInfo{
|
||||
PeerLoginExpirationEnabled: settings.PeerLoginExpirationEnabled.Bool,
|
||||
PeerLoginExpiration: time.Duration(settings.PeerLoginExpiration.Int64),
|
||||
PeerInactivityExpirationEnabled: settings.PeerInactivityExpirationEnabled.Bool,
|
||||
PeerInactivityExpiration: time.Duration(settings.PeerInactivityExpiration.Int64),
|
||||
DNSDomain: settings.DNSDomain.String,
|
||||
RoutingPeerDNSResolutionEnabled: settings.RoutingPeerDNSResolutionEnabled.Bool,
|
||||
LazyConnectionEnabled: settings.LazyConnectionEnabled.Bool,
|
||||
AutoUpdateVersion: settings.AutoUpdateVersion.String,
|
||||
AutoUpdateAlways: settings.AutoUpdateAlways.Bool,
|
||||
MetricsPushEnabled: settings.MetricsPushEnabled.Bool,
|
||||
}
|
||||
if settings.IPv6EnabledGroups != nil {
|
||||
if err := json.Unmarshal(settings.IPv6EnabledGroups, &settingsInfo.IPv6EnabledGroups); err != nil {
|
||||
return nmdata.AccountSettingsInfo{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return settingsInfo, nil
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package networkmap_pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap"
|
||||
)
|
||||
|
||||
const (
|
||||
GetAccountZonesQuery = `
|
||||
select zones.id as id, domain, not enable_search_domain as search_domain_disabled, distribution_groups,
|
||||
r.name as record_name, r.type as record_type, 'IN' record_class, r.ttl as record_ttl, r.content as record_rdata
|
||||
from zones
|
||||
left join records as r on r.zone_id = zones.id
|
||||
where zones.account_id=$1 and zones.enabled
|
||||
`
|
||||
)
|
||||
|
||||
func (pgc *PgStoreConn) GetAppliedZoneCandidates(ctx context.Context, accountId string) ([]networkmap.AppliedZoneCandidate, error) {
|
||||
rows, err := pgc.Conn.Query(ctx, GetAccountZonesQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zones, err := pgx.CollectRows(rows, pgx.RowToStructByName[networkmapdb.Zone])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return networkmapdb.ZonesToAppliedZoneCandidates(zones)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package networkmap_pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
const (
|
||||
GetDnsSettingsQuery = `
|
||||
select dns_settings_disabled_management_groups
|
||||
from accounts
|
||||
where id=$1
|
||||
`
|
||||
)
|
||||
|
||||
func (pgc *PgStoreConn) GetDnsSettings(ctx context.Context, accountId string) (nmdata.DNSSettings, error) {
|
||||
rows, err := pgc.Conn.Query(ctx, GetDnsSettingsQuery, accountId)
|
||||
if err != nil {
|
||||
return nmdata.DNSSettings{}, err
|
||||
}
|
||||
|
||||
return pgx.CollectOneRow(rows, rowToDnsSettings)
|
||||
}
|
||||
|
||||
func rowToDnsSettings(row pgx.CollectableRow) (nmdata.DNSSettings, error) {
|
||||
var value nmdata.DNSSettings
|
||||
var settings json.RawMessage
|
||||
|
||||
if err := row.Scan(&settings); err != nil {
|
||||
return value, err
|
||||
}
|
||||
|
||||
if settings == nil {
|
||||
return nmdata.DNSSettings{}, nil
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(settings, &value.DisabledManagementGroups); err != nil {
|
||||
return value, err
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package networkmap_pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
)
|
||||
|
||||
const (
|
||||
GetDomainsQuery = `
|
||||
select domain, target_cluster
|
||||
from domains
|
||||
where account_id=$1 and domain<>'' and target_cluster<>''
|
||||
`
|
||||
)
|
||||
|
||||
func (pgc *PgStoreConn) GetDomains(ctx context.Context, accountId string) ([]networkmapdb.Domain, error) {
|
||||
rows, err := pgc.Conn.Query(ctx, GetDomainsQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pgx.CollectRows(rows, pgx.RowToStructByName[networkmapdb.Domain])
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package networkmap_pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
const (
|
||||
GetGroupsQuery = `
|
||||
select id, name, public_id, resources,
|
||||
(
|
||||
select array_agg(group_peers.peer_id)
|
||||
from group_peers
|
||||
where group_peers.group_id = groups.id and group_peers.account_id=$1
|
||||
) as peers
|
||||
from groups where account_id=$1
|
||||
`
|
||||
)
|
||||
|
||||
// we also return a resource-to-group index.
|
||||
// an alternative is to add json indexes, query this directly. Not sure how expensive
|
||||
// json indexes are. TODO (dmitri) verify and maybe change the implementation here.
|
||||
func (pgc *PgStoreConn) GetGroups(ctx context.Context, accountId string) ([]nmdata.Group, map[string]map[string]any, error) {
|
||||
rows, err := pgc.Conn.Query(ctx, GetGroupsQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
groups, err := pgx.CollectRows(rows, pgx.RowToStructByName[group])
|
||||
toret := make([]nmdata.Group, 0, len(groups))
|
||||
resourceToGroupIdx := make(map[string]map[string]any)
|
||||
|
||||
for _, g := range groups {
|
||||
dg := nmdata.Group{}
|
||||
err := networkmapdb.FromSqlTypesToSharedTypes(
|
||||
reflect.ValueOf(&g), reflect.ValueOf(&dg))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
toret = append(toret, dg)
|
||||
for _, resource := range dg.Resources {
|
||||
if _, ok := resourceToGroupIdx[resource.ID]; !ok {
|
||||
resourceToGroupIdx[resource.ID] = make(map[string]any)
|
||||
}
|
||||
resourceToGroupIdx[resource.ID][g.ID] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return toret, resourceToGroupIdx, err
|
||||
}
|
||||
|
||||
type group struct {
|
||||
ID string
|
||||
Name sql.NullString
|
||||
PublicID sql.NullString
|
||||
Resources json.RawMessage
|
||||
Peers []string
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package networkmap_pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
const (
|
||||
GetNameserversQuery = `
|
||||
select id, public_id, name, description, name_servers, groups, "primary", domains, enabled, search_domains_enabled
|
||||
from name_server_groups
|
||||
where account_id=$1
|
||||
`
|
||||
)
|
||||
|
||||
func (pgc *PgStoreConn) GetNameServerGroups(ctx context.Context, accountId string) ([]nmdata.NameServerGroup, error) {
|
||||
rows, err := pgc.Conn.Query(ctx, GetNameserversQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nsgroups, err := pgx.CollectRows(rows, pgx.RowToStructByName[networkmapdb.NameserverGroup])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return networkmapdb.ConvertAllToSharedTypes[networkmapdb.NameserverGroup, nmdata.NameServerGroup](nsgroups)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package networkmap_pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
const (
|
||||
GetNetworkQuery = `
|
||||
select network_identifier as identifier, network_net as net, network_net_v6 as net_v6, network_dns as dns, network_serial as serial
|
||||
from accounts
|
||||
where id=$1
|
||||
`
|
||||
)
|
||||
|
||||
func (pgc *PgStoreConn) GetNetwork(ctx context.Context, accountId string) (nmdata.Network, error) {
|
||||
rows, err := pgc.Conn.Query(ctx, GetNetworkQuery, accountId)
|
||||
if err != nil {
|
||||
return nmdata.Network{}, err
|
||||
}
|
||||
|
||||
n, err := pgx.CollectOneRow(rows, pgx.RowToStructByName[networkmapdb.AccountNetwork])
|
||||
if err != nil {
|
||||
return nmdata.Network{}, err
|
||||
}
|
||||
|
||||
toret := nmdata.Network{}
|
||||
err = networkmapdb.FromSqlTypesToSharedTypes(
|
||||
reflect.ValueOf(&n), reflect.ValueOf(&toret))
|
||||
if err != nil {
|
||||
return nmdata.Network{}, err
|
||||
}
|
||||
|
||||
return toret, nil
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package networkmap_pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
const (
|
||||
GetNetworkResourcesQuery = `
|
||||
select id, network_id, account_id, public_id, name, description, type, domain, prefix, enabled
|
||||
from network_resources
|
||||
where account_id=$1
|
||||
`
|
||||
)
|
||||
|
||||
func (pgc *PgStoreConn) GetNetworkResources(ctx context.Context, accountId string) ([]nmdata.NetworkResource, error) {
|
||||
rows, err := pgc.Conn.Query(ctx, GetNetworkResourcesQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
netresorces, err := pgx.CollectRows(rows, pgx.RowToStructByName[networkmapdb.Networkresource])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return networkmapdb.ConvertAllToSharedTypes[networkmapdb.Networkresource, nmdata.NetworkResource](netresorces)
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package networkmap_pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
const (
|
||||
GetNetworkRouterQuery = `
|
||||
select public_id, peer, network_id, masquerade, metric, enabled, peer_groups,
|
||||
(
|
||||
select array_agg(group_peers.peer_id)
|
||||
from group_peers
|
||||
where group_peers.account_id=$1 and group_peers.group_id in (select json_array_elements_text(peer_groups::json))
|
||||
) as peers_via_groups
|
||||
from network_routers
|
||||
where account_id=$1
|
||||
`
|
||||
)
|
||||
|
||||
func (pgc *PgStoreConn) GetNetworkRouters(ctx context.Context, accountId string) (map[string]map[string]*nmdata.NetworkRouter, error) {
|
||||
rows, err := pgc.Conn.Query(ctx, GetNetworkRouterQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
routers, err := pgx.CollectRows(rows, pgx.RowToStructByName[networkrouter])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
toret := make(map[string]map[string]*nmdata.NetworkRouter)
|
||||
for _, router := range routers {
|
||||
if !router.Enabled.Bool {
|
||||
continue
|
||||
}
|
||||
|
||||
networkId := router.NetworkID.String
|
||||
if networkId == "" {
|
||||
return nil, fmt.Errorf("router with public_id %s doesn't have network_id set", router.PublicID.String)
|
||||
}
|
||||
|
||||
nmdatarouter := nmdata.NetworkRouter{}
|
||||
err := networkmapdb.FromSqlTypesToSharedTypes(reflect.ValueOf(&router), reflect.ValueOf(&nmdatarouter))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if toret[networkId] == nil {
|
||||
toret[networkId] = make(map[string]*nmdata.NetworkRouter)
|
||||
}
|
||||
if router.Peer.String != "" {
|
||||
toret[networkId][router.Peer.String] = &nmdatarouter
|
||||
continue
|
||||
}
|
||||
for _, peerId := range router.PeersViaGroups {
|
||||
toret[networkId][peerId] = &nmdatarouter
|
||||
}
|
||||
}
|
||||
|
||||
return toret, nil
|
||||
}
|
||||
|
||||
type networkrouter struct {
|
||||
PublicID sql.NullString
|
||||
NetworkID sql.NullString `nmap:"skip"`
|
||||
Peer sql.NullString `nmap:"skip"`
|
||||
PeerGroups json.RawMessage
|
||||
PeersViaGroups []string `nmap:"skip"`
|
||||
Masquerade sql.NullBool
|
||||
Metric sql.NullInt64
|
||||
Enabled sql.NullBool
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package networkmap_pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
)
|
||||
|
||||
const (
|
||||
GetNetworksQuery = `
|
||||
select id, public_id
|
||||
from networks where account_id=$1
|
||||
`
|
||||
)
|
||||
|
||||
func (pgc *PgStoreConn) GetNetworkXIDToPublicIdMap(ctx context.Context, accountId string) (map[string]string, error) {
|
||||
rows, err := pgc.Conn.Query(ctx, GetNetworksQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
networks, err := pgx.CollectRows(rows, pgx.RowToStructByName[networkmapdb.Network])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
toret := make(map[string]string)
|
||||
for _, n := range networks {
|
||||
if n.PublicID.Valid {
|
||||
toret[n.ID] = n.PublicID.String
|
||||
}
|
||||
}
|
||||
|
||||
return toret, nil
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package networkmap_pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
const (
|
||||
GetPeersQuery = `
|
||||
select id, key, ssh_key, dns_label, extra_dns_labels, user_id, ssh_enabled, login_expiration_enabled, last_login, ip, ipv6,
|
||||
peer_status_requires_approval, peer_status_connected, proxy_meta_embedded, proxy_meta_cluster,
|
||||
meta_wt_version, meta_go_os, meta_os_version, meta_kernel_version, meta_network_addresses, meta_files, meta_capabilities, meta_flags, meta_sync_message_version,
|
||||
location_country_code, location_city_name, location_connection_ip
|
||||
from peers
|
||||
where account_id = $1
|
||||
`
|
||||
)
|
||||
|
||||
func (pgc *PgStoreConn) GetPeers(ctx context.Context, accountId string) ([]nmdata.Peer, map[string][]*nmdata.Peer, error) {
|
||||
rows, err := pgc.Conn.Query(ctx, GetPeersQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
peers, err := pgx.CollectRows(rows, pgx.RowToStructByName[networkmapdb.Peer])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return networkmapdb.ConvertToNmdataPeers(peers)
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package networkmap_pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
)
|
||||
|
||||
const (
|
||||
pgMaxConnections = 30
|
||||
pgMinConnections = 1
|
||||
pgMaxConnLifetime = 60 * time.Minute
|
||||
pgHealthCheckPeriod = 1 * time.Minute
|
||||
)
|
||||
|
||||
var _ networkmapdb.NetworkMapDBStore = &PgStore{}
|
||||
|
||||
type PgStore struct {
|
||||
Pool *pgxpool.Pool
|
||||
Location *time.Location
|
||||
}
|
||||
|
||||
type PgStoreConn struct {
|
||||
Conn pgInterface
|
||||
}
|
||||
|
||||
type pgInterface interface {
|
||||
Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
|
||||
Exec(ctx context.Context, sql string, arguments ...any) (pgconn.CommandTag, error)
|
||||
}
|
||||
|
||||
var _ networkmapdb.NetworkMapDBStoreConn = &PgStoreConn{}
|
||||
|
||||
func NewPostgresqlStore(ctx context.Context, dsn string) (*PgStore, error) {
|
||||
pool, err := connectToPgDb(ctx, dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &PgStore{Pool: pool}, nil
|
||||
}
|
||||
|
||||
// This is used to control the timezone timestamps returned in.
|
||||
// By default pgx returns timestamps in the local timezone,
|
||||
// which may not be desirable.
|
||||
// use .UsingTimeZone(time.UTC) to return timestamps in UTC TZ
|
||||
func (p *PgStore) UsingTimeZone(location *time.Location) {
|
||||
p.Location = location
|
||||
}
|
||||
|
||||
func (p *PgStore) UsingConnection(c *pgx.Conn) networkmapdb.NetworkMapDBStoreConn {
|
||||
if p.Location != nil {
|
||||
c.TypeMap().RegisterType(&pgtype.Type{
|
||||
Name: "timestamptz",
|
||||
OID: pgtype.TimestamptzOID,
|
||||
Codec: &pgtype.TimestamptzCodec{ScanLocation: time.UTC},
|
||||
})
|
||||
}
|
||||
|
||||
return &PgStoreConn{Conn: c}
|
||||
}
|
||||
|
||||
func (p *PgStore) Exec(ctx context.Context, query string, args ...any) error {
|
||||
_, err := p.Pool.Exec(ctx, query, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *PgStore) BeginTx(ctx context.Context) (networkmapdb.NetworkMapDBStoreConn, error) {
|
||||
tx, err := p.Pool.BeginTx(ctx, pgx.TxOptions{IsoLevel: pgx.RepeatableRead, AccessMode: pgx.ReadOnly})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p.Location != nil {
|
||||
tx.Conn().TypeMap().RegisterType(&pgtype.Type{
|
||||
Name: "timestamptz",
|
||||
OID: pgtype.TimestamptzOID,
|
||||
Codec: &pgtype.TimestamptzCodec{ScanLocation: time.UTC},
|
||||
})
|
||||
}
|
||||
return &PgStoreConn{Conn: tx}, nil
|
||||
}
|
||||
|
||||
func (c *PgStoreConn) RollbackTx(ctx context.Context) error {
|
||||
tx, ok := c.Conn.(pgx.Tx)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected an pgx.Tx got %s", reflect.TypeOf(c.Conn).Kind())
|
||||
}
|
||||
return tx.Rollback(ctx)
|
||||
}
|
||||
|
||||
func (c *PgStoreConn) CommitTx(ctx context.Context) error {
|
||||
tx, ok := c.Conn.(pgx.Tx)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected an sql.Tx got %s", reflect.TypeOf(c.Conn).Kind())
|
||||
}
|
||||
return tx.Commit(ctx)
|
||||
}
|
||||
|
||||
func connectToPgDb(ctx context.Context, dsn string) (*pgxpool.Pool, error) {
|
||||
config, err := pgxpool.ParseConfig(dsn)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse database config: %w", err)
|
||||
}
|
||||
|
||||
config.MaxConns = pgMaxConnections
|
||||
config.MinConns = pgMinConnections
|
||||
config.MaxConnLifetime = pgMaxConnLifetime
|
||||
config.HealthCheckPeriod = pgHealthCheckPeriod
|
||||
|
||||
pool, err := pgxpool.NewWithConfig(ctx, config)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create connection pool: %w", err)
|
||||
}
|
||||
|
||||
if err := pool.Ping(ctx); err != nil {
|
||||
pool.Close()
|
||||
return nil, fmt.Errorf("unable to ping database: %w", err)
|
||||
}
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package networkmap_pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
const (
|
||||
GetPoliciesQuery = `
|
||||
select p.id, p.public_id, p.enabled, p.source_posture_checks, pr.enabled as rule_enabled, pr.action, pr.protocol, pr.bidirectional,
|
||||
pr.sources, pr.destinations, pr.source_resource, pr.destination_resource, pr.ports, pr.port_ranges,
|
||||
pr.authorized_groups, pr.authorized_user
|
||||
from policies as p
|
||||
left join policy_rules as pr on p.id = pr.policy_id
|
||||
where account_id=$1
|
||||
`
|
||||
)
|
||||
|
||||
func (pgc *PgStoreConn) GetPolicies(ctx context.Context, accountId string) ([]nmdata.Policy, map[string]map[string]any, map[string]map[string]any, error) {
|
||||
rows, err := pgc.Conn.Query(ctx, GetPoliciesQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
policies, err := pgx.CollectRows(rows, pgx.RowToStructByName[networkmapdb.Policy])
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return networkmapdb.ConvertToNmdataPolicy(policies)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package networkmap_pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
const (
|
||||
GetPostureChecksQuery = `
|
||||
select id, public_id, checks
|
||||
from posture_checks
|
||||
where account_id=$1
|
||||
`
|
||||
)
|
||||
|
||||
func (pgc *PgStoreConn) GetPostureChecks(ctx context.Context, accountId string) ([]nmdata.PostureChecks, map[string]string, error) {
|
||||
rows, err := pgc.Conn.Query(ctx, GetPostureChecksQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
checks, err := pgx.CollectRows(rows, pgx.RowToStructByName[networkmapdb.PostureChecks])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
toret := make([]nmdata.PostureChecks, 0, len(checks))
|
||||
idToPublicIDIdx := make(map[string]string)
|
||||
for _, c := range checks {
|
||||
checks := nmdata.PostureChecks{}
|
||||
err := networkmapdb.FromSqlTypesToSharedTypes(reflect.ValueOf(&c), reflect.ValueOf(&checks))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
toret = append(toret, checks)
|
||||
idToPublicIDIdx[checks.ID] = c.PublicID.String
|
||||
}
|
||||
|
||||
return toret, idToPublicIDIdx, nil
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package networkmap_pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
const (
|
||||
GetRoutesQuery = `
|
||||
select id, account_id, public_id, network, domains, keep_route, net_id, description,
|
||||
peer, peer as peer_id, peer_groups, network_type, masquerade, metric, enabled,
|
||||
groups, access_control_groups, skip_auto_apply
|
||||
from routes
|
||||
where account_id=$1
|
||||
`
|
||||
)
|
||||
|
||||
func (pgc *PgStoreConn) GetRoutes(ctx context.Context, accountId string) ([]nmdata.Route, error) {
|
||||
rows, err := pgc.Conn.Query(ctx, GetRoutesQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
routes, err := pgx.CollectRows(rows, pgx.RowToStructByName[networkmapdb.Route])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return networkmapdb.ConvertAllToSharedTypes[networkmapdb.Route, nmdata.Route](routes)
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package networkmap_pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
)
|
||||
|
||||
const (
|
||||
GetServicesQuery = `
|
||||
select enabled, private, array (select json_array_elements_text(access_groups::json)) as access_groups, proxy_cluster, domain
|
||||
from services
|
||||
where account_id=$1
|
||||
`
|
||||
|
||||
GetProxyTargetedDomainResourcesQuery = `
|
||||
select t.target_id
|
||||
from targets as t
|
||||
join services as s on s.id = t.service_id
|
||||
where s.account_id=$1 and s.enabled and not coalesce(s.terminated, false)
|
||||
and t.enabled and t.target_type='domain' and t.target_id is not null
|
||||
`
|
||||
)
|
||||
|
||||
func (pgc *PgStoreConn) GetPrivateServices(ctx context.Context, accountId string) ([]networkmapdb.Service, error) {
|
||||
rows, err := pgc.Conn.Query(ctx, GetServicesQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return pgx.CollectRows(rows, pgx.RowToStructByName[networkmapdb.Service])
|
||||
}
|
||||
|
||||
func (pgc *PgStoreConn) GetProxyTargetedDomainResourceIDs(ctx context.Context, accountId string) (map[string]struct{}, error) {
|
||||
rows, err := pgc.Conn.Query(ctx, GetProxyTargetedDomainResourcesQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ids, err := pgx.CollectRows(rows, pgx.RowTo[string])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
toret := make(map[string]struct{}, len(ids))
|
||||
for _, id := range ids {
|
||||
toret[id] = struct{}{}
|
||||
}
|
||||
return toret, nil
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package networkmap_pgsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
const (
|
||||
GetAllowedUserIdsQuery = `
|
||||
select id, array (select json_array_elements_text(auto_groups::json)) as auto_groups
|
||||
from users
|
||||
where account_id=$1 and not blocked and not is_service_user
|
||||
`
|
||||
|
||||
GetAllGroupIdQuery = `
|
||||
select array_agg(id) from groups
|
||||
where account_id=$1 and name='All'
|
||||
`
|
||||
)
|
||||
|
||||
func (pgc *PgStoreConn) GetAllowedUsers(ctx context.Context, accountId string) (map[string]struct{}, map[string][]string, error) {
|
||||
rows, err := pgc.Conn.Query(ctx, GetAllowedUserIdsQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
users, err := pgx.CollectRows(rows, pgx.RowToStructByName[user])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
rows, err = pgc.Conn.Query(ctx, GetAllGroupIdQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
allGroupIds, err := pgx.CollectOneRow(rows, pgx.RowTo[[]string])
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
userIdIdx := make(map[string]struct{})
|
||||
groupIdToUserIds := make(map[string][]string)
|
||||
for _, user := range users {
|
||||
userIdIdx[user.ID] = struct{}{}
|
||||
for _, groupId := range user.AutoGroups {
|
||||
groupIdToUserIds[groupId] = append(groupIdToUserIds[groupId], user.ID)
|
||||
}
|
||||
for _, allgid := range allGroupIds {
|
||||
groupIdToUserIds[allgid] = append(groupIdToUserIds[allgid], user.ID)
|
||||
}
|
||||
}
|
||||
|
||||
return userIdIdx, groupIdToUserIds, nil
|
||||
}
|
||||
|
||||
type user struct {
|
||||
ID string
|
||||
AutoGroups []string
|
||||
}
|
||||
@@ -0,0 +1,472 @@
|
||||
package networkmapdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator"
|
||||
"github.com/netbirdio/netbird/management/server/settings"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
var ErrDnsUnsupportedRecordType = errors.New("unsupported record type")
|
||||
|
||||
type NetworkMapDBStore interface { //nolint:revive // established name across the codebase
|
||||
BeginTx(ctx context.Context) (NetworkMapDBStoreConn, error)
|
||||
Exec(ctx context.Context, query string, args ...any) error
|
||||
}
|
||||
|
||||
type NetworkMapDBStoreConn interface { //nolint:revive // established name across the codebase
|
||||
GetGroups(ctx context.Context, accountId string) ([]nmdata.Group, map[string]map[string]any, error)
|
||||
GetDomains(ctx context.Context, accountId string) ([]Domain, error)
|
||||
GetPeers(ctx context.Context, accountId string) ([]nmdata.Peer, map[string][]*nmdata.Peer, error)
|
||||
GetPolicies(ctx context.Context, accountId string) ([]nmdata.Policy, map[string]map[string]any, map[string]map[string]any, error)
|
||||
GetRoutes(ctx context.Context, accountId string) ([]nmdata.Route, error)
|
||||
GetNameServerGroups(ctx context.Context, accountId string) ([]nmdata.NameServerGroup, error)
|
||||
GetNetworkResources(ctx context.Context, accountId string) ([]nmdata.NetworkResource, error)
|
||||
GetNetworkRouters(ctx context.Context, accountId string) (map[string]map[string]*nmdata.NetworkRouter, error)
|
||||
GetNetwork(ctx context.Context, accountId string) (nmdata.Network, error)
|
||||
GetAppliedZoneCandidates(ctx context.Context, accountId string) ([]networkmap.AppliedZoneCandidate, error)
|
||||
GetAccountSettings(ctx context.Context, accountId string) (nmdata.AccountSettingsInfo, error)
|
||||
GetPostureChecks(ctx context.Context, accountId string) ([]nmdata.PostureChecks, map[string]string, error)
|
||||
GetAllowedUsers(ctx context.Context, accountId string) (map[string]struct{}, map[string][]string, error)
|
||||
GetDnsSettings(ctx context.Context, accountId string) (nmdata.DNSSettings, error)
|
||||
GetNetworkXIDToPublicIdMap(ctx context.Context, accountId string) (map[string]string, error)
|
||||
GetPrivateServices(ctx context.Context, accountId string) ([]Service, error)
|
||||
GetProxyTargetedDomainResourceIDs(ctx context.Context, accountId string) (map[string]struct{}, error)
|
||||
|
||||
CommitTx(ctx context.Context) error
|
||||
RollbackTx(ctx context.Context) error
|
||||
}
|
||||
|
||||
type NetworkMapDBStoreImpl struct { //nolint:revive // established name across the codebase
|
||||
Store NetworkMapDBStore
|
||||
IntegratedPeerValidator integrated_validator.IntegratedValidator
|
||||
ExtraSettingsManager settings.Manager
|
||||
}
|
||||
|
||||
// The order of fields in these structs is important.
|
||||
// Mapping of results of sqlite queries relies on the order
|
||||
// of the fields in these structs, when a query or a struct changes,
|
||||
// corresponding changes must be made to its counterpart.
|
||||
|
||||
type Account struct {
|
||||
PeerLoginExpirationEnabled sql.NullBool
|
||||
PeerLoginExpiration sql.NullInt64
|
||||
PeerInactivityExpirationEnabled sql.NullBool
|
||||
PeerInactivityExpiration sql.NullInt64
|
||||
DNSDomain sql.NullString
|
||||
IPv6EnabledGroups []byte `nmap:"json"`
|
||||
RoutingPeerDNSResolutionEnabled sql.NullBool
|
||||
LazyConnectionEnabled sql.NullBool
|
||||
AutoUpdateVersion sql.NullString
|
||||
AutoUpdateAlways sql.NullBool
|
||||
MetricsPushEnabled sql.NullBool
|
||||
}
|
||||
|
||||
type Domain struct {
|
||||
Domain sql.NullString
|
||||
TargetCluster sql.NullString
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
Enabled sql.NullBool
|
||||
Private sql.NullBool
|
||||
AccessGroups []string
|
||||
ProxyCluster sql.NullString
|
||||
Domain sql.NullString
|
||||
}
|
||||
|
||||
type Zone struct {
|
||||
Id string `nmap:"skip"`
|
||||
Domain sql.NullString
|
||||
SearchDomainDisabled sql.NullBool
|
||||
DistributionGroups []byte `nmap:"skip,json"`
|
||||
RecordName sql.NullString `nmap:"skip"`
|
||||
RecordType sql.NullString `nmap:"skip"`
|
||||
RecordClass sql.NullString `nmap:"skip"`
|
||||
RecordTTL sql.NullInt64 `nmap:"skip"`
|
||||
RecordRData sql.NullString `nmap:"skip"`
|
||||
}
|
||||
|
||||
type NameserverGroup struct {
|
||||
ID string
|
||||
PublicID sql.NullString
|
||||
Name sql.NullString
|
||||
Description sql.NullString
|
||||
NameServers []byte `nmap:"json"`
|
||||
Groups []byte `nmap:"json"`
|
||||
Primary sql.NullBool
|
||||
Domains []byte `nmap:"json"`
|
||||
Enabled sql.NullBool
|
||||
SearchDomainsEnabled sql.NullBool
|
||||
}
|
||||
|
||||
type Networkresource struct {
|
||||
ID string
|
||||
NetworkID sql.NullString
|
||||
AccountID sql.NullString
|
||||
PublicID sql.NullString
|
||||
Name sql.NullString
|
||||
Description sql.NullString
|
||||
Type sql.NullString
|
||||
Domain sql.NullString
|
||||
Prefix []byte `nmap:"json"`
|
||||
Enabled sql.NullBool
|
||||
}
|
||||
|
||||
type AccountNetwork struct {
|
||||
Identifier sql.NullString
|
||||
Net []byte `nmap:"json"`
|
||||
NetV6 []byte `nmap:"json"`
|
||||
Dns sql.NullString
|
||||
Serial sql.NullInt64
|
||||
}
|
||||
|
||||
type Network struct {
|
||||
ID string
|
||||
PublicID sql.NullString
|
||||
}
|
||||
|
||||
type Policy struct {
|
||||
ID string
|
||||
PublicID sql.NullString
|
||||
Enabled sql.NullBool
|
||||
SourcePostureChecks []byte `nmap:"json"`
|
||||
RuleEnabled sql.NullBool `nmap:"skip"`
|
||||
Action sql.NullString `nmap:"skip"`
|
||||
Protocol sql.NullString `nmap:"skip"`
|
||||
Bidirectional sql.NullBool `nmap:"skip"`
|
||||
Sources []byte `nmap:"skip,json"`
|
||||
Destinations []byte `nmap:"skip,json"`
|
||||
SourceResource []byte `nmap:"skip,json"`
|
||||
DestinationResource []byte `nmap:"skip,json"`
|
||||
Ports []byte `nmap:"skip,json"`
|
||||
PortRanges []byte `nmap:"skip,json"`
|
||||
AuthorizedGroups []byte `nmap:"skip,json"`
|
||||
AuthorizedUser sql.NullString `nmap:"skip"`
|
||||
}
|
||||
|
||||
// Depending on db interface LastLogin contains time in different formats:
|
||||
// for sqlite/sql.NullTime the time in UTC
|
||||
// for pgx the time is in the local timezone
|
||||
// TODO add support for creating struct fields from denormalized fields
|
||||
type Peer struct {
|
||||
ID string
|
||||
Key sql.NullString
|
||||
SSHKey sql.NullString
|
||||
DNSLabel sql.NullString
|
||||
ExtraDNSLabels []byte `nmap:"json"`
|
||||
UserID sql.NullString
|
||||
SSHEnabled sql.NullBool
|
||||
LoginExpirationEnabled sql.NullBool
|
||||
LastLogin sql.NullTime
|
||||
IP []byte `nmap:"json"`
|
||||
IPv6 []byte `nmap:"json"`
|
||||
PeerStatusRequiresApproval sql.NullBool `nmap:"map_to:RequiresApproval"`
|
||||
PeerStatusConnected sql.NullBool `nmap:"skip"`
|
||||
ProxyMetaEmbedded sql.NullBool `nmap:"skip"`
|
||||
ProxyMetaCluster sql.NullString `nmap:"skip"`
|
||||
MetaWtVersion sql.NullString `nmap:"skip"`
|
||||
MetaGoOS sql.NullString `nmap:"skip"`
|
||||
MetaOSVersion sql.NullString `nmap:"skip"`
|
||||
MetaKernelVersion sql.NullString `nmap:"skip"`
|
||||
MetaNetworkAddresses []byte `nmap:"skip,json"`
|
||||
MetaFiles []byte `nmap:"skip,json"`
|
||||
MetaCapabilities []byte `nmap:"skip,json"`
|
||||
MetaFlags []byte `nmap:"skip,json"`
|
||||
MetaSyncMessageVersion sql.NullInt64 `nmap:"skip"`
|
||||
LocationCountryCode sql.NullString `nmap:"skip"`
|
||||
LocationCityName sql.NullString `nmap:"skip"`
|
||||
LocationConnectionIp []byte `nmap:"skip,json"`
|
||||
}
|
||||
|
||||
type PostureChecks struct {
|
||||
ID string
|
||||
PublicID sql.NullString `nmap:"skip"`
|
||||
Checks []byte `nmap:"json"`
|
||||
}
|
||||
|
||||
type Route struct {
|
||||
ID string
|
||||
AccountID sql.NullString
|
||||
PublicID sql.NullString
|
||||
Network []byte `nmap:"json"`
|
||||
Domains []byte `nmap:"json"`
|
||||
KeepRoute sql.NullBool
|
||||
NetID sql.NullString
|
||||
Description sql.NullString
|
||||
Peer sql.NullString
|
||||
PeerID sql.NullString
|
||||
PeerGroups []byte `nmap:"json"`
|
||||
NetworkType sql.NullInt64
|
||||
Masquerade sql.NullBool
|
||||
Metric sql.NullInt64
|
||||
Enabled sql.NullBool
|
||||
Groups []byte `nmap:"json"`
|
||||
AccessControlGroups []byte `nmap:"json"`
|
||||
SkipAutoApply sql.NullBool
|
||||
}
|
||||
|
||||
func RecordTypeAndRdata(t, rdata string) (int, string, error) {
|
||||
switch t {
|
||||
case "A":
|
||||
return int(dns.TypeA), rdata, nil
|
||||
case "AAAA":
|
||||
return int(dns.TypeAAAA), rdata, nil
|
||||
case "CNAME":
|
||||
return int(dns.TypeCNAME), dns.Fqdn(rdata), nil
|
||||
default:
|
||||
return 0, "", fmt.Errorf("record type: %s %w", t, ErrDnsUnsupportedRecordType)
|
||||
}
|
||||
}
|
||||
|
||||
func ZonesToAppliedZoneCandidates(zones []Zone) ([]networkmap.AppliedZoneCandidate, error) {
|
||||
toret := make([]networkmap.AppliedZoneCandidate, 0, len(zones))
|
||||
currentZoneId := ""
|
||||
for _, z := range zones {
|
||||
if !z.RecordType.Valid {
|
||||
continue
|
||||
}
|
||||
|
||||
zone := nmdata.CustomZone{}
|
||||
err := FromSqlTypesToSharedTypes(
|
||||
reflect.ValueOf(&z), reflect.ValueOf(&zone))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var distributionGroups []string
|
||||
if err := json.Unmarshal(z.DistributionGroups, &distributionGroups); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if z.Id != currentZoneId {
|
||||
// The account-side builder (types.buildAppliedZoneCandidates) states
|
||||
// the shape of an applied zone: names fully qualified, served
|
||||
// non-authoritatively. Both builders feed the same client-facing map,
|
||||
// so this one has to produce the same value.
|
||||
zone.Domain = dns.Fqdn(zone.Domain)
|
||||
zone.NonAuthoritative = true
|
||||
zone.Records = []nmdata.SimpleRecord{}
|
||||
toret = append(toret, AppliedZoneCandidateFromZone(zone, distributionGroups))
|
||||
currentZoneId = z.Id
|
||||
}
|
||||
|
||||
rtype, rdata, err := RecordTypeAndRdata(z.RecordType.String, z.RecordRData.String)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrDnsUnsupportedRecordType) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lastZone := &toret[len(toret)-1]
|
||||
lastZone.Zone.Records = append(lastZone.Zone.Records, nmdata.SimpleRecord{
|
||||
Name: dns.Fqdn(z.RecordName.String),
|
||||
Class: z.RecordClass.String,
|
||||
TTL: int(z.RecordTTL.Int64),
|
||||
RData: rdata,
|
||||
Type: rtype,
|
||||
})
|
||||
}
|
||||
return toret, nil
|
||||
}
|
||||
|
||||
func AppliedZoneCandidateFromZone(z nmdata.CustomZone, distributionGroups []string) networkmap.AppliedZoneCandidate {
|
||||
return networkmap.AppliedZoneCandidate{
|
||||
DistributionGroups: distributionGroups,
|
||||
Zone: z,
|
||||
}
|
||||
}
|
||||
|
||||
func ConvertToNmdataPeers(peers []Peer) ([]nmdata.Peer, map[string][]*nmdata.Peer, error) {
|
||||
toret := make([]nmdata.Peer, 0, len(peers))
|
||||
clusterToPeerIdx := make(map[string][]*nmdata.Peer)
|
||||
for _, p := range peers {
|
||||
dp := nmdata.Peer{}
|
||||
err := FromSqlTypesToSharedTypes(
|
||||
reflect.ValueOf(&p), reflect.ValueOf(&dp))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if p.ProxyMetaEmbedded.Valid {
|
||||
dp.ProxyMeta.Embedded = p.ProxyMetaEmbedded.Bool
|
||||
}
|
||||
dp.ProxyMeta.Cluster = p.ProxyMetaCluster.String
|
||||
// This is only used to build private service candidates, not connected peers are skipped
|
||||
if dp.ProxyMeta.Embedded && p.PeerStatusConnected.Bool {
|
||||
clusterToPeerIdx[p.ProxyMetaCluster.String] = append(clusterToPeerIdx[p.ProxyMetaCluster.String], &dp)
|
||||
}
|
||||
if p.MetaWtVersion.Valid {
|
||||
dp.Meta.WtVersion = p.MetaWtVersion.String
|
||||
}
|
||||
if p.MetaSyncMessageVersion.Valid {
|
||||
dp.Meta.SyncMessageVersion = int(p.MetaSyncMessageVersion.Int64)
|
||||
}
|
||||
if p.MetaGoOS.Valid {
|
||||
dp.Meta.GoOS = p.MetaGoOS.String
|
||||
}
|
||||
if p.MetaOSVersion.Valid {
|
||||
dp.Meta.OSVersion = p.MetaOSVersion.String
|
||||
}
|
||||
if p.MetaKernelVersion.Valid {
|
||||
dp.Meta.KernelVersion = p.MetaKernelVersion.String
|
||||
}
|
||||
if p.LocationCountryCode.Valid {
|
||||
dp.Location.CountryCode = p.LocationCountryCode.String
|
||||
}
|
||||
if p.LocationCityName.Valid {
|
||||
dp.Location.CityName = p.LocationCityName.String
|
||||
}
|
||||
if p.LocationConnectionIp != nil {
|
||||
err := json.Unmarshal(p.LocationConnectionIp, &dp.Location.ConnectionIP)
|
||||
if err != nil {
|
||||
return toret, nil, err
|
||||
}
|
||||
}
|
||||
if p.MetaFiles != nil {
|
||||
err := json.Unmarshal(p.MetaFiles, &dp.Meta.Files)
|
||||
if err != nil {
|
||||
return toret, nil, err
|
||||
}
|
||||
}
|
||||
if p.MetaCapabilities != nil {
|
||||
err := json.Unmarshal(p.MetaCapabilities, &dp.Meta.Capabilities)
|
||||
if err != nil {
|
||||
return toret, nil, err
|
||||
}
|
||||
}
|
||||
if p.MetaFlags != nil {
|
||||
err := json.Unmarshal(p.MetaFlags, &dp.Meta.Flags)
|
||||
if err != nil {
|
||||
return toret, nil, err
|
||||
}
|
||||
}
|
||||
if p.MetaNetworkAddresses != nil {
|
||||
err := json.Unmarshal(p.MetaNetworkAddresses, &dp.Meta.NetworkAddresses)
|
||||
if err != nil {
|
||||
return toret, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
toret = append(toret, dp)
|
||||
}
|
||||
|
||||
return toret, clusterToPeerIdx, nil
|
||||
}
|
||||
|
||||
func ConvertToNmdataPolicy(policies []Policy) ([]nmdata.Policy, map[string]map[string]any, map[string]map[string]any, error) {
|
||||
toret := make([]nmdata.Policy, 0, len(policies))
|
||||
policyToDestinationResourceIdx := make(map[string]map[string]any) // policy id to destination resource id
|
||||
policyToDestinationGroupIdx := make(map[string]map[string]any) // policy id to destination group id
|
||||
for _, p := range policies {
|
||||
policy := nmdata.Policy{}
|
||||
err := FromSqlTypesToSharedTypes(
|
||||
reflect.ValueOf(&p), reflect.ValueOf(&policy))
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
var policyRule *nmdata.PolicyRule
|
||||
pr := func() *nmdata.PolicyRule {
|
||||
if policyRule != nil {
|
||||
return policyRule
|
||||
}
|
||||
|
||||
policyRule = &nmdata.PolicyRule{}
|
||||
return policyRule
|
||||
}
|
||||
|
||||
if p.RuleEnabled.Valid {
|
||||
pr().Enabled = p.RuleEnabled.Bool
|
||||
}
|
||||
if p.Action.Valid {
|
||||
pr().Action = p.Action.String
|
||||
}
|
||||
if p.Protocol.Valid {
|
||||
pr().Protocol = p.Protocol.String
|
||||
}
|
||||
if p.Bidirectional.Valid {
|
||||
pr().Bidirectional = p.Bidirectional.Bool
|
||||
}
|
||||
if len(p.Sources) > 0 {
|
||||
err := json.Unmarshal([]byte(p.Sources), &pr().Sources)
|
||||
if err != nil {
|
||||
return toret, nil, nil, err
|
||||
}
|
||||
}
|
||||
if len(p.Destinations) > 0 {
|
||||
err := json.Unmarshal([]byte(p.Destinations), &pr().Destinations)
|
||||
if err != nil {
|
||||
return toret, nil, nil, err
|
||||
}
|
||||
|
||||
if p.RuleEnabled.Valid && p.RuleEnabled.Bool {
|
||||
for _, dst := range pr().Destinations {
|
||||
if _, ok := policyToDestinationGroupIdx[p.ID]; !ok {
|
||||
policyToDestinationGroupIdx[p.ID] = make(map[string]any)
|
||||
}
|
||||
policyToDestinationGroupIdx[p.ID][dst] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(p.SourceResource) > 0 {
|
||||
err := json.Unmarshal([]byte(p.SourceResource), &pr().SourceResource)
|
||||
if err != nil {
|
||||
return toret, nil, nil, err
|
||||
}
|
||||
}
|
||||
if len(p.DestinationResource) > 0 {
|
||||
err := json.Unmarshal([]byte(p.DestinationResource), &pr().DestinationResource)
|
||||
if err != nil {
|
||||
return toret, nil, nil, err
|
||||
}
|
||||
|
||||
if p.RuleEnabled.Valid && p.RuleEnabled.Bool {
|
||||
if _, ok := policyToDestinationResourceIdx[p.ID]; !ok {
|
||||
policyToDestinationResourceIdx[p.ID] = make(map[string]any)
|
||||
}
|
||||
policyToDestinationResourceIdx[p.ID][pr().DestinationResource.ID] = struct{}{}
|
||||
}
|
||||
}
|
||||
if len(p.Ports) > 0 {
|
||||
err := json.Unmarshal([]byte(p.Ports), &pr().Ports)
|
||||
if err != nil {
|
||||
return toret, nil, nil, err
|
||||
}
|
||||
}
|
||||
if len(p.PortRanges) > 0 {
|
||||
err := json.Unmarshal([]byte(p.PortRanges), &pr().PortRanges)
|
||||
if err != nil {
|
||||
return toret, nil, nil, err
|
||||
}
|
||||
}
|
||||
if len(p.AuthorizedGroups) > 0 {
|
||||
err := json.Unmarshal([]byte(p.AuthorizedGroups), &pr().AuthorizedGroups)
|
||||
if err != nil {
|
||||
return toret, nil, nil, err
|
||||
}
|
||||
}
|
||||
if p.AuthorizedUser.Valid {
|
||||
pr().AuthorizedUser = p.AuthorizedUser.String
|
||||
}
|
||||
|
||||
if policyRule != nil {
|
||||
policyRule.ID = p.ID
|
||||
policyRule.PolicyID = p.ID
|
||||
policy.Rules = []*nmdata.PolicyRule{policyRule}
|
||||
}
|
||||
|
||||
toret = append(toret, policy)
|
||||
}
|
||||
|
||||
return toret, policyToDestinationResourceIdx, policyToDestinationGroupIdx, nil
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package networkmapdb
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRecordTypeAndRdata(t *testing.T) {
|
||||
var tests = []struct {
|
||||
recordType string
|
||||
expectedRecordType int
|
||||
rdata string
|
||||
expectedRdata string
|
||||
expectedErr error
|
||||
}{
|
||||
{recordType: "A", expectedRecordType: 1, rdata: "test.com", expectedRdata: "test.com", expectedErr: nil},
|
||||
{recordType: "AAAA", expectedRecordType: 28, rdata: "test.com", expectedRdata: "test.com", expectedErr: nil},
|
||||
{recordType: "CNAME", expectedRecordType: 5, rdata: "test.com", expectedRdata: "test.com.", expectedErr: nil},
|
||||
{recordType: "CNAME", expectedRecordType: 5, rdata: "test.com.", expectedRdata: "test.com.", expectedErr: nil},
|
||||
{recordType: "TypeMX", expectedErr: ErrDnsUnsupportedRecordType},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.recordType, func(t *testing.T) {
|
||||
recordType, rdata, err := RecordTypeAndRdata(tt.recordType, tt.rdata)
|
||||
|
||||
if tt.expectedErr != nil {
|
||||
assert.ErrorIs(t, err, ErrDnsUnsupportedRecordType)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, recordType, tt.expectedRecordType)
|
||||
assert.Equal(t, rdata, tt.expectedRdata)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
package networkmapdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNullStringSupport(t *testing.T) {
|
||||
src := withNullString{Name: sql.NullString{String: "string", Valid: true}}
|
||||
dst := withString{}
|
||||
assert.NoError(t, FromSqlTypesToSharedTypes(reflect.ValueOf(&src), reflect.ValueOf(&dst)))
|
||||
assert.Equal(t, withString{Name: "string"}, dst)
|
||||
|
||||
src = withNullString{Name: sql.NullString{Valid: false}}
|
||||
dst = withString{}
|
||||
assert.NoError(t, FromSqlTypesToSharedTypes(reflect.ValueOf(&src), reflect.ValueOf(&dst)))
|
||||
assert.Equal(t, withString{Name: ""}, dst)
|
||||
}
|
||||
|
||||
func TestNullBoolSupport(t *testing.T) {
|
||||
src := withNullBool{TrueOrFalse: sql.NullBool{Bool: true, Valid: true}}
|
||||
dst := withBool{}
|
||||
assert.NoError(t, FromSqlTypesToSharedTypes(reflect.ValueOf(&src), reflect.ValueOf(&dst)))
|
||||
assert.Equal(t, withBool{TrueOrFalse: true}, dst)
|
||||
|
||||
}
|
||||
|
||||
func TestRawJsonSupport(t *testing.T) {
|
||||
jb, _ := json.Marshal(embeddedS{Name: "blob-name", SomeField: 1})
|
||||
src := withRawJson{Blob: json.RawMessage(jb)}
|
||||
dst := fromJson{}
|
||||
assert.NoError(t, FromSqlTypesToSharedTypes(reflect.ValueOf(&src), reflect.ValueOf(&dst)))
|
||||
assert.Equal(t, fromJson{Blob: embeddedS{Name: "blob-name", SomeField: 1}}, dst)
|
||||
|
||||
src1 := withRawJson{}
|
||||
dst1 := fromJson{}
|
||||
assert.NoError(t, FromSqlTypesToSharedTypes(reflect.ValueOf(&src1), reflect.ValueOf(&dst1)))
|
||||
assert.Equal(t, fromJson{}, dst1)
|
||||
}
|
||||
|
||||
func TestShouldSkipTag(t *testing.T) {
|
||||
src5 := withSkipTag{Field: "shouldskip"}
|
||||
dst5 := emptySkipTagTarget{}
|
||||
assert.NoError(t, FromSqlTypesToSharedTypes(reflect.ValueOf(&src5), reflect.ValueOf(&dst5)))
|
||||
assert.Equal(t, emptySkipTagTarget{}, dst5)
|
||||
|
||||
}
|
||||
|
||||
func TestMapToTag(t *testing.T) {
|
||||
src6 := withMapToTag{Field: "fieldvalue"}
|
||||
dst6 := mapToTagTarget{}
|
||||
assert.NoError(t, FromSqlTypesToSharedTypes(reflect.ValueOf(&src6), reflect.ValueOf(&dst6)))
|
||||
assert.Equal(t, mapToTagTarget{AnotherField: "fieldvalue"}, dst6)
|
||||
}
|
||||
|
||||
func TestNullableInt64Support(t *testing.T) {
|
||||
src := withInt64{Field: sql.NullInt64{Int64: int64(1), Valid: true}}
|
||||
dst := int64Target{}
|
||||
assert.NoError(t, FromSqlTypesToSharedTypes(reflect.ValueOf(&src), reflect.ValueOf(&dst)))
|
||||
assert.Equal(t, int64Target{Field: 1}, dst)
|
||||
}
|
||||
|
||||
func TestNullableTimeSupport(t *testing.T) {
|
||||
now := time.Now()
|
||||
src := withNullableTime{Field: sql.NullTime{Time: now, Valid: true}}
|
||||
dst := nullableTimeTarget{}
|
||||
assert.NoError(t, FromSqlTypesToSharedTypes(reflect.ValueOf(&src), reflect.ValueOf(&dst)))
|
||||
assert.Equal(t, nullableTimeTarget{Field: now}, dst)
|
||||
}
|
||||
|
||||
func TestNullableTimePointerSupport(t *testing.T) {
|
||||
now := time.Now()
|
||||
src := withNullableTime{Field: sql.NullTime{Time: now, Valid: true}}
|
||||
dst := nullableTimePointerTarget{}
|
||||
assert.NoError(t, FromSqlTypesToSharedTypes(reflect.ValueOf(&src), reflect.ValueOf(&dst)))
|
||||
assert.Equal(t, nullableTimePointerTarget{Field: &now}, dst)
|
||||
}
|
||||
|
||||
func TestStringSLiceSupport(t *testing.T) {
|
||||
src := withStringSlice{Field: []string{"one"}}
|
||||
dst := withStringSlice{}
|
||||
assert.NoError(t, FromSqlTypesToSharedTypes(reflect.ValueOf(&src), reflect.ValueOf(&dst)))
|
||||
assert.Equal(t, withStringSlice{Field: []string{"one"}}, dst)
|
||||
}
|
||||
|
||||
func TestNullStringSLiceSupport(t *testing.T) {
|
||||
src := withStringSlice{}
|
||||
dst := withStringSlice{}
|
||||
assert.NoError(t, FromSqlTypesToSharedTypes(reflect.ValueOf(&src), reflect.ValueOf(&dst)))
|
||||
assert.Equal(t, withStringSlice{}, dst)
|
||||
}
|
||||
|
||||
func TestWithMultipleFields(t *testing.T) {
|
||||
now := time.Now()
|
||||
src := withMultipleFields{
|
||||
Field1: sql.NullString{String: "aaa", Valid: true},
|
||||
Field2: sql.NullBool{Bool: true, Valid: true},
|
||||
Field3: sql.NullTime{Time: now, Valid: true},
|
||||
Field4: sql.NullInt64{Int64: 1, Valid: true},
|
||||
Field5: "another",
|
||||
}
|
||||
dst := multipleFieldsTarget{}
|
||||
assert.NoError(t, FromSqlTypesToSharedTypes(reflect.ValueOf(&src), reflect.ValueOf(&dst)))
|
||||
assert.Equal(t, multipleFieldsTarget{
|
||||
Field1: "aaa",
|
||||
Field2: true,
|
||||
Field3: now,
|
||||
Field4: 1,
|
||||
Field5: "another",
|
||||
}, dst)
|
||||
}
|
||||
|
||||
func TestEmptyPublicIdsFilled(t *testing.T) {
|
||||
src := withEmptyPublicIds{}
|
||||
dst := emptyPublicIdTarget{}
|
||||
assert.NoError(t, FromSqlTypesToSharedTypes(reflect.ValueOf(&src), reflect.ValueOf(&dst)))
|
||||
assert.NotEmpty(t, dst.PublicID)
|
||||
assert.NotEmpty(t, dst.PublicId)
|
||||
}
|
||||
|
||||
// only []byte and []uint8 slices with "json" tag are being parsed
|
||||
func TestByteSliceSupport(t *testing.T) {
|
||||
src := withByteSlice{
|
||||
Field: []byte("[\"one\",\"two\",\"three\"]"),
|
||||
}
|
||||
dst := byteSliceTarget{}
|
||||
assert.NoError(t, FromSqlTypesToSharedTypes(reflect.ValueOf(&src), reflect.ValueOf(&dst)))
|
||||
assert.Equal(t, []string{"one", "two", "three"}, dst.Field)
|
||||
}
|
||||
|
||||
func TestUint8SliceSupport(t *testing.T) {
|
||||
src := withUint8Slice{
|
||||
Field: []uint8("[\"one\",\"two\",\"three\"]"),
|
||||
}
|
||||
dst := uint8SliceTarget{}
|
||||
assert.NoError(t, FromSqlTypesToSharedTypes(reflect.ValueOf(&src), reflect.ValueOf(&dst)))
|
||||
assert.Equal(t, []string{"one", "two", "three"}, dst.Field)
|
||||
}
|
||||
|
||||
type withNullString struct {
|
||||
Name sql.NullString
|
||||
}
|
||||
|
||||
type withString struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
type withMultipleFields struct {
|
||||
Field1 sql.NullString
|
||||
Field2 sql.NullBool
|
||||
Field3 sql.NullTime
|
||||
Field4 sql.NullInt64
|
||||
Field5 string
|
||||
}
|
||||
|
||||
type multipleFieldsTarget struct {
|
||||
Field1 string
|
||||
Field2 bool
|
||||
Field3 time.Time
|
||||
Field4 int64
|
||||
Field5 string
|
||||
}
|
||||
|
||||
type withNullBool struct {
|
||||
TrueOrFalse sql.NullBool
|
||||
}
|
||||
|
||||
type withBool struct {
|
||||
TrueOrFalse bool
|
||||
}
|
||||
|
||||
type withRawJson struct {
|
||||
Blob json.RawMessage
|
||||
}
|
||||
|
||||
type embeddedS struct {
|
||||
Name string
|
||||
SomeField int
|
||||
}
|
||||
type fromJson struct {
|
||||
Blob embeddedS
|
||||
}
|
||||
|
||||
type withSkipTag struct {
|
||||
Field string `nmap:"skip"`
|
||||
}
|
||||
|
||||
type emptySkipTagTarget struct {
|
||||
Field string
|
||||
}
|
||||
|
||||
type withMapToTag struct {
|
||||
Field string `nmap:"map_to:AnotherField"`
|
||||
}
|
||||
|
||||
type mapToTagTarget struct {
|
||||
AnotherField string
|
||||
}
|
||||
|
||||
type withInt64 struct {
|
||||
Field sql.NullInt64
|
||||
}
|
||||
|
||||
type int64Target struct {
|
||||
Field int
|
||||
}
|
||||
|
||||
type withNullableTime struct {
|
||||
Field sql.NullTime
|
||||
}
|
||||
|
||||
type nullableTimeTarget struct {
|
||||
Field time.Time
|
||||
}
|
||||
|
||||
type nullableTimePointerTarget struct {
|
||||
Field *time.Time
|
||||
}
|
||||
|
||||
type withStringSlice struct {
|
||||
Field []string
|
||||
}
|
||||
|
||||
type withEmptyPublicIds struct {
|
||||
PublicID sql.NullString
|
||||
PublicId sql.NullString
|
||||
}
|
||||
|
||||
type emptyPublicIdTarget struct {
|
||||
PublicID string
|
||||
PublicId string
|
||||
}
|
||||
|
||||
type withByteSlice struct {
|
||||
Field []byte `nmap:"json"`
|
||||
}
|
||||
|
||||
type byteSliceTarget struct {
|
||||
Field []string
|
||||
}
|
||||
|
||||
type withUint8Slice struct {
|
||||
Field []byte `nmap:"json"`
|
||||
}
|
||||
|
||||
type uint8SliceTarget struct {
|
||||
Field []string
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package networkmap_sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
const (
|
||||
GetAccountSettingsQuery = `
|
||||
select settings_peer_login_expiration_enabled as peer_login_expiration_enabled,
|
||||
settings_peer_login_expiration as peer_login_expiration,
|
||||
settings_peer_inactivity_expiration_enabled as peer_inactivity_expiration_enabled,
|
||||
settings_peer_inactivity_expiration as peer_inactivity_expiration,
|
||||
settings_dns_domain as dns_domain,
|
||||
settings_ipv6_enabled_groups as ipv6_enabled_groups,
|
||||
settings_routing_peer_dns_resolution_enabled as routing_peer_dns_resolution_enabled,
|
||||
settings_lazy_connection_enabled as lazy_connection_enabled,
|
||||
settings_auto_update_version as auto_update_version,
|
||||
settings_auto_update_always as auto_update_always,
|
||||
settings_metrics_push_enabled as metrics_push_enabled
|
||||
from accounts
|
||||
where id=?
|
||||
`
|
||||
)
|
||||
|
||||
func (sc *SqliteStoreConn) GetAccountSettings(ctx context.Context, accountId string) (nmdata.AccountSettingsInfo, error) {
|
||||
rows, err := sc.Conn.QueryContext(ctx, GetAccountSettingsQuery, accountId)
|
||||
if err != nil {
|
||||
return nmdata.AccountSettingsInfo{}, err
|
||||
}
|
||||
|
||||
a, err := CollectOneRowForSqlite[networkmapdb.Account](rows)
|
||||
if err != nil {
|
||||
return nmdata.AccountSettingsInfo{}, err
|
||||
}
|
||||
|
||||
settingsInfo := nmdata.AccountSettingsInfo{}
|
||||
err = networkmapdb.FromSqlTypesToSharedTypes(reflect.ValueOf(&a), reflect.ValueOf(&settingsInfo))
|
||||
if err != nil {
|
||||
return nmdata.AccountSettingsInfo{}, err
|
||||
}
|
||||
|
||||
return settingsInfo, nil
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package networkmap_sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap"
|
||||
)
|
||||
|
||||
const (
|
||||
GetAccountZonesQuery = `
|
||||
select zones.id as id, domain, not enable_search_domain as search_domain_disabled, distribution_groups,
|
||||
r.name as record_name, r.type as record_type, 'IN' record_class, r.ttl as record_ttl, r.content as record_rdata
|
||||
from zones
|
||||
left join records as r on r.zone_id = zones.id
|
||||
where zones.account_id=? and zones.enabled
|
||||
`
|
||||
)
|
||||
|
||||
func (sc *SqliteStoreConn) GetAppliedZoneCandidates(ctx context.Context, accountId string) ([]networkmap.AppliedZoneCandidate, error) {
|
||||
rows, err := sc.Conn.QueryContext(ctx, GetAccountZonesQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zones, err := CollectRowsForSqlite[networkmapdb.Zone](rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return networkmapdb.ZonesToAppliedZoneCandidates(zones)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package networkmap_sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
const (
|
||||
GetDnsSettingsQuery = `
|
||||
select dns_settings_disabled_management_groups
|
||||
from accounts
|
||||
where id=?
|
||||
`
|
||||
)
|
||||
|
||||
func (sc *SqliteStoreConn) GetDnsSettings(ctx context.Context, accountId string) (nmdata.DNSSettings, error) {
|
||||
rows, err := sc.Conn.QueryContext(ctx, GetDnsSettingsQuery, accountId)
|
||||
if err != nil {
|
||||
return nmdata.DNSSettings{}, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var value nmdata.DNSSettings
|
||||
var settings []byte
|
||||
|
||||
rows.Next()
|
||||
if err := rows.Scan(&settings); err != nil {
|
||||
return value, err
|
||||
}
|
||||
|
||||
if settings == nil {
|
||||
return nmdata.DNSSettings{}, nil
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(settings, &value.DisabledManagementGroups); err != nil {
|
||||
return value, err
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package networkmap_sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
)
|
||||
|
||||
const (
|
||||
GetDomainsQuery = `
|
||||
select domain, target_cluster
|
||||
from domains
|
||||
where account_id=? and domain<>'' and target_cluster<>''
|
||||
`
|
||||
)
|
||||
|
||||
func (sc *SqliteStoreConn) GetDomains(ctx context.Context, accountId string) ([]networkmapdb.Domain, error) {
|
||||
rows, err := sc.Conn.QueryContext(ctx, GetDomainsQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return CollectRowsForSqlite[networkmapdb.Domain](rows)
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package networkmap_sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"reflect"
|
||||
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
const (
|
||||
GetGroupsQuery = `
|
||||
select groups.id, groups.name, groups.public_id, groups.resources, gp.peer_id
|
||||
from groups
|
||||
left join group_peers gp on gp.group_id=groups.id and gp.account_id=?
|
||||
where groups.account_id=?
|
||||
`
|
||||
)
|
||||
|
||||
// we also return a resource-to-group index.
|
||||
// an alternative is to add json indexes, query this directly. Not sure how expensive
|
||||
// json indexes are. TODO (dmitri) verify and maybe change the implementation here.
|
||||
func (sc *SqliteStoreConn) GetGroups(ctx context.Context, accountId string) ([]nmdata.Group, map[string]map[string]any, error) {
|
||||
rows, err := sc.Conn.QueryContext(ctx, GetGroupsQuery, accountId, accountId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
groups, err := CollectRowsForSqlite[group](rows)
|
||||
|
||||
toret := make([]nmdata.Group, 0, len(groups))
|
||||
resourceToGroupIdx := make(map[string]map[string]any)
|
||||
|
||||
for _, g := range groups {
|
||||
if len(toret) > 0 && toret[len(toret)-1].ID == g.ID && g.PeerID.Valid {
|
||||
toret[len(toret)-1].Peers = append(toret[len(toret)-1].Peers, g.PeerID.String)
|
||||
continue
|
||||
}
|
||||
|
||||
dg := nmdata.Group{}
|
||||
err := networkmapdb.FromSqlTypesToSharedTypes(
|
||||
reflect.ValueOf(&g), reflect.ValueOf(&dg))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if g.PeerID.Valid {
|
||||
dg.Peers = append(dg.Peers, g.PeerID.String)
|
||||
}
|
||||
toret = append(toret, dg)
|
||||
|
||||
for _, resource := range dg.Resources {
|
||||
if _, ok := resourceToGroupIdx[resource.ID]; !ok {
|
||||
resourceToGroupIdx[resource.ID] = make(map[string]any)
|
||||
}
|
||||
resourceToGroupIdx[resource.ID][g.ID] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return toret, resourceToGroupIdx, err
|
||||
}
|
||||
|
||||
type group struct {
|
||||
ID string
|
||||
Name sql.NullString
|
||||
PublicID sql.NullString
|
||||
Resources []byte `nmap:"json"`
|
||||
PeerID sql.NullString `nmap:"skip"`
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package networkmap_sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
const (
|
||||
GetNameserversQuery = `
|
||||
select id, public_id, name, description, name_servers, groups, "primary", domains, enabled, search_domains_enabled
|
||||
from name_server_groups
|
||||
where account_id=?
|
||||
`
|
||||
)
|
||||
|
||||
func (sc *SqliteStoreConn) GetNameServerGroups(ctx context.Context, accountId string) ([]nmdata.NameServerGroup, error) {
|
||||
rows, err := sc.Conn.QueryContext(ctx, GetNameserversQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nsgroups, err := CollectRowsForSqlite[networkmapdb.NameserverGroup](rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return networkmapdb.ConvertAllToSharedTypes[networkmapdb.NameserverGroup, nmdata.NameServerGroup](nsgroups)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package networkmap_sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
const (
|
||||
GetNetworkQuery = `
|
||||
select network_identifier as identifier, network_net as net, network_net_v6 as net_v6, network_dns as dns, network_serial as serial
|
||||
from accounts
|
||||
where id=?
|
||||
`
|
||||
)
|
||||
|
||||
func (sc *SqliteStoreConn) GetNetwork(ctx context.Context, accountId string) (nmdata.Network, error) {
|
||||
rows, err := sc.Conn.QueryContext(ctx, GetNetworkQuery, accountId)
|
||||
if err != nil {
|
||||
return nmdata.Network{}, err
|
||||
}
|
||||
|
||||
n, err := CollectOneRowForSqlite[networkmapdb.AccountNetwork](rows)
|
||||
if err != nil {
|
||||
return nmdata.Network{}, err
|
||||
}
|
||||
|
||||
toret := nmdata.Network{}
|
||||
err = networkmapdb.FromSqlTypesToSharedTypes(
|
||||
reflect.ValueOf(&n), reflect.ValueOf(&toret))
|
||||
if err != nil {
|
||||
return nmdata.Network{}, err
|
||||
}
|
||||
|
||||
return toret, nil
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package networkmap_sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
const (
|
||||
GetNetworkResourcesQuery = `
|
||||
select id, network_id, account_id, public_id, name, description, type, domain, prefix, enabled
|
||||
from network_resources
|
||||
where account_id=?
|
||||
`
|
||||
)
|
||||
|
||||
func (sc *SqliteStoreConn) GetNetworkResources(ctx context.Context, accountId string) ([]nmdata.NetworkResource, error) {
|
||||
rows, err := sc.Conn.QueryContext(ctx, GetNetworkResourcesQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
netresorces, err := CollectRowsForSqlite[networkmapdb.Networkresource](rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return networkmapdb.ConvertAllToSharedTypes[networkmapdb.Networkresource, nmdata.NetworkResource](netresorces)
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package networkmap_sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
const (
|
||||
GetNetworkRouterQuery = `
|
||||
select public_id, peer, network_id, masquerade, metric, enabled, peer_groups, group_peers.peer_id
|
||||
from network_routers, json_each(peer_groups)
|
||||
left join group_peers on group_peers.account_id=? and group_peers.group_id=json_each.value
|
||||
where network_routers.account_id=?
|
||||
`
|
||||
)
|
||||
|
||||
func (sc *SqliteStoreConn) GetNetworkRouters(ctx context.Context, accountId string) (map[string]map[string]*nmdata.NetworkRouter, error) {
|
||||
rows, err := sc.Conn.QueryContext(ctx, GetNetworkRouterQuery, accountId, accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
routers, err := CollectRowsForSqlite[networkrouter](rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
toret := make(map[string]map[string]*nmdata.NetworkRouter)
|
||||
for _, router := range routers {
|
||||
if !router.Enabled.Bool {
|
||||
continue
|
||||
}
|
||||
|
||||
networkId := router.NetworkID.String
|
||||
if networkId == "" {
|
||||
return nil, fmt.Errorf("router with public_id %s doesn't have network_id set", router.PublicID.String)
|
||||
}
|
||||
|
||||
nmdatarouter := nmdata.NetworkRouter{}
|
||||
err := networkmapdb.FromSqlTypesToSharedTypes(reflect.ValueOf(&router), reflect.ValueOf(&nmdatarouter))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if toret[networkId] == nil {
|
||||
toret[networkId] = make(map[string]*nmdata.NetworkRouter)
|
||||
}
|
||||
if router.Peer.String != "" {
|
||||
toret[networkId][router.Peer.String] = &nmdatarouter
|
||||
continue
|
||||
}
|
||||
if router.PeerViaGroups.String != "" {
|
||||
toret[networkId][router.PeerViaGroups.String] = &nmdatarouter
|
||||
}
|
||||
}
|
||||
|
||||
return toret, nil
|
||||
}
|
||||
|
||||
type networkrouter struct {
|
||||
PublicID sql.NullString
|
||||
Peer sql.NullString `nmap:"skip"`
|
||||
NetworkID sql.NullString `nmap:"skip"`
|
||||
Masquerade sql.NullBool
|
||||
Metric sql.NullInt64
|
||||
Enabled sql.NullBool
|
||||
PeerGroups []byte `nmap:"json"`
|
||||
PeerViaGroups sql.NullString `nmap:"skip"`
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package networkmap_sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
)
|
||||
|
||||
const (
|
||||
GetNetworksQuery = `
|
||||
select id, public_id
|
||||
from networks where account_id=?
|
||||
`
|
||||
)
|
||||
|
||||
func (sc *SqliteStoreConn) GetNetworkXIDToPublicIdMap(ctx context.Context, accountId string) (map[string]string, error) {
|
||||
rows, err := sc.Conn.QueryContext(ctx, GetNetworksQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
networks, err := CollectRowsForSqlite[networkmapdb.Network](rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
toret := make(map[string]string)
|
||||
for _, n := range networks {
|
||||
if n.PublicID.Valid {
|
||||
toret[n.ID] = n.PublicID.String
|
||||
}
|
||||
}
|
||||
|
||||
return toret, nil
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package networkmap_sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
const (
|
||||
GetPeersQuery = `
|
||||
select id, key, ssh_key, dns_label, extra_dns_labels, user_id, ssh_enabled, login_expiration_enabled, last_login, ip, ipv6,
|
||||
peer_status_requires_approval, peer_status_connected, proxy_meta_embedded, proxy_meta_cluster,
|
||||
meta_wt_version, meta_go_os, meta_os_version, meta_kernel_version, meta_network_addresses, meta_files, meta_capabilities, meta_flags, meta_sync_message_version,
|
||||
location_country_code, location_city_name, location_connection_ip
|
||||
from peers
|
||||
where account_id = ?
|
||||
`
|
||||
)
|
||||
|
||||
func (sc *SqliteStoreConn) GetPeers(ctx context.Context, accountId string) ([]nmdata.Peer, map[string][]*nmdata.Peer, error) {
|
||||
rows, err := sc.Conn.QueryContext(ctx, GetPeersQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
peers, err := CollectRowsForSqlite[networkmapdb.Peer](rows)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return networkmapdb.ConvertToNmdataPeers(peers)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package networkmap_sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
const (
|
||||
GetPoliciesQuery = `
|
||||
select p.id, p.public_id, p.enabled, p.source_posture_checks, pr.enabled as rule_enabled, pr.action, pr.protocol, pr.bidirectional,
|
||||
pr.sources, pr.destinations, pr.source_resource, pr.destination_resource, pr.ports, pr.port_ranges,
|
||||
pr.authorized_groups, pr.authorized_user
|
||||
from policies as p
|
||||
left join policy_rules as pr on p.id = pr.policy_id
|
||||
where account_id=?
|
||||
`
|
||||
)
|
||||
|
||||
func (sc *SqliteStoreConn) GetPolicies(ctx context.Context, accountId string) ([]nmdata.Policy, map[string]map[string]any, map[string]map[string]any, error) {
|
||||
rows, err := sc.Conn.QueryContext(ctx, GetPoliciesQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
policies, err := CollectRowsForSqlite[networkmapdb.Policy](rows)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return networkmapdb.ConvertToNmdataPolicy(policies)
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package networkmap_sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"reflect"
|
||||
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
const (
|
||||
GetPostureChecksQuery = `
|
||||
select id, public_id, checks
|
||||
from posture_checks
|
||||
where account_id=?
|
||||
`
|
||||
)
|
||||
|
||||
func (sc *SqliteStoreConn) GetPostureChecks(ctx context.Context, accountId string) ([]nmdata.PostureChecks, map[string]string, error) {
|
||||
rows, err := sc.Conn.QueryContext(ctx, GetPostureChecksQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
checks, err := CollectRowsForSqlite[networkmapdb.PostureChecks](rows)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
toret := make([]nmdata.PostureChecks, 0, len(checks))
|
||||
idToPublicIDIdx := make(map[string]string)
|
||||
for _, c := range checks {
|
||||
checks := nmdata.PostureChecks{}
|
||||
err := networkmapdb.FromSqlTypesToSharedTypes(reflect.ValueOf(&c), reflect.ValueOf(&checks))
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
toret = append(toret, checks)
|
||||
idToPublicIDIdx[checks.ID] = c.PublicID.String
|
||||
}
|
||||
|
||||
return toret, idToPublicIDIdx, nil
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package networkmap_sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
"github.com/netbirdio/netbird/shared/management/networkmap/nmdata"
|
||||
)
|
||||
|
||||
const (
|
||||
GetRoutesQuery = `
|
||||
select id, account_id, public_id, network, domains, keep_route, net_id, description,
|
||||
peer, peer as peer_id, peer_groups, network_type, masquerade, metric, enabled,
|
||||
groups, access_control_groups, skip_auto_apply
|
||||
from routes
|
||||
where account_id=?
|
||||
`
|
||||
)
|
||||
|
||||
func (sc *SqliteStoreConn) GetRoutes(ctx context.Context, accountId string) ([]nmdata.Route, error) {
|
||||
rows, err := sc.Conn.QueryContext(ctx, GetRoutesQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
routes, err := CollectRowsForSqlite[networkmapdb.Route](rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return networkmapdb.ConvertAllToSharedTypes[networkmapdb.Route, nmdata.Route](routes)
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package networkmap_sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
)
|
||||
|
||||
const (
|
||||
GetServicesQuery = `
|
||||
select enabled, private, access_groups, proxy_cluster, domain
|
||||
from services
|
||||
where account_id=?
|
||||
`
|
||||
|
||||
GetProxyTargetedDomainResourcesQuery = `
|
||||
select t.target_id
|
||||
from targets as t
|
||||
join services as s on s.id = t.service_id
|
||||
where s.account_id=? and s.enabled and not coalesce(s.terminated, false)
|
||||
and t.enabled and t.target_type='domain' and t.target_id is not null
|
||||
`
|
||||
)
|
||||
|
||||
func (sc *SqliteStoreConn) GetPrivateServices(ctx context.Context, accountId string) ([]networkmapdb.Service, error) {
|
||||
rows, err := sc.Conn.QueryContext(ctx, GetServicesQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
services, err := CollectRowsForSqlite[service](rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
toret := make([]networkmapdb.Service, 0, len(services))
|
||||
for _, service := range services {
|
||||
acg := []string{}
|
||||
if service.AccessGroups != nil {
|
||||
if err := json.Unmarshal(service.AccessGroups, &acg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
s := networkmapdb.Service{
|
||||
Enabled: service.Enabled,
|
||||
Private: service.Private,
|
||||
AccessGroups: acg,
|
||||
ProxyCluster: service.ProxyCluster,
|
||||
Domain: service.Domain,
|
||||
}
|
||||
|
||||
toret = append(toret, s)
|
||||
}
|
||||
return toret, nil
|
||||
}
|
||||
|
||||
func (sc *SqliteStoreConn) GetProxyTargetedDomainResourceIDs(ctx context.Context, accountId string) (map[string]struct{}, error) {
|
||||
rows, err := sc.Conn.QueryContext(ctx, GetProxyTargetedDomainResourcesQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
toret := make(map[string]struct{})
|
||||
for rows.Next() {
|
||||
var id string
|
||||
err := rows.Scan(&id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
toret[id] = struct{}{}
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toret, nil
|
||||
}
|
||||
|
||||
type service struct {
|
||||
Enabled sql.NullBool
|
||||
Private sql.NullBool
|
||||
AccessGroups []byte
|
||||
ProxyCluster sql.NullString
|
||||
Domain sql.NullString
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package networkmap_sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"database/sql"
|
||||
|
||||
networkmapdb "github.com/netbirdio/netbird/management/internals/network_map_db"
|
||||
)
|
||||
|
||||
var ErrNoRows = errors.New("no rows in result set")
|
||||
|
||||
type SqliteStore struct {
|
||||
Db *sql.DB
|
||||
}
|
||||
|
||||
type sqliteInterface interface {
|
||||
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
|
||||
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
|
||||
}
|
||||
|
||||
type SqliteStoreConn struct {
|
||||
Conn sqliteInterface
|
||||
}
|
||||
|
||||
func NewSqliteStore(storeFile, dataDir string) (*SqliteStore, error) {
|
||||
dbfile := storeFile
|
||||
if envFile, ok := os.LookupEnv("NB_STORE_ENGINE_SQLITE_FILE"); ok && envFile != "" {
|
||||
dbfile = envFile
|
||||
}
|
||||
|
||||
// Separate file path from any SQLite URI query parameters (e.g., "store.db?mode=rwc")
|
||||
filePath, query, hasQuery := strings.Cut(dbfile, "?")
|
||||
|
||||
connStr := filePath
|
||||
if filePath != ":memory:" && !filepath.IsAbs(filePath) {
|
||||
connStr = filepath.Join(dataDir, filePath)
|
||||
}
|
||||
|
||||
// Compose query parameters. User-provided ?_busy_timeout (or its mattn alias
|
||||
// ?_timeout) overrides our default; otherwise inject 30s so SQLite waits at
|
||||
// most that long on a lock instead of blocking the only Go-side connection.
|
||||
// mattn/go-sqlite3 applies PRAGMA from the DSN on every fresh connection, so
|
||||
// the value survives ConnMaxIdleTime/ConnMaxLifetime recycling. cache=shared
|
||||
// stays the default on non-Windows for the same reason as before.
|
||||
parsed, _ := url.ParseQuery(query)
|
||||
var defaults []string
|
||||
if parsed.Get("_busy_timeout") == "" && parsed.Get("_timeout") == "" {
|
||||
defaults = append(defaults, "_busy_timeout=30000")
|
||||
}
|
||||
if !hasQuery && runtime.GOOS != "windows" {
|
||||
// To avoid `The process cannot access the file because it is being used by another process` on Windows
|
||||
defaults = append(defaults, "cache=shared")
|
||||
}
|
||||
parts := defaults
|
||||
if hasQuery {
|
||||
parts = append(parts, query)
|
||||
}
|
||||
if len(parts) > 0 {
|
||||
connStr += "?" + strings.Join(parts, "&")
|
||||
}
|
||||
|
||||
db, err := sql.Open("sqlite3", connStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &SqliteStore{Db: db}, nil
|
||||
}
|
||||
|
||||
func (s *SqliteStore) BeginTx(ctx context.Context) (networkmapdb.NetworkMapDBStoreConn, error) {
|
||||
tx, err := s.Db.BeginTx(ctx, &sql.TxOptions{ReadOnly: true, Isolation: sql.LevelRepeatableRead})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SqliteStoreConn{Conn: tx}, nil
|
||||
}
|
||||
|
||||
func (s *SqliteStore) Exec(_ context.Context, query string, args ...any) error {
|
||||
_, err := s.Db.Exec(query, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (sc *SqliteStoreConn) RollbackTx(ctx context.Context) error {
|
||||
tx, ok := sc.Conn.(*sql.Tx)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected an sql.Tx got %s", reflect.TypeOf(sc.Conn).Kind())
|
||||
}
|
||||
return tx.Rollback()
|
||||
}
|
||||
|
||||
func (sc *SqliteStoreConn) CommitTx(ctx context.Context) error {
|
||||
tx, ok := sc.Conn.(*sql.Tx)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected an sql.Tx got %s", reflect.TypeOf(sc.Conn).Kind())
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (s *SqliteStore) UsingConn() *SqliteStoreConn {
|
||||
return &SqliteStoreConn{Conn: s.Db}
|
||||
}
|
||||
|
||||
func CollectOneRowForSqlite[T any](rows *sql.Rows) (T, error) {
|
||||
defer rows.Close()
|
||||
var r T
|
||||
|
||||
if !rows.Next() {
|
||||
if err := rows.Err(); err != nil {
|
||||
return r, err
|
||||
}
|
||||
return r, ErrNoRows
|
||||
}
|
||||
err := rows.Scan(networkmapdb.StructFields(&r)...)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func CollectRowsForSqlite[T any](rows *sql.Rows) ([]T, error) {
|
||||
defer rows.Close()
|
||||
toret := make([]T, 0)
|
||||
|
||||
for rows.Next() {
|
||||
var r T
|
||||
err := rows.Scan(networkmapdb.StructFields(&r)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
toret = append(toret, r)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toret, nil
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package networkmap_sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
const (
|
||||
GetAllowedUserIdsQuery = `
|
||||
select id, auto_groups
|
||||
from users
|
||||
where account_id=? and not blocked and not is_service_user
|
||||
`
|
||||
|
||||
GetAllGroupIdQuery = `
|
||||
select id from groups
|
||||
where account_id=? and name='All'
|
||||
`
|
||||
)
|
||||
|
||||
func (sc *SqliteStoreConn) GetAllowedUsers(ctx context.Context, accountId string) (map[string]struct{}, map[string][]string, error) {
|
||||
rows, err := sc.Conn.QueryContext(ctx, GetAllowedUserIdsQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
users, err := CollectRowsForSqlite[user](rows)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
rows, err = sc.Conn.QueryContext(ctx, GetAllGroupIdQuery, accountId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
allGroupIds, err := collectAllGroupIds(rows)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
userIdIdx := make(map[string]struct{})
|
||||
groupIdToUserIds := make(map[string][]string)
|
||||
for _, user := range users {
|
||||
autogroups := make([]string, 0)
|
||||
if err := json.Unmarshal(user.AutoGroups, &autogroups); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
userIdIdx[user.ID] = struct{}{}
|
||||
for _, groupId := range autogroups {
|
||||
groupIdToUserIds[groupId] = append(groupIdToUserIds[groupId], user.ID)
|
||||
}
|
||||
for _, allgid := range allGroupIds {
|
||||
groupIdToUserIds[allgid] = append(groupIdToUserIds[allgid], user.ID)
|
||||
}
|
||||
}
|
||||
|
||||
return userIdIdx, groupIdToUserIds, nil
|
||||
}
|
||||
|
||||
func collectAllGroupIds(rows *sql.Rows) ([]string, error) {
|
||||
defer rows.Close()
|
||||
var toret []string
|
||||
|
||||
for rows.Next() {
|
||||
var id string
|
||||
err := rows.Scan(&id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
toret = append(toret, id)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return toret, nil
|
||||
}
|
||||
|
||||
type user struct {
|
||||
ID string
|
||||
AutoGroups []byte
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
package networkmapdb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/xid"
|
||||
)
|
||||
|
||||
var ErrNoRows = errors.New("no rows in result set")
|
||||
|
||||
const (
|
||||
NMAP_STRUCT_TAG = "nmap"
|
||||
NMAP_SKIP = "skip"
|
||||
NMAP_MAP_TO = "map_to"
|
||||
NMAP_JSON = "json"
|
||||
)
|
||||
|
||||
type fieldTag struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
func tagFromString(t string) fieldTag {
|
||||
kv := strings.Split(t, ":")
|
||||
if len(kv) == 1 {
|
||||
return fieldTag{Key: strings.TrimSpace(kv[0])}
|
||||
}
|
||||
return fieldTag{Key: strings.TrimSpace(kv[0]), Value: strings.TrimSpace(kv[1])}
|
||||
}
|
||||
|
||||
func FromSqlTypesToSharedTypes(src reflect.Value, dst reflect.Value) error {
|
||||
typ := src.Elem().Type()
|
||||
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
f := typ.Field(i)
|
||||
|
||||
fieldTags := make(map[string]string)
|
||||
if v := f.Tag.Get(NMAP_STRUCT_TAG); v != "" {
|
||||
for _, t := range strings.Split(v, ",") {
|
||||
kv := tagFromString(t)
|
||||
fieldTags[kv.Key] = kv.Value
|
||||
}
|
||||
}
|
||||
if _, ok := fieldTags[NMAP_SKIP]; ok {
|
||||
continue
|
||||
}
|
||||
if f.PkgPath != "" { // skip unexported fields
|
||||
continue
|
||||
}
|
||||
dstFieldName := f.Name
|
||||
if override, ok := fieldTags[NMAP_MAP_TO]; ok {
|
||||
dstFieldName = override
|
||||
}
|
||||
|
||||
dstField := dst.Elem().FieldByName(dstFieldName)
|
||||
if !dstField.IsValid() {
|
||||
return errors.New("unsupported type in destination field: " + dstFieldName)
|
||||
}
|
||||
|
||||
srcField := src.Elem().Field(i)
|
||||
srcFieldType := srcField.Type().String()
|
||||
switch srcFieldType {
|
||||
case "string":
|
||||
s := srcField.Interface().(string)
|
||||
dstField.SetString(s)
|
||||
case "sql.NullString":
|
||||
s := srcField.Interface().(sql.NullString)
|
||||
if s.Valid {
|
||||
dstField.SetString(s.String)
|
||||
}
|
||||
if (dstFieldName == "PublicId" || dstFieldName == "PublicID") && s.String == "" {
|
||||
dstField.SetString(xid.New().String()) // TODO (dmitri) this needs to be removed to support delta updates
|
||||
}
|
||||
case "sql.NullTime":
|
||||
s := srcField.Interface().(sql.NullTime)
|
||||
if s.Valid {
|
||||
if dstField.Kind() == reflect.Ptr {
|
||||
t := reflect.ValueOf(&s.Time).Elem()
|
||||
dstField.Set(t.Addr())
|
||||
} else {
|
||||
dstField.Set(reflect.ValueOf(s.Time))
|
||||
}
|
||||
}
|
||||
case "sql.NullBool":
|
||||
s := srcField.Interface().(sql.NullBool)
|
||||
if s.Valid {
|
||||
dstField.SetBool(s.Bool)
|
||||
}
|
||||
case "sql.NullInt64":
|
||||
s := srcField.Interface().(sql.NullInt64)
|
||||
if s.Valid {
|
||||
dstField.SetInt(s.Int64)
|
||||
}
|
||||
case "json.RawMessage":
|
||||
s := srcField.Interface().(json.RawMessage)
|
||||
if len(s) == 0 {
|
||||
continue
|
||||
}
|
||||
if err := json.Unmarshal(s, dstField.Addr().Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
case "[]byte", "[]uint8":
|
||||
s := srcField.Interface().([]byte)
|
||||
if _, ok := fieldTags[NMAP_JSON]; !ok || len(s) == 0 {
|
||||
continue
|
||||
}
|
||||
if err := json.Unmarshal(s, dstField.Addr().Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
case "[]string":
|
||||
if srcField.IsNil() {
|
||||
continue
|
||||
}
|
||||
dstv := reflect.MakeSlice(dstField.Type(), srcField.Len(), srcField.Cap())
|
||||
reflect.Copy(dstv, srcField)
|
||||
dstField.Set(dstv)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func StructFields(s any) []any {
|
||||
src := reflect.ValueOf(s)
|
||||
toret := make([]any, 0)
|
||||
typ := src.Elem().Type()
|
||||
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
f := typ.Field(i)
|
||||
if f.PkgPath != "" { // skip unexported fields
|
||||
continue
|
||||
}
|
||||
|
||||
srcField := src.Elem().Field(i)
|
||||
toret = append(toret, srcField.Addr().Interface())
|
||||
}
|
||||
|
||||
return toret
|
||||
}
|
||||
|
||||
func ConvertAllToSharedTypes[T any, T1 any](allsrc []T) ([]T1, error) {
|
||||
toret := make([]T1, 0, len(allsrc))
|
||||
for _, src := range allsrc {
|
||||
var dst T1
|
||||
err := FromSqlTypesToSharedTypes(
|
||||
reflect.ValueOf(&src), reflect.ValueOf(&dst))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
toret = append(toret, dst)
|
||||
}
|
||||
return toret, nil
|
||||
}
|
||||
Reference in New Issue
Block a user