Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 151 additions & 0 deletions cmd/db-gen/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package main

import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"time"

"github.com/glebarez/sqlite"
v1 "github.com/xeol-io/xeol/xeol/db/v1"
"github.com/xeol-io/xeol/xeol/db/v1/store/model"
"gorm.io/gorm"
)

type EOLResponse struct {
Cycle string `json:"cycle"`
ReleaseDate string `json:"releaseDate"`
Eol interface{} `json:"eol"` // can be bool or string
Latest string `json:"latest"`
LatestReleaseDate string `json:"latestReleaseDate"`
Lts interface{} `json:"lts"`
}

// PurlModel matches the DB schema used by the xeol store.
type PurlModel struct {
ID int `gorm:"primary_key;column:id;"`
ProductID int `gorm:"column:product_id"`
Purl string `gorm:"column:purl"`
}

func (m PurlModel) TableName() string { return "purls" }

func main() {
dbPath := "xeol.db"
_ = os.Remove(dbPath)
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
if err != nil {
log.Fatalf("failed to open database: %v", err)
}

if err := db.AutoMigrate(&model.IDModel{}, &model.ProductModel{}, &PurlModel{}, &model.CycleModel{}); err != nil {
log.Fatalf("failed to migrate: %v", err)
}

// 1. Insert DB metadata
db.Create(&model.IDModel{
SchemaVersion: v1.SchemaVersion,
BuildTimestamp: time.Now().UTC().Format(time.RFC3339Nano),
})

// 2. Seed the Terraform BINARY lifecycle (from endoflife.date)
seedProduct(db, productSpec{
Name: "Terraform",
Permalink: "terraform",
Purl: "pkg:terraform/hashicorp/terraform",
APIURL: "https://endoflife.date/api/terraform.json",
})

// 3. Seed hashicorp/aws provider with known EOL cycles.
// The AWS provider follows a major-version deprecation model; v4.x and below
// are EOL as of 2024-01-31 (announced by HashiCorp).
seedProviderWithStaticCycles(db, productSpec{
Name: "Terraform AWS Provider",
Permalink: "terraform-provider-aws",
Purl: "pkg:terraform/hashicorp/aws",
}, []staticCycle{
{Cycle: "4", EolDate: "2024-01-31", Latest: "4.67.0"},
{Cycle: "3", EolDate: "2023-04-30", Latest: "3.76.1"},
{Cycle: "2", EolDate: "2022-09-30", Latest: "2.70.0"},
})

fmt.Println("✅ xeol.db generated successfully with Terraform binary + provider EOL data!")
}

type productSpec struct {
Name string
Permalink string
Purl string
APIURL string
}

type staticCycle struct {
Cycle string
EolDate string
Latest string
}

func seedProduct(db *gorm.DB, spec productSpec) {
product := model.ProductModel{Name: spec.Name, Permalink: spec.Permalink}
db.Create(&product)
db.Create(&PurlModel{ProductID: product.ID, Purl: spec.Purl})

resp, err := http.Get(spec.APIURL)
if err != nil {
log.Fatalf("failed to fetch %s: %v", spec.APIURL, err)
}
defer resp.Body.Close()

var data []EOLResponse
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
log.Fatalf("failed to decode %s: %v", spec.APIURL, err)
}

for _, item := range data {
cycle := model.CycleModel{
ProductID: product.ID,
ProductName: product.Name,
ProductPermalink: product.Permalink,
ReleaseCycle: item.Cycle,
LatestRelease: item.Latest,
}
if d, err := time.Parse("2006-01-02", item.ReleaseDate); err == nil {
cycle.ReleaseDate = d
}
if d, err := time.Parse("2006-01-02", item.LatestReleaseDate); err == nil {
cycle.LatestReleaseDate = d
}
if eolBool, ok := item.Eol.(bool); ok {
cycle.EolBool = eolBool
} else if eolStr, ok := item.Eol.(string); ok {
if d, err := time.Parse("2006-01-02", eolStr); err == nil {
cycle.Eol = d
}
}
db.Create(&cycle)
}
fmt.Printf(" seeded %s (%d cycles)\n", spec.Name, len(data))
}

