2021-11-29 13:55:28 +00:00
package fetcher
import (
"context"
2023-03-18 16:38:05 +00:00
"io"
2021-11-29 13:55:28 +00:00
"net/http"
"regexp"
"time"
"github.com/pkg/errors"
"github.com/Luzifer/go-latestver/internal/database"
2023-03-18 16:38:05 +00:00
"github.com/Luzifer/go-latestver/internal/helpers"
2021-11-29 13:55:28 +00:00
"github.com/Luzifer/go_helpers/v2/fieldcollection"
)
2021-12-01 16:58:24 +00:00
/ *
* @ module regex
* @ module_desc Fetches URL and applies a regular expression to extract a version from it
* /
2021-11-29 13:55:28 +00:00
const (
httpStatus3xx = 300
regexpFetcherExpectedLength = 2
)
type (
2023-03-18 16:38:05 +00:00
// RegexFetcher implements the fetcher interface to monitor versions on a web request by regex
2021-11-29 13:55:28 +00:00
RegexFetcher struct { }
)
func init ( ) { registerFetcher ( "regex" , func ( ) Fetcher { return & RegexFetcher { } } ) }
2023-03-18 16:38:05 +00:00
// FetchVersion retrieves the latest version for the catalog entry
func ( RegexFetcher ) FetchVersion ( ctx context . Context , attrs * fieldcollection . FieldCollection ) ( string , time . Time , error ) {
2021-11-29 13:55:28 +00:00
req , err := http . NewRequestWithContext ( ctx , http . MethodGet , attrs . MustString ( "url" , nil ) , nil )
if err != nil {
return "" , time . Time { } , errors . Wrap ( err , "creating request" )
}
resp , err := http . DefaultClient . Do ( req )
if err != nil {
return "" , time . Time { } , errors . Wrap ( err , "executing request" )
}
2023-03-18 16:38:05 +00:00
defer func ( ) { helpers . LogIfErr ( resp . Body . Close ( ) , "closing response body after read" ) } ( )
2021-11-29 13:55:28 +00:00
if resp . StatusCode >= httpStatus3xx {
return "" , time . Time { } , errors . Errorf ( "HTTP status %d" , resp . StatusCode )
}
2023-03-18 16:38:05 +00:00
data , err := io . ReadAll ( resp . Body )
2021-11-29 13:55:28 +00:00
if err != nil {
return "" , time . Time { } , errors . Wrap ( err , "reading response body" )
}
matches := regexp . MustCompile ( attrs . MustString ( "regex" , nil ) ) . FindStringSubmatch ( string ( data ) )
if matches == nil {
return "" , time . Time { } , errors . New ( "regex did not match the response" )
}
if l := len ( matches ) ; l != regexpFetcherExpectedLength {
return "" , time . Time { } , errors . Errorf ( "unpexected number of matches: %d != 2" , l )
}
return matches [ 1 ] , time . Now ( ) , nil
}
2023-03-18 16:38:05 +00:00
// Links retrieves a collection of links for the fetcher
func ( RegexFetcher ) Links ( attrs * fieldcollection . FieldCollection ) [ ] database . CatalogLink {
2021-11-29 13:55:28 +00:00
return [ ] database . CatalogLink {
{
IconClass : "fas fa-globe" ,
Name : "Website" ,
URL : attrs . MustString ( "url" , nil ) ,
} ,
}
}
2023-03-18 16:38:05 +00:00
// Validate validates the configuration given to the fetcher
func ( RegexFetcher ) Validate ( attrs * fieldcollection . FieldCollection ) error {
2021-12-01 16:58:24 +00:00
// @attr url required string "" URL to fetch the content from
2021-11-29 13:55:28 +00:00
if v , err := attrs . String ( "url" ) ; err != nil || v == "" {
return errors . New ( "url is expected to be non-empty string" )
}
2021-12-01 16:58:24 +00:00
// @attr regex required string "" Regular expression (RE2) to apply to the text fetched from the URL. The regex MUST have exactly one submatch containing the version.
2021-11-29 13:55:28 +00:00
if v , err := attrs . String ( "regex" ) ; err != nil || v == "" {
return errors . New ( "regex is expected to be non-empty string" )
}
r , err := regexp . Compile ( attrs . MustString ( "regex" , nil ) )
if err != nil {
return errors . Wrap ( err , "compiling regex expression" )
}
if n := r . NumSubexp ( ) ; n != 1 {
return errors . Errorf ( "regex must have 1 submatch, has %d" , n )
}
return nil
}