diff --git a/internal/handler/eolfunctions.go b/internal/handler/eolfunctions.go index 67c6ea4..ea5bf29 100644 --- a/internal/handler/eolfunctions.go +++ b/internal/handler/eolfunctions.go @@ -3,9 +3,11 @@ package handler import ( "encoding/json" "fmt" + "github.com/uselagoon/lagoon/services/insights-handler/internal/lagoonclient" "io/ioutil" "net/http" "os" + "time" ) // eolfunctions.go contains all the basic functionality for checking key facts' end of life status against https://endoflife.date/docs/api @@ -23,8 +25,9 @@ type PackageInfo struct { } type EOLData struct { - Packages map[string][]PackageInfo - CacheLocation string + Packages map[string][]PackageInfo + CacheLocation string + ComparisonDate *time.Time } type NewEOLDataArgs struct { @@ -71,9 +74,60 @@ func NewEOLData(args NewEOLDataArgs) (*EOLData, error) { return nil, err } + timeNow := time.Now() + data.ComparisonDate = &timeNow + return data, nil } +// GenerateProblemsForPackages takes in a map of package names to version (strings) and returns a set of outdated +func (t *EOLData) GenerateProblemsForPackages(packages map[string]string, environmentId int, service string) ([]lagoonclient.LagoonProblem, error) { + var problems []lagoonclient.LagoonProblem + now := time.Now() + for packageName, version := range packages { + packageData, err := t.EolDataForPackage(packageName, version) + if err == nil { + date, err := time.Parse("2006-01-02", packageData.EOL) + if err != nil { + return problems, fmt.Errorf("Unable to parse date '%v' for package information", packageData.EOL) + } + if t.ComparisonDate != nil { + date = *t.ComparisonDate + } + if date.Before(now) { + problems = append(problems, lagoonclient.LagoonProblem{ + Environment: environmentId, + Identifier: fmt.Sprintf("EOL-%v-%v", packageName, version), + Version: version, + FixedVersion: "", + Source: "insights-handler-EOLData", + Service: service, + Data: "{}", + Severity: "", + SeverityScore: 0, + AssociatedPackage: "", + Description: fmt.Sprintf("Package '%v' is at End-of-life as of '%v'", packageName, packageData.EOL), + Links: "", + }) + } + } + } + return problems, nil +} + +func (t *EOLData) EolDataForPackage(packageName, ver string) (PackageInfo, error) { + if packages := t.Packages[packageName]; packages != nil { + for _, p := range packages { + if p.Cycle == ver { + return p, nil + } + } + } else { + return PackageInfo{}, fmt.Errorf("Package '%v' not found in EOL list", packageName) + } + return PackageInfo{}, fmt.Errorf("Package '%v' version '%v' not found in EOL list", packageName, ver) +} + func GetEndOfLifeInfo(packageNames []string) map[string][]PackageInfo { endOfLifeInfo := make(map[string][]PackageInfo) diff --git a/internal/handler/eolfunctions_test.go b/internal/handler/eolfunctions_test.go index 5660896..fffca43 100644 --- a/internal/handler/eolfunctions_test.go +++ b/internal/handler/eolfunctions_test.go @@ -2,9 +2,12 @@ package handler import ( "fmt" + "github.com/uselagoon/lagoon/services/insights-handler/internal/lagoonclient" "os" "path/filepath" + "reflect" "testing" + "time" ) func TestGetEndOfLifeInfo(t *testing.T) { @@ -160,3 +163,165 @@ func TestNewEOLDataWithExistingCache(t *testing.T) { }) } } + +func TestEOLData_EolDataForPackage(t1 *testing.T) { + type fields struct { + Packages []string + CacheLocation string + } + type args struct { + packageName string + ver string + } + tests := []struct { + name string + fields fields + args args + want PackageInfo + wantErr bool + }{ + { + name: "Find basic package", + fields: fields{ + Packages: []string{"alpine"}, + CacheLocation: "testassets/EOLdata/testnocache.json", + }, + args: args{ + packageName: "alpine", + ver: "3.16", + }, + want: PackageInfo{ + Cycle: "3.16", + ReleaseDate: "2022-05-23", + EOL: "2024-05-23", + Latest: "3.16.9", + LatestReleaseDate: "2024-01-26", + Link: "https://alpinelinux.org/posts/Alpine-3.16.9-3.17.7-3.18.6-released.html", + LTS: false, + }, + }, + { + name: "No match", + fields: fields{ + Packages: []string{"alpine"}, + CacheLocation: "testassets/EOLdata/testnocache.json", + }, + args: args{ + packageName: "NOMATCH", + ver: "1.1", + }, + want: PackageInfo{}, + wantErr: true, + }, + } + for _, tt := range tests { + t1.Run(tt.name, func(t1 *testing.T) { + t, err := NewEOLData(NewEOLDataArgs{ + Packages: tt.fields.Packages, + CacheLocation: tt.fields.CacheLocation, + PreventCacheRefresh: true, + ForceCacheRefresh: false, + }) + + if err != nil { + //This shouldn't happen, so let's just die + t1.Errorf("Issue with checking packages - have to exit") + } + + got, err := t.EolDataForPackage(tt.args.packageName, tt.args.ver) + if (err != nil) != tt.wantErr { + t1.Errorf("EolDataForPackage() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t1.Errorf("EolDataForPackage() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestEOLData_GenerateProblemsForPackages(t1 *testing.T) { + type initData struct { + Packages []string + InitTime time.Time + } + type args struct { + packages map[string]string + environmentId int + service string + } + tests := []struct { + name string + initData initData + args args + want []lagoonclient.LagoonProblem + wantErr bool + }{ + { + name: "Check > EOL", + initData: initData{ + Packages: []string{"alpine"}, + InitTime: time.Now(), + }, + args: args{ + packages: map[string]string{ + "alpine": "3.19", + }, + environmentId: 0, + service: "", + }, + wantErr: false, + want: nil, + }, + { + name: "Check < EOL", + initData: initData{ + Packages: []string{"alpine"}, + InitTime: time.Date(1990, time.January, 1, 1, 1, 1, 1, time.Local), + }, + args: args{ + packages: map[string]string{ + "alpine": "3.19", + }, + environmentId: 0, + service: "", + }, + wantErr: false, + want: []lagoonclient.LagoonProblem{ + lagoonclient.LagoonProblem{ + Environment: 0, + Identifier: fmt.Sprintf("EOL-%v-%v", "alpine", "3.19"), + Version: "3.19", + FixedVersion: "", + Source: "insights-handler-EOLData", + Service: "", + Data: "{}", + Severity: "", + SeverityScore: 0, + AssociatedPackage: "", + Description: fmt.Sprintf("Package '%v' is at End-of-life as of '%v'", "alpine", "2025-11-01"), + Links: "", + }, + }, + }, + } + for _, tt := range tests { + t1.Run(tt.name, func(t1 *testing.T) { + t, err := NewEOLData(NewEOLDataArgs{ + Packages: tt.initData.Packages, + CacheLocation: "testassets/EOLdata/cachedata.json", + PreventCacheRefresh: true, + ForceCacheRefresh: false, + }) + + got, err := t.GenerateProblemsForPackages(tt.args.packages, tt.args.environmentId, tt.args.service) + if (err != nil) != tt.wantErr { + t1.Errorf("GenerateProblemsForPackages() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t1.Errorf("GenerateProblemsForPackages() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/handler/testassets/EOLdata/cachedata.json b/internal/handler/testassets/EOLdata/cachedata.json new file mode 100644 index 0000000..223702b --- /dev/null +++ b/internal/handler/testassets/EOLdata/cachedata.json @@ -0,0 +1,123 @@ +{ + "Packages": { + "alpine": [ + { + "cycle": "3.19", + "releaseDate": "2023-12-07", + "eol": "2025-11-01", + "latest": "3.19.1", + "latestReleaseDate": "2024-01-26", + "link": "https://alpinelinux.org/posts/Alpine-3.19.1-released.html", + "lts": false + }, + { + "cycle": "3.18", + "releaseDate": "2023-05-09", + "eol": "2025-05-09", + "latest": "3.18.6", + "latestReleaseDate": "2024-01-26", + "link": "https://alpinelinux.org/posts/Alpine-3.16.9-3.17.7-3.18.6-released.html", + "lts": false + }, + { + "cycle": "3.17", + "releaseDate": "2022-11-22", + "eol": "2024-11-22", + "latest": "3.17.7", + "latestReleaseDate": "2024-01-26", + "link": "https://alpinelinux.org/posts/Alpine-3.16.9-3.17.7-3.18.6-released.html", + "lts": false + }, + { + "cycle": "3.16", + "releaseDate": "2022-05-23", + "eol": "2024-05-23", + "latest": "3.16.9", + "latestReleaseDate": "2024-01-26", + "link": "https://alpinelinux.org/posts/Alpine-3.16.9-3.17.7-3.18.6-released.html", + "lts": false + }, + { + "cycle": "3.15", + "releaseDate": "2021-11-24", + "eol": "2023-11-01", + "latest": "3.15.11", + "latestReleaseDate": "2023-11-30", + "link": "https://alpinelinux.org/posts/Alpine-3.15.10-3.16.7-3.17.5-3.18.3-released.html", + "lts": false + }, + { + "cycle": "3.14", + "releaseDate": "2021-06-15", + "eol": "2023-05-01", + "latest": "3.14.10", + "latestReleaseDate": "2023-03-29", + "link": "https://alpinelinux.org/posts/Alpine-3.14.10-3.15.8-3.16.5-released.html", + "lts": false + }, + { + "cycle": "3.13", + "releaseDate": "2021-01-14", + "eol": "2022-11-01", + "latest": "3.13.12", + "latestReleaseDate": "2022-08-09", + "link": "https://alpinelinux.org/posts/Alpine-3.12.12-3.13.10-3.14.6-3.15.4-released.html", + "lts": false + }, + { + "cycle": "3.12", + "releaseDate": "2020-05-29", + "eol": "2022-05-01", + "latest": "3.12.12", + "latestReleaseDate": "2022-04-04", + "link": "https://alpinelinux.org/posts/Alpine-3.12.12-3.13.10-3.14.6-3.15.4-released.html", + "lts": false + }, + { + "cycle": "3.11", + "releaseDate": "2019-12-19", + "eol": "2021-11-01", + "latest": "3.11.13", + "latestReleaseDate": "2021-11-12", + "link": "https://alpinelinux.org/posts/Alpine-3.11.13-3.12.9-3.13.7-released.html", + "lts": false + }, + { + "cycle": "3.10", + "releaseDate": "2019-06-19", + "eol": "2021-05-01", + "latest": "3.10.9", + "latestReleaseDate": "2021-04-14", + "link": "https://alpinelinux.org/posts/Alpine-3.10.9-3.11.11-3.12.7-released.html", + "lts": false + }, + { + "cycle": "3.9", + "releaseDate": "2019-01-29", + "eol": "2020-11-01", + "latest": "3.9.6", + "latestReleaseDate": "2020-04-23", + "link": "https://alpinelinux.org/posts/Alpine-3.9.6-and-3.10.5-released.html", + "lts": false + }, + { + "cycle": "3.8", + "releaseDate": "2018-06-26", + "eol": "2020-05-01", + "latest": "3.8.5", + "latestReleaseDate": "2020-01-23", + "link": "https://git.alpinelinux.org/aports/log/?h=3.8-stable", + "lts": false + }, + { + "cycle": "3.7", + "releaseDate": "2017-11-30", + "eol": "2019-11-01", + "latest": "3.7.3", + "latestReleaseDate": "2019-03-06", + "link": "https://git.alpinelinux.org/aports/log/?h=3.7-stable", + "lts": false + } + ] + } +} \ No newline at end of file