Skip to content

Commit deb6a87

Browse files
authored
fix: use correct primary key columns in config_access_logs OnConflict (#1877)
1 parent 0bbead1 commit deb6a87

File tree

2 files changed

+144
-2
lines changed

2 files changed

+144
-2
lines changed

db/update.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -886,8 +886,7 @@ func saveResults(ctx api.ScrapeContext, results []v1.ScrapeResult) (v1.ScrapeSum
886886
accessLog.ConfigID = uuid.MustParse(config)
887887
}
888888

889-
if err := ctx.DB().Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "id"}}, DoNothing: true}).
890-
Save(&accessLog.ConfigAccessLog).Error; err != nil {
889+
if err := SaveConfigAccessLog(ctx, &accessLog.ConfigAccessLog); err != nil {
891890
return summary, fmt.Errorf("failed to save access log: %w", err)
892891
}
893892
}
@@ -1395,6 +1394,20 @@ type configExternalKey struct {
13951394
parentType string
13961395
}
13971396

1397+
func SaveConfigAccessLog(ctx api.ScrapeContext, accessLog *dutyModels.ConfigAccessLog) error {
1398+
if accessLog == nil {
1399+
return nil
1400+
}
1401+
1402+
return ctx.DB().Clauses(clause.OnConflict{
1403+
Columns: []clause.Column{{Name: "config_id"}, {Name: "external_user_id"}, {Name: "scraper_id"}},
1404+
DoUpdates: clause.AssignmentColumns([]string{"created_at", "mfa", "properties"}),
1405+
Where: clause.Where{Exprs: []clause.Expression{
1406+
clause.Expr{SQL: "excluded.created_at > config_access_logs.created_at"},
1407+
}},
1408+
}).Create(accessLog).Error
1409+
}
1410+
13981411
// extractResult holds the extracted configs & changes from the scrape result
13991412
type extractResult struct {
14001413
newConfigs []*models.ConfigItem

scrapers/run_test.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package scrapers
33
import (
44
gocontext "context"
55
"encoding/json"
6+
"errors"
67
"fmt"
78
"math/rand/v2"
89
"os"
@@ -1628,3 +1629,131 @@ var _ = Describe("External groups with aliases e2e test", Ordered, func() {
16281629
Expect(developersID).To(Equal(devsID)) // Same group, same ID
16291630
})
16301631
})
1632+
1633+
var _ = Describe("Config access logs upsert", Ordered, func() {
1634+
fetchAccessLogRefs := func() (dutymodels.ConfigItem, uuid.UUID, api.ScrapeContext, uuid.UUID, func()) {
1635+
var configItem dutymodels.ConfigItem
1636+
err := DefaultContext.DB().Where("scraper_id IS NOT NULL").First(&configItem).Error
1637+
Expect(err).NotTo(HaveOccurred(), "failed to find config item with scraper_id")
1638+
1639+
scraperIDValue := lo.FromPtr(configItem.ScraperID)
1640+
Expect(scraperIDValue).NotTo(BeEmpty())
1641+
1642+
scraperID, err := uuid.Parse(scraperIDValue)
1643+
Expect(err).NotTo(HaveOccurred(), "failed to parse scraper id")
1644+
1645+
err = DefaultContext.DB().First(&dutymodels.ConfigScraper{}, "id = ?", scraperID).Error
1646+
Expect(err).NotTo(HaveOccurred(), "failed to find scraper")
1647+
1648+
cleanupExternalUser := func() {}
1649+
var externalUser dutymodels.ExternalUser
1650+
err = DefaultContext.DB().Where("scraper_id = ?", scraperID).First(&externalUser).Error
1651+
if err != nil {
1652+
if errors.Is(err, gorm.ErrRecordNotFound) {
1653+
externalUser = dutymodels.ExternalUser{
1654+
ID: uuid.New(),
1655+
Name: "access-log-user",
1656+
AccountID: "test-account",
1657+
UserType: "user",
1658+
ScraperID: scraperID,
1659+
CreatedAt: time.Now(),
1660+
}
1661+
err = DefaultContext.DB().Create(&externalUser).Error
1662+
Expect(err).NotTo(HaveOccurred(), "failed to create external user")
1663+
cleanupExternalUser = func() {
1664+
err := DefaultContext.DB().Delete(&externalUser).Error
1665+
Expect(err).NotTo(HaveOccurred(), "failed to delete external user")
1666+
}
1667+
} else {
1668+
Expect(err).NotTo(HaveOccurred(), "failed to fetch external user")
1669+
}
1670+
}
1671+
1672+
return configItem, scraperID, api.NewScrapeContext(DefaultContext), externalUser.ID, cleanupExternalUser
1673+
}
1674+
1675+
cleanupAccessLog := func(configID, externalUserID, scraperID uuid.UUID) {
1676+
err := DefaultContext.DB().Where("config_id = ? AND external_user_id = ? AND scraper_id = ?",
1677+
configID, externalUserID, scraperID).Delete(&dutymodels.ConfigAccessLog{}).Error
1678+
Expect(err).NotTo(HaveOccurred(), "failed to delete access logs")
1679+
}
1680+
1681+
It("should update access log when newer", func() {
1682+
configItem, scraperID, scraperCtx, externalUserID, cleanupExternalUser := fetchAccessLogRefs()
1683+
DeferCleanup(func() {
1684+
cleanupAccessLog(configItem.ID, externalUserID, scraperID)
1685+
cleanupExternalUser()
1686+
})
1687+
1688+
olderTime := time.Now().Add(-2 * time.Hour)
1689+
latestAccessTime := time.Now().Add(-1 * time.Hour)
1690+
1691+
olderLog := dutymodels.ConfigAccessLog{
1692+
ConfigID: configItem.ID,
1693+
ExternalUserID: externalUserID,
1694+
ScraperID: scraperID,
1695+
CreatedAt: olderTime,
1696+
MFA: false,
1697+
}
1698+
1699+
err := db.SaveConfigAccessLog(scraperCtx, &olderLog)
1700+
Expect(err).NotTo(HaveOccurred())
1701+
1702+
newerLog := dutymodels.ConfigAccessLog{
1703+
ConfigID: configItem.ID,
1704+
ExternalUserID: externalUserID,
1705+
ScraperID: scraperID,
1706+
CreatedAt: latestAccessTime,
1707+
MFA: true,
1708+
}
1709+
1710+
err = db.SaveConfigAccessLog(scraperCtx, &newerLog)
1711+
Expect(err).NotTo(HaveOccurred())
1712+
1713+
var storedLog dutymodels.ConfigAccessLog
1714+
err = DefaultContext.DB().Where("config_id = ? AND external_user_id = ? AND scraper_id = ?",
1715+
configItem.ID, externalUserID, scraperID).First(&storedLog).Error
1716+
Expect(err).NotTo(HaveOccurred())
1717+
Expect(storedLog.MFA).To(BeTrue())
1718+
Expect(storedLog.CreatedAt).To(BeTemporally("~", latestAccessTime, time.Second))
1719+
})
1720+
1721+
It("should ignore older access log", func() {
1722+
configItem, scraperID, scraperCtx, externalUserID, cleanupExternalUser := fetchAccessLogRefs()
1723+
DeferCleanup(func() {
1724+
cleanupAccessLog(configItem.ID, externalUserID, scraperID)
1725+
cleanupExternalUser()
1726+
})
1727+
1728+
latestAccessTime := time.Now().Add(-1 * time.Hour)
1729+
latestLog := dutymodels.ConfigAccessLog{
1730+
ConfigID: configItem.ID,
1731+
ExternalUserID: externalUserID,
1732+
ScraperID: scraperID,
1733+
CreatedAt: latestAccessTime,
1734+
MFA: true,
1735+
}
1736+
1737+
err := db.SaveConfigAccessLog(scraperCtx, &latestLog)
1738+
Expect(err).NotTo(HaveOccurred())
1739+
1740+
olderTime := time.Now().Add(-3 * time.Hour)
1741+
olderLog := dutymodels.ConfigAccessLog{
1742+
ConfigID: configItem.ID,
1743+
ExternalUserID: externalUserID,
1744+
ScraperID: scraperID,
1745+
CreatedAt: olderTime,
1746+
MFA: false,
1747+
}
1748+
1749+
err = db.SaveConfigAccessLog(scraperCtx, &olderLog)
1750+
Expect(err).NotTo(HaveOccurred())
1751+
1752+
var storedLog dutymodels.ConfigAccessLog
1753+
err = DefaultContext.DB().Where("config_id = ? AND external_user_id = ? AND scraper_id = ?",
1754+
configItem.ID, externalUserID, scraperID).First(&storedLog).Error
1755+
Expect(err).NotTo(HaveOccurred())
1756+
Expect(storedLog.MFA).To(BeTrue())
1757+
Expect(storedLog.CreatedAt).To(BeTemporally("~", latestAccessTime, time.Second))
1758+
})
1759+
})

0 commit comments

Comments
 (0)