diff --git a/.golangci.yml b/.golangci.yml index 15f6269..4a4e877 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -4,9 +4,13 @@ run: linters: enable: + - errcheck - gocyclo - misspell - revive + - govet + - gosec + - staticcheck formatters: enable: - gofmt diff --git a/build/codeCheck.sh b/build/codeCheck.sh index ddd9442..f34ebd2 100755 --- a/build/codeCheck.sh +++ b/build/codeCheck.sh @@ -1,15 +1,24 @@ #!/usr/bin/env bash +set -e -o pipefail ## ensure golangci-lint - https://golangci-lint.run golangci="$(go env GOPATH)/bin/golangci-lint" if ! ${golangci} version &> /dev/null; then echo "Installing golangci-lint binary to ${golangci}" curl -sSfL https://golangci-lint.run/install.sh | sh -s -- -b $(go env GOPATH)/bin - echo "done" fi ## run golangci-lint - all config in .golangci.yml -lintRun=$(${golangci} run --show-stats ./...) -lintRet=$? -echo "golangci-lint ${lintRun}" -exit $lintRet +echo "Running golangci-lint ..." +${golangci} run --show-stats ./... + +## ensure gvulncheck +govulncheck="$(go env GOPATH)/bin/govulncheck" +if ! ${govulncheck} -version &> /dev/null; then + echo "Installing govulncheck binary to ${govulncheck}" + go install golang.org/x/vuln/cmd/govulncheck@latest +fi + +## run govulncheck +echo "Running govulncheck ..." +${govulncheck} -show verbose ./... diff --git a/command/check/command.go b/command/check/command.go index 4f4e2a3..b69785e 100644 --- a/command/check/command.go +++ b/command/check/command.go @@ -24,13 +24,17 @@ type Command struct { // Run is a function to run the command func (c *Command) Run(args []string) int { + var showHelp bool var err error // init flags - if err = c.setupFlags(args); err != nil { + if showHelp, err = c.setupFlags(args); err != nil { c.Log.Error("failed to init flags", "err", err) return 1 } + if showHelp { + return 0 + } // check ip if err = c.checkIP(); err != nil { diff --git a/command/check/util.go b/command/check/util.go index f4cdc73..e4cccc1 100644 --- a/command/check/util.go +++ b/command/check/util.go @@ -11,7 +11,7 @@ import ( ) // setupFlags initializes the instance configuration -func (c *Command) setupFlags(args []string) error { +func (c *Command) setupFlags(args []string) (bool, error) { var cmdFlags *flag.FlagSet // instance flagset var err error @@ -33,17 +33,17 @@ func (c *Command) setupFlags(args []string) error { cmdFlags.StringVar(&c.config.ipbe, "ipbe", "ipify", "IP lookup backend") - // parse flags and ignore error + // parse flags and catch help if err = cmdFlags.Parse(args); err != nil { if stderrors.Is(err, flag.ErrHelp) { - return nil + return true, nil } - return fmt.Errorf("parse check flags: %w", err) + return false, fmt.Errorf("parse check flags: %w", err) } // check for remaining garbage if cmdFlags.NArg() > 0 { - return internalerrors.ErrUnknownArg + return false, internalerrors.ErrUnknownArg } // default to v4 if not specified @@ -53,8 +53,8 @@ func (c *Command) setupFlags(args []string) error { // init ip backend if c.ip, err = internal.GetIPBackend(c.config.ipbe); err != nil { - return fmt.Errorf("init ip backend: %w", err) + return false, fmt.Errorf("init ip backend: %w", err) } - return nil + return false, nil } diff --git a/command/check/util_test.go b/command/check/util_test.go index b598938..bdd3de4 100644 --- a/command/check/util_test.go +++ b/command/check/util_test.go @@ -12,9 +12,13 @@ func TestSetupFlagsDefaultsToIPv4(t *testing.T) { cmd := &Command{Self: "ipman"} - if err := cmd.setupFlags(nil); err != nil { + showHelp, err := cmd.setupFlags(nil) + if err != nil { t.Fatalf("setupFlags() error = %v", err) } + if showHelp { + t.Fatal("setupFlags() unexpectedly requested help") + } if !cmd.config.v4 { t.Fatal("setupFlags() did not default to IPv4") @@ -34,10 +38,13 @@ func TestSetupFlagsReturnsParseError(t *testing.T) { cmd := &Command{Self: "ipman"} - err := cmd.setupFlags([]string{"-unknown"}) + showHelp, err := cmd.setupFlags([]string{"-unknown"}) if err == nil { t.Fatal("setupFlags() error = nil, want parse failure") } + if showHelp { + t.Fatal("setupFlags() unexpectedly requested help") + } if !strings.Contains(err.Error(), "parse check flags") { t.Fatalf("setupFlags() error = %v, want contextual parse error", err) @@ -49,10 +56,13 @@ func TestSetupFlagsRejectsTrailingArgs(t *testing.T) { cmd := &Command{Self: "ipman"} - err := cmd.setupFlags([]string{"extra"}) + showHelp, err := cmd.setupFlags([]string{"extra"}) if err == nil { t.Fatal("setupFlags() error = nil, want trailing arg failure") } + if showHelp { + t.Fatal("setupFlags() unexpectedly requested help") + } if err != internalerrors.ErrUnknownArg { t.Fatalf("setupFlags() error = %v, want %v", err, internalerrors.ErrUnknownArg) diff --git a/command/update/command.go b/command/update/command.go index 9723ea7..88f302e 100644 --- a/command/update/command.go +++ b/command/update/command.go @@ -32,13 +32,17 @@ type Command struct { // Run is a function to run the command func (c *Command) Run(args []string) int { + var showHelp bool var err error // init flags - if err = c.setupFlags(args); err != nil { + if showHelp, err = c.setupFlags(args); err != nil { c.Log.Error("failed to init flags", "err", err) return 1 } + if showHelp { + return 0 + } // attempt to update p if err = c.updateIP(); err != nil { diff --git a/command/update/util.go b/command/update/util.go index 73dd586..36364e7 100644 --- a/command/update/util.go +++ b/command/update/util.go @@ -11,7 +11,7 @@ import ( ) // setupFlags initializes the instance configuration -func (c *Command) setupFlags(args []string) error { +func (c *Command) setupFlags(args []string) (bool, error) { var cmdFlags *flag.FlagSet // instance flagset var err error @@ -45,23 +45,23 @@ func (c *Command) setupFlags(args []string) error { cmdFlags.StringVar(&c.config.dnsbe, "dnsbe", "cloudflare", "DNS update backend") - // parse flags and ignore error + // parse flags and catch help if err = cmdFlags.Parse(args); err != nil { if stderrors.Is(err, flag.ErrHelp) { - return nil + return true, nil } - return fmt.Errorf("parse update flags: %w", err) + return false, fmt.Errorf("parse update flags: %w", err) } // check for remaining garbage if cmdFlags.NArg() > 0 { - return internalerrors.ErrUnknownArg + return false, internalerrors.ErrUnknownArg } // check zone and attempt to get from environment if c.config.zone == "" { if c.config.zone = os.Getenv("IPMAN_DNS_ZONE"); c.config.zone == "" { - return internalerrors.ErrMissingZone + return false, internalerrors.ErrMissingZone } } @@ -82,12 +82,12 @@ func (c *Command) setupFlags(args []string) error { // init ip backend if c.ip, err = internal.GetIPBackend(c.config.ipbe); err != nil { - return fmt.Errorf("init ip backend: %w", err) + return false, fmt.Errorf("init ip backend: %w", err) } // init dns backend if c.dns, err = internal.GetDNSBackend(c.config.dnsbe); err != nil { - return fmt.Errorf("init dns backend: %w", err) + return false, fmt.Errorf("init dns backend: %w", err) } // set backend access key if needed @@ -105,5 +105,5 @@ func (c *Command) setupFlags(args []string) error { c.config.ttl = c.dns.RecordTTL() } - return nil + return false, nil } diff --git a/command/update/util_test.go b/command/update/util_test.go index d2a64f5..5686d09 100644 --- a/command/update/util_test.go +++ b/command/update/util_test.go @@ -12,9 +12,13 @@ func TestSetupFlagsLoadsZoneFromEnv(t *testing.T) { cmd := &Command{Self: "ipman"} - if err := cmd.setupFlags(nil); err != nil { + showHelp, err := cmd.setupFlags(nil) + if err != nil { t.Fatalf("setupFlags() error = %v", err) } + if showHelp { + t.Fatal("setupFlags() unexpectedly requested help") + } if got := cmd.config.zone; got != "example.com" { t.Fatalf("setupFlags() zone = %q, want %q", got, "example.com") @@ -34,10 +38,13 @@ func TestSetupFlagsReturnsParseError(t *testing.T) { cmd := &Command{Self: "ipman"} - err := cmd.setupFlags([]string{"-ttl=bad"}) + showHelp, err := cmd.setupFlags([]string{"-ttl=bad"}) if err == nil { t.Fatal("setupFlags() error = nil, want parse failure") } + if showHelp { + t.Fatal("setupFlags() unexpectedly requested help") + } if !strings.Contains(err.Error(), "parse update flags") { t.Fatalf("setupFlags() error = %v, want contextual parse error", err) @@ -49,10 +56,13 @@ func TestSetupFlagsRequiresZoneWhenUnset(t *testing.T) { cmd := &Command{Self: "ipman"} - err := cmd.setupFlags(nil) + showHelp, err := cmd.setupFlags(nil) if err == nil { t.Fatal("setupFlags() error = nil, want missing zone failure") } + if showHelp { + t.Fatal("setupFlags() unexpectedly requested help") + } if err != internalerrors.ErrMissingZone { t.Fatalf("setupFlags() error = %v, want %v", err, internalerrors.ErrMissingZone) diff --git a/go.mod b/go.mod index 4110c87..7af288b 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/leprechau/ipman -go 1.21 +go 1.25.0 -require github.com/go-resty/resty/v2 v2.13.1 +require github.com/go-resty/resty/v2 v2.17.2 -require golang.org/x/net v0.27.0 // indirect +require golang.org/x/net v0.52.0 // indirect diff --git a/go.sum b/go.sum index a037da7..fcbf2b4 100644 --- a/go.sum +++ b/go.sum @@ -1,50 +1,6 @@ -github.com/go-resty/resty/v2 v2.13.1 h1:x+LHXBI2nMB1vqndymf26quycC4aggYJ7DECYbiz03g= -github.com/go-resty/resty/v2 v2.13.1/go.mod h1:GznXlLxkq6Nh4sU59rPmUw3VtgpO3aS96ORAI6Q7d+0= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +github.com/go-resty/resty/v2 v2.17.2 h1:FQW5oHYcIlkCNrMD2lloGScxcHJ0gkjshV3qcQAyHQk= +github.com/go-resty/resty/v2 v2.17.2/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= diff --git a/internal/dns/cloudflare/config.go b/internal/dns/cloudflare/config.go index ba3f400..5f87969 100644 --- a/internal/dns/cloudflare/config.go +++ b/internal/dns/cloudflare/config.go @@ -1,10 +1,14 @@ package cloudflare import ( + "time" + "github.com/go-resty/resty/v2" "github.com/leprechau/ipman/internal/dns" ) +const requestTimeout = 30 * time.Second + // Config contains backend configuration type Config struct { apiToken string @@ -21,7 +25,8 @@ func DefaultConfig() *Config { recordTTL: 600, client: resty.New(). SetHeader("Accept", "application/json"). - SetBaseURL("https://api.cloudflare.com/client/v4"), + SetBaseURL("https://api.cloudflare.com/client/v4"). + SetTimeout(requestTimeout), } } diff --git a/internal/dns/godaddy/config.go b/internal/dns/godaddy/config.go index d443f4f..cd8bffc 100644 --- a/internal/dns/godaddy/config.go +++ b/internal/dns/godaddy/config.go @@ -1,10 +1,14 @@ package godaddy import ( + "time" + "github.com/go-resty/resty/v2" "github.com/leprechau/ipman/internal/dns" ) +const requestTimeout = 30 * time.Second + // Config contains backend configuration type Config struct { accessKey string @@ -21,7 +25,8 @@ func DefaultConfig() *Config { recordTTL: 600, client: resty.New(). SetHeader("Accept", "application/json"). - SetBaseURL("https://api.godaddy.com/v1"), + SetBaseURL("https://api.godaddy.com/v1"). + SetTimeout(requestTimeout), } } diff --git a/internal/ip/ipify/config.go b/internal/ip/ipify/config.go index 5f2756c..a9469f6 100644 --- a/internal/ip/ipify/config.go +++ b/internal/ip/ipify/config.go @@ -1,10 +1,14 @@ package ipify import ( + "time" + "github.com/go-resty/resty/v2" "github.com/leprechau/ipman/internal/ip" ) +const requestTimeout = 30 * time.Second + // Config contains backend configuration type Config struct { v4URL string @@ -20,6 +24,7 @@ func DefaultConfig() *Config { v4URL: "https://api.ipify.org", v6URL: "https://api6.ipify.org", client: resty.New(). - SetHeader("Accept", "application/json"), + SetHeader("Accept", "application/json"). + SetTimeout(requestTimeout), } }