From 827162dde9c553f9690005d86b64d212af345c36 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Mon, 24 Feb 2025 15:07:28 +0100 Subject: [PATCH 1/9] add logger --- logger/logger.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 logger/logger.go diff --git a/logger/logger.go b/logger/logger.go new file mode 100644 index 0000000..1461150 --- /dev/null +++ b/logger/logger.go @@ -0,0 +1,27 @@ +package logger + +import "log" + +type Logger struct { + Verbose bool +} + +var logger *Logger + +func Get() *Logger { + if logger == nil { + logger = new(Logger) + } + + return logger +} + +func (l *Logger) SetVerbose(value bool) { + l.Verbose = value +} + +func (l *Logger) Logf(format string, v ...any) { + if l.Verbose { + log.Printf(format, v...) + } +} From 892cd62f3e597b73b2ea2bf2db0efca81fb9264a Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Mon, 24 Feb 2025 15:07:37 +0100 Subject: [PATCH 2/9] add logger --- app.go | 10 ++++++++++ checker/certificates.go | 10 +++++++++- checker/domains.go | 25 ++++++++++++++++++++++--- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/app.go b/app.go index a39982f..a45ac26 100644 --- a/app.go +++ b/app.go @@ -3,6 +3,7 @@ package main import ( "github.com/urfave/cli/v2" "gitnet.fr/deblan/expiration-check/checker" + "gitnet.fr/deblan/expiration-check/logger" "gitnet.fr/deblan/expiration-check/render" ) @@ -33,6 +34,11 @@ func App() *cli.App { Value: "table", Usage: "output format: table, csv, tsv, html, json, markdown", }, + &cli.BoolFlag{ + Name: "verbose", + Aliases: []string{"v"}, + Required: false, + }, } return &cli.App{ @@ -45,6 +51,8 @@ func App() *cli.App { Usage: "Checks certificate", Flags: flags, Action: func(c *cli.Context) error { + logger.Get().SetVerbose(c.Bool("verbose")) + render.Render( checker.CheckCertificates(c.StringSlice("domain")), 30, 14, @@ -60,6 +68,8 @@ func App() *cli.App { Aliases: []string{"d", "domains"}, Flags: flags, Action: func(c *cli.Context) error { + logger.Get().SetVerbose(c.Bool("verbose")) + render.Render( checker.CheckDomains(c.StringSlice("domain")), 30, 14, diff --git a/checker/certificates.go b/checker/certificates.go index d0721c6..1ee3caf 100644 --- a/checker/certificates.go +++ b/checker/certificates.go @@ -6,6 +6,8 @@ import ( "math" "strings" "time" + + "gitnet.fr/deblan/expiration-check/logger" ) func FormatDomain(domain string) string { @@ -26,13 +28,19 @@ func CheckCertificate(domain string) Domain { date := conn.ConnectionState().PeerCertificates[0].NotAfter daysLeft := date.Sub(now).Hours() / 24 - return Domain{ + d := Domain{ Name: domain, DaysLeft: math.Floor(daysLeft), Date: date.Format(time.DateTime), } + + logger.Get().Logf(`CheckCertificate: domain="%s" value="%+v"`, domain, d) + + return d } + logger.Get().Logf("CheckCertificate: domain=%s err=%s", domain, err) + return Domain{Name: domain, Failed: true} } diff --git a/checker/domains.go b/checker/domains.go index 119d229..1d975e2 100644 --- a/checker/domains.go +++ b/checker/domains.go @@ -10,6 +10,7 @@ import ( "time" "github.com/likexian/whois" + "gitnet.fr/deblan/expiration-check/logger" ) type RdapResponseData struct { @@ -64,15 +65,23 @@ func RdapCheck(domain, service string) Domain { date, _ := time.Parse(time.RFC3339, event.EventDate) daysLeft := date.Sub(now).Hours() / 24 - return Domain{ + d := Domain{ Name: domain, DaysLeft: math.Floor(daysLeft), Date: date.Format(time.DateTime), } + + logger.Get().Logf(`RdapCheck: domain="%s" value="%+v"`, domain, d) + + return d } } - return Domain{Name: domain, Failed: true} + d := Domain{Name: domain, Failed: true} + + logger.Get().Logf(`RdapCheck: domain="%s" value="%+v"`, domain, d) + + return d } func WhoisCheck(domain string) Domain { @@ -108,16 +117,22 @@ func WhoisCheck(domain string) Domain { if err == nil { daysLeft := date.Sub(now).Hours() / 24 - return Domain{ + d := Domain{ Name: domain, DaysLeft: math.Floor(daysLeft), Date: date.Format(time.DateTime), } + + logger.Get().Logf(`WhoisCheck: domain="%s" value="%+v"`, domain, d) + + return d } } } } + logger.Get().Logf(`WhoisCheck: domain="%s" value="%+v"`, domain, domainFailed) + return domainFailed } @@ -130,8 +145,12 @@ func CheckDomains(domains []string) []Domain { service := services[tld] if service != "" { + logger.Get().Logf(`CheckDomains: domain="%s" rdap=true whois=false`, domain) + values = append(values, RdapCheck(domain, service)) } else { + logger.Get().Logf(`CheckDomains: domain="%s" rdap=false whois=true`, domain) + values = append(values, WhoisCheck(domain)) } } From e1f6b29a033594e20df490e63ed81f9137eda0bd Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Mon, 24 Feb 2025 15:08:33 +0100 Subject: [PATCH 3/9] fix: render days and date when the value is later than danger value --- render/render.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/render/render.go b/render/render.go index d3cd037..eed71eb 100644 --- a/render/render.go +++ b/render/render.go @@ -65,8 +65,8 @@ func Render(values []checker.Domain, warning, danger float64, format string) { var date string if value.DaysLeft <= danger { - days = failed - date = failed + days = RenderColor(fmt.Sprintf("%.0f", value.DaysLeft), text.Colors{0, text.FgRed}, format) + date = RenderColor(value.Date, text.Colors{0, text.FgRed}, format) } else if value.DaysLeft <= warning { days = RenderColor(fmt.Sprintf("%.0f", value.DaysLeft), text.Colors{0, text.FgYellow}, format) date = RenderColor(value.Date, text.Colors{0, text.FgYellow}, format) From 56a09ca38f969f64e88559a680fc824c972e1159 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Mon, 24 Feb 2025 15:09:22 +0100 Subject: [PATCH 4/9] update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0df0213..3211966 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ [Unreleased] +## v1.4.0 +### Added +- add logger and option `-v` +### Fixed +- fix: render days and date when the value is later than danger value + ## v1.3.0 ### Added - add manpage for Debian From b27f7773ca0b2d9b80d796059ecabb8607d402ab Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Mon, 24 Feb 2025 15:11:04 +0100 Subject: [PATCH 5/9] fix typo in CI --- .woodpecker/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.woodpecker/test.yml b/.woodpecker/test.yml index 09fb0ed..81b482f 100644 --- a/.woodpecker/test.yml +++ b/.woodpecker/test.yml @@ -9,7 +9,7 @@ when: - renovate/* steps: - "Check sehll scripts": + "Check shell scripts": image: pipelinecomponents/shellcheck commands: - shellcheck ./bin/*.sh From 0281eea177a8bf11607d2077f0741df7c0d7d249 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Fri, 3 Oct 2025 12:47:15 +0200 Subject: [PATCH 6/9] refactor: refactor archirecture --- checker/certificates.go | 55 ----------- checker/domains.go | 159 ------------------------------- checker/struct.go | 8 -- cmd/main.go | 14 +++ app.go => internal/cmd/app.go | 8 +- main.go | 12 --- pkg/checker/certificate.go | 38 ++++++++ pkg/checker/domain.go | 53 +++++++++++ pkg/extractor/tld.go | 13 +++ pkg/formatter/http_domain.go | 16 ++++ {logger => pkg/logger}/logger.go | 0 pkg/model/result.go | 24 +++++ pkg/rdap/rdap.go | 75 +++++++++++++++ {render => pkg/render}/render.go | 22 ++--- pkg/whois/whois.go | 47 +++++++++ 15 files changed, 295 insertions(+), 249 deletions(-) delete mode 100644 checker/certificates.go delete mode 100644 checker/domains.go delete mode 100644 checker/struct.go create mode 100644 cmd/main.go rename app.go => internal/cmd/app.go (91%) delete mode 100644 main.go create mode 100644 pkg/checker/certificate.go create mode 100644 pkg/checker/domain.go create mode 100644 pkg/extractor/tld.go create mode 100644 pkg/formatter/http_domain.go rename {logger => pkg/logger}/logger.go (100%) create mode 100644 pkg/model/result.go create mode 100644 pkg/rdap/rdap.go rename {render => pkg/render}/render.go (67%) create mode 100644 pkg/whois/whois.go diff --git a/checker/certificates.go b/checker/certificates.go deleted file mode 100644 index 1ee3caf..0000000 --- a/checker/certificates.go +++ /dev/null @@ -1,55 +0,0 @@ -package checker - -import ( - "crypto/tls" - "fmt" - "math" - "strings" - "time" - - "gitnet.fr/deblan/expiration-check/logger" -) - -func FormatDomain(domain string) string { - elements := strings.Split(domain, ":") - - if len(elements) == 1 { - return fmt.Sprintf("%s:443", elements[0]) - } - - return domain -} - -func CheckCertificate(domain string) Domain { - now := time.Now() - conn, err := tls.Dial("tcp", FormatDomain(domain), nil) - - if err == nil { - date := conn.ConnectionState().PeerCertificates[0].NotAfter - daysLeft := date.Sub(now).Hours() / 24 - - d := Domain{ - Name: domain, - DaysLeft: math.Floor(daysLeft), - Date: date.Format(time.DateTime), - } - - logger.Get().Logf(`CheckCertificate: domain="%s" value="%+v"`, domain, d) - - return d - } - - logger.Get().Logf("CheckCertificate: domain=%s err=%s", domain, err) - - return Domain{Name: domain, Failed: true} -} - -func CheckCertificates(domains []string) []Domain { - values := []Domain{} - - for _, domain := range domains { - values = append(values, CheckCertificate(domain)) - } - - return values -} diff --git a/checker/domains.go b/checker/domains.go deleted file mode 100644 index 1d975e2..0000000 --- a/checker/domains.go +++ /dev/null @@ -1,159 +0,0 @@ -package checker - -import ( - "encoding/json" - "fmt" - "math" - "net/http" - "regexp" - "strings" - "time" - - "github.com/likexian/whois" - "gitnet.fr/deblan/expiration-check/logger" -) - -type RdapResponseData struct { - Events []struct { - EventAction string `json:"eventAction"` - EventDate string `json:"eventDate"` - } `json:"events"` -} - -type RdapServices struct { - Services [][][]string `json:"services"` -} - -func GetRdapServices() map[string]string { - response, _ := http.Get("https://data.iana.org/rdap/dns.json") - values := make(map[string]string) - - defer response.Body.Close() - var data RdapServices - json.NewDecoder(response.Body).Decode(&data) - - for _, value := range data.Services { - for _, tld := range value[0] { - values[tld] = value[1][0] - } - } - - return values -} - -func ExtractTld(domain string) string { - elements := strings.Split(domain, ".") - - if len(elements) == 1 { - return elements[0] - } - - return strings.Join(elements[1:], ".") -} - -func RdapCheck(domain, service string) Domain { - url := fmt.Sprintf("%sdomain/%s?jscard=1", service, domain) - response, _ := http.Get(url) - now := time.Now() - - defer response.Body.Close() - var data RdapResponseData - json.NewDecoder(response.Body).Decode(&data) - - for _, event := range data.Events { - if event.EventAction == "expiration" { - date, _ := time.Parse(time.RFC3339, event.EventDate) - daysLeft := date.Sub(now).Hours() / 24 - - d := Domain{ - Name: domain, - DaysLeft: math.Floor(daysLeft), - Date: date.Format(time.DateTime), - } - - logger.Get().Logf(`RdapCheck: domain="%s" value="%+v"`, domain, d) - - return d - } - } - - d := Domain{Name: domain, Failed: true} - - logger.Get().Logf(`RdapCheck: domain="%s" value="%+v"`, domain, d) - - return d -} - -func WhoisCheck(domain string) Domain { - domainFailed := Domain{Name: domain, Failed: true} - result, err := whois.Whois(domain) - if err != nil { - return domainFailed - } - - now := time.Now() - formats := []string{ - "expiration date", - "expiry date", - "expires on", - "paid-till", - "renewal", - "expires", - "domain_datebilleduntil", - "expiration", - "registry expiry", - "registrar registration expiration", - } - - result = strings.ToLower(result) - - for _, format := range formats { - r, _ := regexp.Compile(fmt.Sprintf(`%s\s*:?\s*([^\s]+)`, format)) - - for i, match := range r.FindStringSubmatch(result) { - if i%2 == 1 { - date, err := time.Parse(time.RFC3339, strings.ToUpper(match)) - - if err == nil { - daysLeft := date.Sub(now).Hours() / 24 - - d := Domain{ - Name: domain, - DaysLeft: math.Floor(daysLeft), - Date: date.Format(time.DateTime), - } - - logger.Get().Logf(`WhoisCheck: domain="%s" value="%+v"`, domain, d) - - return d - } - } - } - } - - logger.Get().Logf(`WhoisCheck: domain="%s" value="%+v"`, domain, domainFailed) - - return domainFailed -} - -func CheckDomains(domains []string) []Domain { - values := []Domain{} - services := GetRdapServices() - - for _, domain := range domains { - tld := ExtractTld(domain) - service := services[tld] - - if service != "" { - logger.Get().Logf(`CheckDomains: domain="%s" rdap=true whois=false`, domain) - - values = append(values, RdapCheck(domain, service)) - } else { - logger.Get().Logf(`CheckDomains: domain="%s" rdap=false whois=true`, domain) - - values = append(values, WhoisCheck(domain)) - } - } - - return values -} diff --git a/checker/struct.go b/checker/struct.go deleted file mode 100644 index ffdcfde..0000000 --- a/checker/struct.go +++ /dev/null @@ -1,8 +0,0 @@ -package checker - -type Domain struct { - Name string `json:"name"` - DaysLeft float64 `json:"days"` - Date string `json:"date"` - Failed bool `json:"failed"` -} diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..f436970 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,14 @@ +package main + +import ( + "log" + "os" + + "gitnet.fr/deblan/expiration-check/internal/cmd" +) + +func main() { + if err := cmd.App().Run(os.Args); err != nil { + log.Fatal(err) + } +} diff --git a/app.go b/internal/cmd/app.go similarity index 91% rename from app.go rename to internal/cmd/app.go index a45ac26..f0704f7 100644 --- a/app.go +++ b/internal/cmd/app.go @@ -1,10 +1,10 @@ -package main +package cmd import ( "github.com/urfave/cli/v2" - "gitnet.fr/deblan/expiration-check/checker" - "gitnet.fr/deblan/expiration-check/logger" - "gitnet.fr/deblan/expiration-check/render" + "gitnet.fr/deblan/expiration-check/pkg/checker" + "gitnet.fr/deblan/expiration-check/pkg/logger" + "gitnet.fr/deblan/expiration-check/pkg/render" ) func NormalizeFormat(format string) string { diff --git a/main.go b/main.go deleted file mode 100644 index b2bb647..0000000 --- a/main.go +++ /dev/null @@ -1,12 +0,0 @@ -package main - -import ( - "log" - "os" -) - -func main() { - if err := App().Run(os.Args); err != nil { - log.Fatal(err) - } -} diff --git a/pkg/checker/certificate.go b/pkg/checker/certificate.go new file mode 100644 index 0000000..ccc1f67 --- /dev/null +++ b/pkg/checker/certificate.go @@ -0,0 +1,38 @@ +package checker + +import ( + "crypto/tls" + "math" + "time" + + "gitnet.fr/deblan/expiration-check/pkg/formatter" + "gitnet.fr/deblan/expiration-check/pkg/model" +) + +func CheckCertificate(domain string) *model.Result { + conn, err := tls.Dial("tcp", formatter.ToDomainAndHttpPort(domain), nil) + + if err == nil { + date := conn.ConnectionState().PeerCertificates[0].NotAfter + + result := model.NewResult( + domain, + math.Floor(date.Sub(time.Now()).Hours()/24), + date.Format(time.DateTime), + ) + + return result + } + + return model.NewResultFailed(domain) +} + +func CheckCertificates(domains []string) []*model.Result { + var values []*model.Result + + for _, domain := range domains { + values = append(values, CheckCertificate(domain)) + } + + return values +} diff --git a/pkg/checker/domain.go b/pkg/checker/domain.go new file mode 100644 index 0000000..3b078bf --- /dev/null +++ b/pkg/checker/domain.go @@ -0,0 +1,53 @@ +package checker + +import ( + "math" + "time" + + "gitnet.fr/deblan/expiration-check/pkg/extractor" + "gitnet.fr/deblan/expiration-check/pkg/logger" + "gitnet.fr/deblan/expiration-check/pkg/model" + "gitnet.fr/deblan/expiration-check/pkg/rdap" + "gitnet.fr/deblan/expiration-check/pkg/whois" +) + +func CheckDomains(domains []string) []*model.Result { + var err error + var date *time.Time + var values []*model.Result + + services, err := rdap.GetRdapServices() + + if err != nil { + logger.Get().Logf("GetRdapServices failed: error=%v", err) + + return values + } + + for _, domain := range domains { + tld := extractor.ExtractTld(domain) + service, ok := services[tld] + + if ok { + date, err = rdap.GetExpiration(domain, service) + } else { + date, err = whois.GetExpiration(domain) + } + + if err != nil { + logger.Get().Logf("CheckDomain: domain=%s error=%s", domain, err) + + values = append(values, model.NewResultFailed(domain)) + } else { + daysLeft := math.Floor(date.Sub(time.Now()).Hours() / 24) + + values = append(values, model.NewResult( + domain, + daysLeft, + date.Format(time.DateTime), + )) + } + } + + return values +} diff --git a/pkg/extractor/tld.go b/pkg/extractor/tld.go new file mode 100644 index 0000000..4031eeb --- /dev/null +++ b/pkg/extractor/tld.go @@ -0,0 +1,13 @@ +package extractor + +import "strings" + +func ExtractTld(domain string) string { + elements := strings.Split(domain, ".") + + if len(elements) == 1 { + return elements[0] + } + + return strings.Join(elements[1:], ".") +} diff --git a/pkg/formatter/http_domain.go b/pkg/formatter/http_domain.go new file mode 100644 index 0000000..42dd1e3 --- /dev/null +++ b/pkg/formatter/http_domain.go @@ -0,0 +1,16 @@ +package formatter + +import ( + "fmt" + "strings" +) + +func ToDomainAndHttpPort(domain string) string { + elements := strings.Split(domain, ":") + + if len(elements) == 1 { + return fmt.Sprintf("%s:443", elements[0]) + } + + return domain +} diff --git a/logger/logger.go b/pkg/logger/logger.go similarity index 100% rename from logger/logger.go rename to pkg/logger/logger.go diff --git a/pkg/model/result.go b/pkg/model/result.go new file mode 100644 index 0000000..4384f08 --- /dev/null +++ b/pkg/model/result.go @@ -0,0 +1,24 @@ +package model + +type Result struct { + Name string `json:"name"` + DaysLeft *float64 `json:"days"` + Date *string `json:"date"` + Failed bool `json:"failed"` +} + +func NewResult(name string, daysLeft float64, date string) *Result { + return &Result{ + Name: name, + DaysLeft: &daysLeft, + Date: &date, + Failed: false, + } +} + +func NewResultFailed(name string) *Result { + return &Result{ + Name: name, + Failed: true, + } +} diff --git a/pkg/rdap/rdap.go b/pkg/rdap/rdap.go new file mode 100644 index 0000000..1335d61 --- /dev/null +++ b/pkg/rdap/rdap.go @@ -0,0 +1,75 @@ +package rdap + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "time" +) + +type RdapResponseData struct { + Events []struct { + EventAction string `json:"eventAction"` + EventDate string `json:"eventDate"` + } `json:"events"` +} + +type RdapServices struct { + Services [][][]string `json:"services"` +} + +func GetRdapServices() (map[string]string, error) { + response, err := http.Get("https://data.iana.org/rdap/dns.json") + + if err != nil { + return nil, err + } + + values := make(map[string]string) + + defer response.Body.Close() + var data RdapServices + + json.NewDecoder(response.Body).Decode(&data) + + for _, value := range data.Services { + for _, tld := range value[0] { + values[tld] = value[1][0] + } + } + + return values, nil +} + +func GetExpiration(domain, service string) (*time.Time, error) { + url := fmt.Sprintf("%sdomain/%s?jscard=1", service, domain) + response, err := http.Get(url) + + if err != nil { + return nil, err + } + + defer response.Body.Close() + var data RdapResponseData + + json.NewDecoder(response.Body).Decode(&data) + + actions := []string{"expiration", "Record expires"} + + for _, event := range data.Events { + for _, action := range actions { + if action == event.EventAction { + date, err := time.Parse(time.RFC3339, event.EventDate) + + if err != nil { + return nil, err + } + + return &date, nil + } + } + } + + return nil, errors.New("Expiration date not found") +} diff --git a/render/render.go b/pkg/render/render.go similarity index 67% rename from render/render.go rename to pkg/render/render.go index eed71eb..fded2b0 100644 --- a/render/render.go +++ b/pkg/render/render.go @@ -8,7 +8,7 @@ import ( "github.com/jedib0t/go-pretty/v6/table" "github.com/jedib0t/go-pretty/v6/text" - "gitnet.fr/deblan/expiration-check/checker" + "gitnet.fr/deblan/expiration-check/pkg/model" ) func RenderColor(value string, c text.Colors, format string) string { @@ -19,7 +19,7 @@ func RenderColor(value string, c text.Colors, format string) string { return c.Sprint(value) } -func Render(values []checker.Domain, warning, danger float64, format string) { +func Render(values []*model.Result, warning, danger float64, format string) { sort.SliceStable(values, func(i, j int) bool { if values[i].Failed && values[j].Failed { return values[i].Name < values[j].Name @@ -33,7 +33,7 @@ func Render(values []checker.Domain, warning, danger float64, format string) { return true } - return values[i].DaysLeft < values[j].DaysLeft + return *values[i].DaysLeft < *values[j].DaysLeft }) if format == "json" { @@ -64,15 +64,15 @@ func Render(values []checker.Domain, warning, danger float64, format string) { var days string var date string - if value.DaysLeft <= danger { - days = RenderColor(fmt.Sprintf("%.0f", value.DaysLeft), text.Colors{0, text.FgRed}, format) - date = RenderColor(value.Date, text.Colors{0, text.FgRed}, format) - } else if value.DaysLeft <= warning { - days = RenderColor(fmt.Sprintf("%.0f", value.DaysLeft), text.Colors{0, text.FgYellow}, format) - date = RenderColor(value.Date, text.Colors{0, text.FgYellow}, format) + if *value.DaysLeft <= danger { + days = RenderColor(fmt.Sprintf("%.0f", *value.DaysLeft), text.Colors{0, text.FgRed}, format) + date = RenderColor(*value.Date, text.Colors{0, text.FgRed}, format) + } else if *value.DaysLeft <= warning { + days = RenderColor(fmt.Sprintf("%.0f", *value.DaysLeft), text.Colors{0, text.FgYellow}, format) + date = RenderColor(*value.Date, text.Colors{0, text.FgYellow}, format) } else { - days = RenderColor(fmt.Sprintf("%.0f", value.DaysLeft), text.Colors{0, text.FgGreen}, format) - date = RenderColor(value.Date, text.Colors{0, text.FgGreen}, format) + days = RenderColor(fmt.Sprintf("%.0f", *value.DaysLeft), text.Colors{0, text.FgGreen}, format) + date = RenderColor(*value.Date, text.Colors{0, text.FgGreen}, format) } t.AppendRow(table.Row{ diff --git a/pkg/whois/whois.go b/pkg/whois/whois.go new file mode 100644 index 0000000..afc0617 --- /dev/null +++ b/pkg/whois/whois.go @@ -0,0 +1,47 @@ +package whois + +import ( + "errors" + "fmt" + "regexp" + "strings" + "time" + + w "github.com/likexian/whois" +) + +func GetExpiration(domain string) (*time.Time, error) { + result, err := w.Whois(domain) + + if err != nil { + return nil, err + } + + result = strings.ToLower(result) + formats := []string{ + "expiration date", + "expiry date", + "expires on", + "paid-till", + "renewal", + "expires", + "domain_datebilleduntil", + "expiration", + "registry expiry", + "registrar registration expiration", + } + + for _, format := range formats { + r, _ := regexp.Compile(fmt.Sprintf(`%s\s*:?\s*([^\s]+)`, format)) + + for i, match := range r.FindStringSubmatch(result) { + if i%2 == 1 { + if date, err := time.Parse(time.RFC3339, strings.ToUpper(match)); err == nil { + return &date, nil + } + } + } + } + + return nil, errors.New("Expiration date not found") +} From f9c209e57688df6ddb5ed4ed387de67f2ca369e1 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Fri, 3 Oct 2025 12:47:33 +0200 Subject: [PATCH 7/9] ci: upgrade golang to v1.24 --- .woodpecker/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml index 2a09ff7..44b684f 100644 --- a/.woodpecker/build.yml +++ b/.woodpecker/build.yml @@ -9,7 +9,7 @@ when: - renovate/* variables: - - &golang_image 'golang:1.22' + - &golang_image 'golang:1.24' depends_on: - test From 5e37669ba6fb9058523049df14574ef7f26547a2 Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Fri, 3 Oct 2025 12:50:08 +0200 Subject: [PATCH 8/9] build: update makefile rules according to the refactor --- Makefile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index b895abb..576acde 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,7 @@ $(BIN_LINUX_AMD64): GOOS=$(GO_OS_LINUX) \ CGO_ENABLED=$(CGO_ENABLED) \ $(CC) $(CFLAGS) -ldflags="$(LDFLAGS)" -gcflags="$(GCFLAGS)" -asmflags="$(ASMFLAGS)" \ - -o $(BIN_LINUX_AMD64) . + -o $(BIN_LINUX_AMD64) cmd/main.go .PHONY: $(BIN_LINUX_ARM64) $(BIN_LINUX_ARM64): @@ -59,7 +59,7 @@ $(BIN_LINUX_ARM64): GOOS=$(GO_OS_LINUX) \ CGO_ENABLED=$(CGO_ENABLED) \ $(CC) $(CFLAGS) -ldflags="$(LDFLAGS)" -gcflags="$(GCFLAGS)" -asmflags="$(ASMFLAGS)" \ - -o $(BIN_LINUX_ARM64) . + -o $(BIN_LINUX_ARM64) cmd/main.go .PHONY: $(BIN_WIN_AMD64) $(BIN_WIN_AMD64): @@ -68,7 +68,7 @@ $(BIN_WIN_AMD64): GOOS=$(GO_OS_WIN) \ CGO_ENABLED=$(CGO_ENABLED) \ $(CC) $(CFLAGS) -ldflags="$(LDFLAGS)" -gcflags="$(GCFLAGS)" -asmflags="$(ASMFLAGS)" \ - -o $(BIN_WIN_AMD64) . + -o $(BIN_WIN_AMD64) cmd/main.go .PHONY: $(BIN_WIN_ARM64) $(BIN_WIN_ARM64): @@ -77,7 +77,7 @@ $(BIN_WIN_ARM64): GOOS=$(GO_OS_WIN) \ CGO_ENABLED=$(CGO_ENABLED) \ $(CC) $(CFLAGS) -ldflags="$(LDFLAGS)" -gcflags="$(GCFLAGS)" -asmflags="$(ASMFLAGS)" \ - -o $(BIN_WIN_ARM64) . + -o $(BIN_WIN_ARM64) cmd/main.go .PHONY: $(BIN_DARWIN_AMD64) $(BIN_DARWIN_AMD64): @@ -86,7 +86,7 @@ $(BIN_DARWIN_AMD64): GOOS=$(GO_OS_DARWIN) \ CGO_ENABLED=$(CGO_ENABLED) \ $(CC) $(CFLAGS) -ldflags="$(LDFLAGS)" -gcflags="$(GCFLAGS)" -asmflags="$(ASMFLAGS)" \ - -o $(BIN_DARWIN_AMD64) . + -o $(BIN_DARWIN_AMD64) cmd/main.go .PHONY: $(BIN_DARWIN_ARM64) $(BIN_DARWIN_ARM64): @@ -95,7 +95,7 @@ $(BIN_DARWIN_ARM64): GOOS=$(GO_OS_DARWIN) \ CGO_ENABLED=$(CGO_ENABLED) \ $(CC) $(CFLAGS) -ldflags="$(LDFLAGS)" -gcflags="$(GCFLAGS)" -asmflags="$(ASMFLAGS)" \ - -o $(BIN_DARWIN_ARM64) . + -o $(BIN_DARWIN_ARM64) cmd/main.go .PHONY: $(MAN_OUTPUT) $(MAN_OUTPUT): From cdfa3c8e093ad569ac0580acf84843a28a8bb41f Mon Sep 17 00:00:00 2001 From: Simon Vieille Date: Fri, 3 Oct 2025 12:51:21 +0200 Subject: [PATCH 9/9] update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3211966..e3fb5b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ [Unreleased] +## v2.0.0 +### Changed +- new project archirecture + ## v1.4.0 ### Added - add logger and option `-v`