func seedProviderWithStaticCycles(db *gorm.DB, spec productSpec, cycles []staticCycle) {
product := model.ProductModel{Name: spec.Name, Permalink: spec.Permalink}
db.Create(&product)
db.Create(&PurlModel{ProductID: product.ID, Purl: spec.Purl})

for _, c := range cycles {
cycle := model.CycleModel{
ProductID: product.ID,
ProductName: product.Name,
ProductPermalink: product.Permalink,
ReleaseCycle: c.Cycle,
LatestRelease: c.Latest,
}
if d, err := time.Parse("2006-01-02", c.EolDate); err == nil {
cycle.Eol = d
}
db.Create(&cycle)
}
fmt.Printf(" seeded %s (%d static cycles)\n", spec.Name, len(cycles))
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ require (
github.com/nwaples/rardecode v1.1.2 // indirect
github.com/opencontainers/runtime-spec v1.1.0 // indirect
github.com/opencontainers/selinux v1.11.0 // indirect
github.com/owenrumney/go-sarif/v2 v2.3.3 // indirect
github.com/pborman/indent v1.2.1 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHG
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46 h1:vmXNl+HDfqqXgr0uY1UgK1GAhps8nbAAtqHNBcgyf+4=
github.com/aquasecurity/go-pep440-version v0.0.0-20210121094942-22b2f8951d46/go.mod h1:olhPNdiiAAMiSujemd1O/sc6GcyePr23f/6uGKtthNg=
github.com/aquasecurity/go-version v0.0.0-20210121072130-637058cfe492 h1:rcEG5HI490FF0a7zuvxOxen52ddygCfNVjP0XOCMl+M=
Expand Down Expand Up @@ -907,6 +908,10 @@ github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaL
github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
github.com/oras-project/oras-credentials-go v0.3.1 h1:sfGqZ8sjPifEaOtjHOQTPr8D+Tql4bpw58Dd9wjmm9w=
github.com/oras-project/oras-credentials-go v0.3.1/go.mod h1:fFCebDQo0Do+gnM96uV9YUnRay0pwuRQupypvofsp4s=
github.com/owenrumney/go-sarif v1.1.1 h1:QNObu6YX1igyFKhdzd7vgzmw7XsWN3/6NMGuDzBgXmE=
github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U=
github.com/owenrumney/go-sarif/v2 v2.3.3 h1:ubWDJcF5i3L/EIOER+ZyQ03IfplbSU1BLOE26uKQIIU=
github.com/owenrumney/go-sarif/v2 v2.3.3/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/indent v1.2.1 h1:lFiviAbISHv3Rf0jcuh489bi06hj98JsVMtIDZQb9yM=
Expand Down Expand Up @@ -1091,6 +1096,8 @@ github.com/veraison/go-cose v1.3.0 h1:2/H5w8kdSpQJyVtIhx8gmwPJ2uSz1PkyWFx0idbd7r
github.com/veraison/go-cose v1.3.0/go.mod h1:df09OV91aHoQWLmy1KsDdYiagtXgyAwAl8vFeFn1gMc=
github.com/vifraa/gopom v1.0.0 h1:L9XlKbyvid8PAIK8nr0lihMApJQg/12OBvMA28BcWh0=
github.com/vifraa/gopom v1.0.0/go.mod h1:oPa1dcrGrtlO37WPDBm5SqHAT+wTgF8An1Q71Z6Vv4o=
github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651 h1:jIVmlAFIqV3d+DOxazTR9v+zgj8+VYuQBzPgBZvWBHA=
github.com/wagoodman/go-partybus v0.0.0-20230516145632-8ccac152c651/go.mod h1:b26F2tHLqaoRQf8DywqzVaV1MQ9yvjb0OMcNl7Nxu20=
github.com/wagoodman/go-presenter v0.0.0-20211015174752-f9c01afc824b h1:uWNQ0khA6RdFzODOMwKo9XXu7fuewnnkHykUtuKru8s=
Expand All @@ -1117,6 +1124,7 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs=
Expand Down
4 changes: 4 additions & 0 deletions internal/format/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const (
UnknownFormat Format = "unknown"
JSONFormat Format = "json"
TableFormat Format = "table"
SarifFormat Format = "sarif"
)

// Format is a dedicated type to represent a specific kind of presenter output format.
Expand All @@ -26,6 +27,8 @@ func Parse(userInput string) Format {
return JSONFormat
case strings.ToLower(TableFormat.String()):
return TableFormat
case strings.ToLower(SarifFormat.String()):
return SarifFormat
default:
return UnknownFormat
}
Expand All @@ -35,4 +38,5 @@ func Parse(userInput string) Format {
var AvailableFormats = []Format{
JSONFormat,
TableFormat,
SarifFormat,
}
3 changes: 3 additions & 0 deletions internal/format/presenter.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (

"github.com/xeol-io/xeol/xeol/presenter/json"
"github.com/xeol-io/xeol/xeol/presenter/models"
"github.com/xeol-io/xeol/xeol/presenter/sarif"
"github.com/xeol-io/xeol/xeol/presenter/table"
)

Expand All @@ -15,6 +16,8 @@ func GetPresenter(format Format, pb models.PresenterConfig) presenter.Presenter
return json.NewPresenter(pb)
case TableFormat:
return table.NewPresenter(pb)
case SarifFormat:
return sarif.NewPresenter(pb)
default:
return nil
}
Expand Down
1 change: 1 addition & 0 deletions xeol/db/v1/store/model/cycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ type CycleModel struct {
ProductName string `gorm:"column:product_name"`
ProductPermalink string `gorm:"column:product_permalink"`
ID int `gorm:"primary_key;column:id;"`
ProductID int `gorm:"column:product_id"`
ReleaseCycle string `gorm:"column:release_cycle"`
Eol time.Time `gorm:"column:eol"`
EolBool bool `gorm:"column:eol_bool"`
Expand Down
18 changes: 15 additions & 3 deletions xeol/matcher/matchers.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"github.com/xeol-io/xeol/xeol/match"
distroMatcher "github.com/xeol-io/xeol/xeol/matcher/distro"
pkgMatcher "github.com/xeol-io/xeol/xeol/matcher/packages"
terraformMatcher "github.com/xeol-io/xeol/xeol/matcher/terraform"
"github.com/xeol-io/xeol/xeol/pkg"
)

Expand All @@ -24,14 +25,16 @@ type Monitor struct {

// Config contains values used by individual matcher structs for advanced configuration
type Config struct {
Packages pkgMatcher.MatcherConfig
Distro distroMatcher.MatcherConfig
Packages pkgMatcher.MatcherConfig
Distro distroMatcher.MatcherConfig
Terraform terraformMatcher.MatcherConfig
}

func NewDefaultMatchers(_ Config) []Matcher {
return []Matcher{
&pkgMatcher.Matcher{},
&distroMatcher.Matcher{},
&terraformMatcher.Matcher{},
}
}

Expand Down Expand Up @@ -77,6 +80,7 @@ func FindMatches(store interface {
distroMatcher := &distroMatcher.Matcher{
UseCPEs: true,
}
terraformMatcher := &terraformMatcher.Matcher{}

progressMonitor := trackMatcher(len(packages))
defer progressMonitor.SetCompleted()
Expand All @@ -95,7 +99,15 @@ func FindMatches(store interface {
progressMonitor.PackagesProcessed.Increment()
log.Debugf("searching for eol matches for pkg=%s", p)

pkgMatch, err := defaultMatcher.Match(store, p, eolMatchDate)
var pkgMatch match.Match
var err error

if p.Type == "terraform" {
pkgMatch, err = terraformMatcher.Match(store, p, eolMatchDate)
} else {
pkgMatch, err = defaultMatcher.Match(store, p, eolMatchDate)
}

if err != nil {
log.Debugf("matcher failed for pkg=%s: %+v", p, err)
}
Expand Down
41 changes: 41 additions & 0 deletions xeol/matcher/terraform/matcher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package terraform

import (
"time"

syftPkg "github.com/anchore/syft/syft/pkg"

"github.com/xeol-io/xeol/xeol/eol"
"github.com/xeol-io/xeol/xeol/match"
"github.com/xeol-io/xeol/xeol/pkg"
"github.com/xeol-io/xeol/xeol/search"
)

type Matcher struct {}

type MatcherConfig struct {}

func NewTerraformMatcher(cfg MatcherConfig) *Matcher {
return &Matcher{}
}

func (m *Matcher) PackageTypes() []syftPkg.Type {
// Let's assume Syft might have a generic type or we just intercept Terraform specifically
// "terraform" or "hashicorp" might be the package type returned by Syft's lock file cataloger
return []syftPkg.Type{"terraform"}
}

func (m *Matcher) Type() match.MatcherType {
return match.PackageMatcher
}

func (m *Matcher) Match(store eol.Provider, p pkg.Package, eolMatchDate time.Time) (match.Match, error) {
// If the package is not terraform, don't match
if p.Type != "terraform" {
return match.Match{}, nil
}

// We can mutate the PURL here if needed before passing to store,
// or just rely on search.ByPackagePURL if it matches the DB schema.
return search.ByPackagePURL(store, p, m.Type(), eolMatchDate)
}
Loading