Skip to content

Commit

Permalink
First pass at EOL
Browse files Browse the repository at this point in the history
  • Loading branch information
Blaize Kaye committed Apr 14, 2024
1 parent b0ae450 commit ba23bc3
Show file tree
Hide file tree
Showing 3 changed files with 386 additions and 0 deletions.
135 changes: 135 additions & 0 deletions internal/handler/eolfunctions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package handler

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
)

// eolfunctions.go contains all the basic functionality for checking key facts' end of life status against https://endoflife.date/docs/api

const eloCacheLocation = "/tmp/eol.cache"

type PackageInfo struct {
Cycle string `json:"cycle"`
ReleaseDate string `json:"releaseDate"`
EOL string `json:"eol"`
Latest string `json:"latest"`
LatestReleaseDate string `json:"latestReleaseDate"`
Link string `json:"link"`
LTS bool `json:"lts"`
}

type EOLData struct {
Packages map[string][]PackageInfo
CacheLocation string
}

type NewEOLDataArgs struct {
Packages []string
CacheLocation string
PreventCacheRefresh bool
ForceCacheRefresh bool
}

// NewEOLData creates a new EOLData struct with end-of-life information for the provided Packages.
func NewEOLData(args NewEOLDataArgs) (*EOLData, error) {

// basic assertions of logic
if args.ForceCacheRefresh && !args.PreventCacheRefresh {
return nil, fmt.Errorf("You cannot Force Cache Refresh AND Prevent Cache Refresh")
}

packages := args.Packages
cacheLocation := args.CacheLocation
data := &EOLData{
CacheLocation: cacheLocation,
}

// Check if cache file exists
if _, err := os.Stat(data.CacheLocation); err == nil || args.ForceCacheRefresh {
// Cache file exists, load data from file
if err := loadDataFromFile(data.CacheLocation, data); err != nil {
return nil, err
}
} else if os.IsNotExist(err) {
// Cache file does not exist, fetch data and write to file
endOfLifeInfo := GetEndOfLifeInfo(packages)
data.Packages = endOfLifeInfo

// Write to cache file
if err := writeDataToFile(data.CacheLocation, data); err != nil {
return nil, err
}
} else {
// Some other error occurred
return nil, err
}

return data, nil
}

func GetEndOfLifeInfo(packageNames []string) map[string][]PackageInfo {
endOfLifeInfo := make(map[string][]PackageInfo)

for _, packageName := range packageNames {
url := fmt.Sprintf("https://endoflife.date/api/%s.json", packageName)
response, err := http.Get(url)
if err != nil {
fmt.Printf("Error getting end of life info for %s: %v\n", packageName, err)
continue
}
defer response.Body.Close()

body, err := ioutil.ReadAll(response.Body)
if err != nil {
fmt.Printf("Error reading response body for %s: %v\n", packageName, err)
continue
}

var data []PackageInfo
if err := json.Unmarshal(body, &data); err != nil {
fmt.Printf("Error parsing JSON for %s: %v\n", packageName, err)
continue
}

// Assuming the API returns an array of PackageInfo
if len(data) > 0 {
endOfLifeInfo[packageName] = data // Assuming we're interested in the first entry
}
}

return endOfLifeInfo
}

// loadDataFromFile loads data from a file into an EOLData struct.
func loadDataFromFile(filename string, data *EOLData) error {
file, err := os.Open(filename)
if err != nil {
return err
}
defer file.Close()

decoder := json.NewDecoder(file)
if err := decoder.Decode(data); err != nil {
return err
}

return nil
}

// writeDataToFile writes data from an EOLData struct to a file.
func writeDataToFile(filename string, data *EOLData) error {
jsonData, err := json.MarshalIndent(data, "", " ")
if err != nil {
return err
}

if err := ioutil.WriteFile(filename, jsonData, 0644); err != nil {
return err
}

return nil
}
128 changes: 128 additions & 0 deletions internal/handler/eolfunctions_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package handler

import (
"fmt"
"os"
"path/filepath"
"testing"
)

func TestGetEndOfLifeInfo(t *testing.T) {
type args struct {
packageNames []string
}
tests := []struct {
name string
args args
wantResponse bool
}{
{
name: "Get alpine information",
args: args{packageNames: []string{
"alpine",
"ubuntu",
}},
wantResponse: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := GetEndOfLifeInfo(tt.args.packageNames)

if tt.wantResponse == true {
for _, name := range tt.args.packageNames {
if len(got[name]) == 0 {
t.Errorf("Expected data for package %v, got nothing", name)
}
}
}
})
}
}

func TestNewEOLData(t *testing.T) {
type args struct {
EolArgs NewEOLDataArgs
}
tests := []struct {
name string
args args
want *EOLData
wantErr bool
}{
{
name: "Test No Cache",
args: args{
EolArgs: NewEOLDataArgs{
Packages: []string{
"alpine",
},
CacheLocation: "testnocache.json",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Let's set up a temporary location to save the incoming data
dir, err := os.MkdirTemp("", "*-test")
if err != nil {
t.Errorf("Unable to create test directory")
return
}
defer func(path string) {
err := os.RemoveAll(path)
if err != nil {
fmt.Println("Unable to remove directory: ", path)
}
}(dir)
tt.args.EolArgs.CacheLocation = filepath.Join(dir, tt.args.EolArgs.CacheLocation)
got, err := NewEOLData(tt.args.EolArgs)
if (err != nil) != tt.wantErr {
t.Errorf("NewEOLData() error = %v, wantErr %v", err, tt.wantErr)
return
}
if len(got.Packages[tt.args.EolArgs.Packages[0]]) == 0 {
t.Errorf("Could not find any Packages for package '%v'\n", tt.args.EolArgs.Packages[0])
}

})
}
}

func TestNewEOLDataWithExistingCache(t *testing.T) {
type args struct {
EolArgs NewEOLDataArgs
}
tests := []struct {
name string
args args
want *EOLData
wantErr bool
}{
{
name: "Test No Cache",
args: args{
EolArgs: NewEOLDataArgs{
Packages: []string{
"alpine",
},
CacheLocation: "testassets/EOLdata/testnocache.json",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Let's set up a temporary location to save the incoming data
got, err := NewEOLData(tt.args.EolArgs)
if (err != nil) != tt.wantErr {
t.Errorf("NewEOLData() error = %v, wantErr %v", err, tt.wantErr)
return
}
if len(got.Packages[tt.args.EolArgs.Packages[0]]) == 0 {
t.Errorf("Could not find any Packages for package '%v'\n", tt.args.EolArgs.Packages[0])
}
})
}
}
123 changes: 123 additions & 0 deletions internal/handler/testassets/EOLdata/testnocache.json
Original file line number Diff line number Diff line change
@@ -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
}
]
}
}

0 comments on commit ba23bc3

Please sign in to comment.