mirror of
https://github.com/anchore/syft
synced 2024-11-10 06:14:16 +00:00
Add check for app update (#88)
* add check for app update; fix ETUI error handling * validate user args
This commit is contained in:
parent
380cd39514
commit
7ebb9f4e0b
19 changed files with 438 additions and 66 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
.server/
|
||||
.vscode/
|
||||
*.tar
|
||||
*.jar
|
||||
|
|
|
@ -22,7 +22,6 @@ linters:
|
|||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- maligned
|
||||
- misspell
|
||||
- nakedret
|
||||
- nolintlint
|
||||
|
@ -48,6 +47,7 @@ linters:
|
|||
# - gomnd # this is too aggressive
|
||||
# - interfacer # this is a good idea, but is no longer supported and is prone to false positives
|
||||
# - lll # without a way to specify per-line exception cases, this is not usable
|
||||
# - maligned # this is an excellent linter, but tricky to optimize and we are not sensitive to memory layout optimizations
|
||||
# - nestif
|
||||
# - testpackage
|
||||
# - wsl
|
|
@ -38,7 +38,7 @@ func init() {
|
|||
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
log.Errorf("could not start application: %w", err)
|
||||
log.Errorf(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
|
32
cmd/root.go
32
cmd/root.go
|
@ -9,7 +9,9 @@ import (
|
|||
"github.com/anchore/imgbom/imgbom/presenter"
|
||||
"github.com/anchore/imgbom/internal"
|
||||
"github.com/anchore/imgbom/internal/bus"
|
||||
"github.com/anchore/imgbom/internal/log"
|
||||
"github.com/anchore/imgbom/internal/ui"
|
||||
"github.com/anchore/imgbom/internal/version"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/wagoodman/go-partybus"
|
||||
)
|
||||
|
@ -26,9 +28,13 @@ Supports the following image sources:
|
|||
`, map[string]interface{}{
|
||||
"appName": internal.ApplicationName,
|
||||
}),
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
os.Exit(doRunCmd(cmd, args))
|
||||
err := doRunCmd(cmd, args)
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -37,6 +43,21 @@ func startWorker(userInput string) <-chan error {
|
|||
go func() {
|
||||
defer close(errs)
|
||||
|
||||
if appConfig.CheckForAppUpdate {
|
||||
isAvailable, newVersion, err := version.IsUpdateAvailable()
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
}
|
||||
if isAvailable {
|
||||
log.Infof("New version of %s is available: %s", internal.ApplicationName, newVersion)
|
||||
|
||||
bus.Publish(partybus.Event{
|
||||
Type: event.AppUpdateAvailable,
|
||||
Value: newVersion,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
catalog, scope, _, err := imgbom.Catalog(userInput, appConfig.ScopeOpt)
|
||||
if err != nil {
|
||||
errs <- fmt.Errorf("failed to catalog input: %+v", err)
|
||||
|
@ -51,10 +72,9 @@ func startWorker(userInput string) <-chan error {
|
|||
return errs
|
||||
}
|
||||
|
||||
func doRunCmd(_ *cobra.Command, args []string) int {
|
||||
errs := startWorker(args[0])
|
||||
|
||||
func doRunCmd(_ *cobra.Command, args []string) error {
|
||||
userInput := args[0]
|
||||
errs := startWorker(userInput)
|
||||
ux := ui.Select(appConfig.CliOptions.Verbosity > 0, appConfig.Quiet)
|
||||
|
||||
return ux(errs, eventSubscription)
|
||||
}
|
||||
|
|
|
@ -4,16 +4,11 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/anchore/imgbom/internal"
|
||||
"github.com/anchore/imgbom/internal/version"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type Version struct {
|
||||
Version string
|
||||
Commit string
|
||||
BuildTime string
|
||||
}
|
||||
|
||||
var version *Version
|
||||
var showVerboseVersionInfo bool
|
||||
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
|
@ -22,13 +17,23 @@ var versionCmd = &cobra.Command{
|
|||
}
|
||||
|
||||
func init() {
|
||||
versionCmd.Flags().BoolVarP(&showVerboseVersionInfo, "verbose", "v", false, "show additional version information")
|
||||
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
}
|
||||
|
||||
func SetVersion(v *Version) {
|
||||
version = v
|
||||
}
|
||||
|
||||
func printVersion(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("%s %s\n", internal.ApplicationName, version.Version)
|
||||
func printVersion(_ *cobra.Command, _ []string) {
|
||||
versionInfo := version.FromBuild()
|
||||
if showVerboseVersionInfo {
|
||||
fmt.Println("Application: ", internal.ApplicationName)
|
||||
fmt.Println("Version: ", versionInfo.Version)
|
||||
fmt.Println("BuildDate: ", versionInfo.BuildDate)
|
||||
fmt.Println("GitCommit: ", versionInfo.GitCommit)
|
||||
fmt.Println("GitTreeState: ", versionInfo.GitTreeState)
|
||||
fmt.Println("Platform: ", versionInfo.Platform)
|
||||
fmt.Println("GoVersion: ", versionInfo.GoVersion)
|
||||
fmt.Println("Compiler: ", versionInfo.Compiler)
|
||||
} else {
|
||||
fmt.Printf("%s %s\n", internal.ApplicationName, versionInfo.Version)
|
||||
}
|
||||
}
|
||||
|
|
1
go.mod
1
go.mod
|
@ -5,6 +5,7 @@ go 1.14
|
|||
require (
|
||||
github.com/adrg/xdg v0.2.1
|
||||
github.com/anchore/go-testutils v0.0.0-20200624184116-66aa578126db
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b
|
||||
github.com/anchore/stereoscope v0.0.0-20200706164556-7cf39d7f4639
|
||||
github.com/bmatcuk/doublestar v1.3.1
|
||||
github.com/go-test/deep v1.0.6
|
||||
|
|
15
go.sum
15
go.sum
|
@ -124,13 +124,11 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
|
|||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/anchore/go-testutils v0.0.0-20200520222037-edc2bf1864fe h1:YMXe4RA3qy4Ri5fmGQii/Gn+Pxv3oBfiS/LqzeOVuwo=
|
||||
github.com/anchore/go-testutils v0.0.0-20200520222037-edc2bf1864fe/go.mod h1:D3rc2L/q4Hcp9eeX6AIJH4Q+kPjOtJCFhG9za90j+nU=
|
||||
github.com/anchore/go-testutils v0.0.0-20200624184116-66aa578126db h1:LWKezJnFTFxNkZ4MzajVf+YWvJS0+7hwFr59u6SS7cw=
|
||||
github.com/anchore/go-testutils v0.0.0-20200624184116-66aa578126db/go.mod h1:D3rc2L/q4Hcp9eeX6AIJH4Q+kPjOtJCFhG9za90j+nU=
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZVsCYMrIZBpFxwV26CbsuoEh5muXD5I1Ods=
|
||||
github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E=
|
||||
github.com/anchore/stereoscope v0.0.0-20200520221116-025e07f1c93e/go.mod h1:bkyLl5VITnrmgErv4S1vDfVz/TGAZ5il6161IQo7w2g=
|
||||
github.com/anchore/stereoscope v0.0.0-20200624175800-ef5dbfb7cae4 h1:bPd6YFo9VDyoTLVcawFNbW9Z8dQA3M/pCgdD22dR0VQ=
|
||||
github.com/anchore/stereoscope v0.0.0-20200624175800-ef5dbfb7cae4/go.mod h1:f4LZpPnN/5RpQnzcznDsYNeYavFCAW8CpbHN01G3Lh8=
|
||||
github.com/anchore/stereoscope v0.0.0-20200706164556-7cf39d7f4639 h1:J1oytkj+aBuACNF2whtEiVxRXIZ8zwT+EiPTqm/FvwA=
|
||||
github.com/anchore/stereoscope v0.0.0-20200706164556-7cf39d7f4639/go.mod h1:WntReQTI/I27FOQ87UgLVVzWgku6+ZsqfOTLxpIZFCs=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
|
@ -408,6 +406,7 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO
|
|||
github.com/google/go-replayers/grpcreplay v0.1.0/go.mod h1:8Ig2Idjpr6gifRd6pNVggX6TC1Zw6Jx74AKp7QNH2QE=
|
||||
github.com/google/go-replayers/httpreplay v0.1.0/go.mod h1:YKZViNhiGgqdBlUbI2MwGpq4pXxNmhJLPHQ7cv2b5no=
|
||||
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
|
@ -520,6 +519,7 @@ github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgb
|
|||
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
|
@ -612,9 +612,11 @@ github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh
|
|||
github.com/mitchellh/mapstructure v1.3.1 h1:cCBH2gTD2K0OtLlv/Y5H01VQCqmlDxz30kS5Y5bqfLA=
|
||||
github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
|
@ -1200,6 +1202,7 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o=
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
|
||||
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
|
||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.56.0 h1:DPMeDvGTM54DXbPkVIZsp19fp/I2K7zwA/itHYHKo8Y=
|
||||
|
@ -1233,8 +1236,10 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
|
|||
honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
|
||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||
k8s.io/api v0.17.4/go.mod h1:5qxx6vjmwUVG2nHQTKGlLts8Tbok8PzHl4vHtVFuZCA=
|
||||
k8s.io/apimachinery v0.17.4 h1:UzM+38cPUJnzqSQ+E1PY4YxMHIzQyCg29LOoGfo79Zw=
|
||||
k8s.io/apimachinery v0.17.4/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g=
|
||||
k8s.io/apiserver v0.17.4/go.mod h1:5ZDQ6Xr5MNBxyi3iUZXS84QOhZl+W7Oq2us/29c0j9I=
|
||||
k8s.io/client-go v0.17.4 h1:VVdVbpTY70jiNHS1eiFkUt7ZIJX3txd29nDxxXH4en8=
|
||||
k8s.io/client-go v0.17.4/go.mod h1:ouF6o5pz3is8qU0/qYL2RnoxOPqgfuidYLowytyLJmc=
|
||||
k8s.io/cloud-provider v0.17.4/go.mod h1:XEjKDzfD+b9MTLXQFlDGkk6Ho8SGMpaU8Uugx/KNK9U=
|
||||
k8s.io/code-generator v0.17.2/go.mod h1:DVmfPQgxQENqDIzVR2ddLXMH34qeszkKSdH/N+s+38s=
|
||||
|
@ -1244,6 +1249,7 @@ k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8
|
|||
k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
|
||||
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
|
||||
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
|
||||
k8s.io/legacy-cloud-providers v0.17.4/go.mod h1:FikRNoD64ECjkxO36gkDgJeiQWwyZTuBkhu+yxOc1Js=
|
||||
|
@ -1266,6 +1272,7 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
|||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
|
||||
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=
|
||||
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||
sourcegraph.com/sqs/pbtypes v1.0.0 h1:f7lAwqviDEGvON4kRv0o5V7FT/IQK+tbkF664XMbP3o=
|
||||
|
|
|
@ -3,6 +3,7 @@ package event
|
|||
import "github.com/wagoodman/go-partybus"
|
||||
|
||||
const (
|
||||
CatalogerStarted partybus.EventType = "cataloger-started-event"
|
||||
CatalogerFinished partybus.EventType = "cataloger-finished-event"
|
||||
AppUpdateAvailable partybus.EventType = "app-update-available"
|
||||
CatalogerStarted partybus.EventType = "cataloger-started-event"
|
||||
CatalogerFinished partybus.EventType = "cataloger-finished-event"
|
||||
)
|
||||
|
|
|
@ -59,3 +59,16 @@ func ParseCatalogerFinished(e partybus.Event) (presenter.Presenter, error) {
|
|||
|
||||
return pres, nil
|
||||
}
|
||||
|
||||
func ParseAppUpdateAvailable(e partybus.Event) (string, error) {
|
||||
if err := checkEventType(e.Type, event.AppUpdateAvailable); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
newVersion, ok := e.Value.(string)
|
||||
if !ok {
|
||||
return "", newPayloadErr(e.Type, "Value", e.Value)
|
||||
}
|
||||
|
||||
return newVersion, nil
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package imgbom
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/imgbom/imgbom/cataloger"
|
||||
"github.com/anchore/imgbom/imgbom/distro"
|
||||
"github.com/anchore/imgbom/imgbom/logger"
|
||||
|
@ -17,14 +15,14 @@ func Catalog(userInput string, scoptOpt scope.Option) (*pkg.Catalog, *scope.Scop
|
|||
s, cleanup, err := scope.NewScope(userInput, scoptOpt)
|
||||
defer cleanup()
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("failed to create scope: %w", err)
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
d := IdentifyDistro(s)
|
||||
|
||||
catalog, err := CatalogFromScope(s)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("failed to produce catalog: %w", err)
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return catalog, &s, &d, nil
|
||||
|
|
|
@ -20,14 +20,15 @@ type CliOnlyOptions struct {
|
|||
}
|
||||
|
||||
type Application struct {
|
||||
ConfigPath string
|
||||
PresenterOpt presenter.Option
|
||||
Output string `mapstructure:"output"`
|
||||
ScopeOpt scope.Option
|
||||
Scope string `mapstructure:"scope"`
|
||||
Quiet bool `mapstructure:"quiet"`
|
||||
Log Logging `mapstructure:"log"`
|
||||
CliOptions CliOnlyOptions
|
||||
ConfigPath string
|
||||
PresenterOpt presenter.Option
|
||||
Output string `mapstructure:"output"`
|
||||
ScopeOpt scope.Option
|
||||
Scope string `mapstructure:"scope"`
|
||||
Quiet bool `mapstructure:"quiet"`
|
||||
Log Logging `mapstructure:"log"`
|
||||
CliOptions CliOnlyOptions
|
||||
CheckForAppUpdate bool `mapstructure:"check-for-app-update"`
|
||||
}
|
||||
|
||||
type Logging struct {
|
||||
|
@ -41,6 +42,7 @@ func setNonCliDefaultValues(v *viper.Viper) {
|
|||
v.SetDefault("log.level", "")
|
||||
v.SetDefault("log.file", "")
|
||||
v.SetDefault("log.structured", false)
|
||||
v.SetDefault("check-for-app-update", true)
|
||||
}
|
||||
|
||||
func LoadConfigFromFile(v *viper.Viper, cliOpts *CliOnlyOptions) (*Application, error) {
|
||||
|
|
|
@ -32,7 +32,7 @@ func setupScreen(output *os.File) *frame.Frame {
|
|||
}
|
||||
|
||||
// nolint:funlen,gocognit
|
||||
func OutputToEphemeralTUI(workerErrs <-chan error, subscription *partybus.Subscription) int {
|
||||
func OutputToEphemeralTUI(workerErrs <-chan error, subscription *partybus.Subscription) error {
|
||||
output := os.Stderr
|
||||
|
||||
// hide cursor
|
||||
|
@ -42,8 +42,14 @@ func OutputToEphemeralTUI(workerErrs <-chan error, subscription *partybus.Subscr
|
|||
|
||||
fr := setupScreen(output)
|
||||
if fr == nil {
|
||||
return 1
|
||||
return fmt.Errorf("unable to setup screen")
|
||||
}
|
||||
var isClosed bool
|
||||
defer func() {
|
||||
if !isClosed {
|
||||
frame.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
var err error
|
||||
var wg = &sync.WaitGroup{}
|
||||
|
@ -53,39 +59,49 @@ func OutputToEphemeralTUI(workerErrs <-chan error, subscription *partybus.Subscr
|
|||
eventLoop:
|
||||
for {
|
||||
select {
|
||||
case err := <-workerErrs:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case e, ok := <-events:
|
||||
if !ok {
|
||||
// is this unexpected? if so should we indicate this?
|
||||
break eventLoop
|
||||
}
|
||||
switch e.Type {
|
||||
case imgbomEvent.AppUpdateAvailable:
|
||||
err = appUpdateAvailableHandler(ctx, fr, e, wg)
|
||||
if err != nil {
|
||||
log.Errorf("unable to show AppUpdateAvailable event: %+v", err)
|
||||
}
|
||||
|
||||
case stereoscopeEvent.ReadImage:
|
||||
err = imageReadHandler(ctx, fr, e, wg)
|
||||
if err != nil {
|
||||
log.Errorf("unable to show read image event: %+v", err)
|
||||
log.Errorf("unable to show ReadImage event: %+v", err)
|
||||
}
|
||||
|
||||
case stereoscopeEvent.FetchImage:
|
||||
err = imageFetchHandler(ctx, fr, e, wg)
|
||||
if err != nil {
|
||||
log.Errorf("unable to show fetch image event: %+v", err)
|
||||
log.Errorf("unable to show FetchImage event: %+v", err)
|
||||
}
|
||||
|
||||
case imgbomEvent.CatalogerStarted:
|
||||
err = catalogerStartedHandler(ctx, fr, e, wg)
|
||||
if err != nil {
|
||||
log.Errorf("unable to show catalog image start event: %+v", err)
|
||||
log.Errorf("unable to show CatalogerStarted event: %+v", err)
|
||||
}
|
||||
case imgbomEvent.CatalogerFinished:
|
||||
// we may have other background processes still displaying progress, wait for them to
|
||||
// finish before discontinuing dynamic content and showing the final report
|
||||
wg.Wait()
|
||||
frame.Close()
|
||||
isClosed = true
|
||||
fmt.Println()
|
||||
|
||||
err := common.CatalogerFinishedHandler(e)
|
||||
if err != nil {
|
||||
log.Errorf("unable to show catalog image finished event: %+v", err)
|
||||
log.Errorf("unable to show CatalogerFinished event: %+v", err)
|
||||
}
|
||||
|
||||
// this is the last expected event
|
||||
|
@ -99,5 +115,5 @@ eventLoop:
|
|||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -143,3 +143,20 @@ func catalogerStartedHandler(ctx context.Context, fr *frame.Frame, event partybu
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func appUpdateAvailableHandler(_ context.Context, fr *frame.Frame, event partybus.Event, _ *sync.WaitGroup) error {
|
||||
newVersion, err := imgbomEventParsers.ParseAppUpdateAvailable(event)
|
||||
if err != nil {
|
||||
return fmt.Errorf("bad AppUpdateAvailable event: %w", err)
|
||||
}
|
||||
|
||||
line, err := fr.Prepend()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
message := color.Magenta.Sprintf("New Update Available: %s", newVersion)
|
||||
_, _ = io.WriteString(line, message)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -7,17 +7,14 @@ import (
|
|||
"github.com/wagoodman/go-partybus"
|
||||
)
|
||||
|
||||
func LoggerUI(workerErrs <-chan error, subscription *partybus.Subscription) int {
|
||||
var returnCode int
|
||||
|
||||
func LoggerUI(workerErrs <-chan error, subscription *partybus.Subscription) error {
|
||||
events := subscription.Events()
|
||||
eventLoop:
|
||||
for {
|
||||
select {
|
||||
case err := <-workerErrs:
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
returnCode = 1
|
||||
return err
|
||||
}
|
||||
case e, ok := <-events:
|
||||
if !ok {
|
||||
|
@ -38,5 +35,5 @@ eventLoop:
|
|||
}
|
||||
}
|
||||
|
||||
return returnCode
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -4,4 +4,4 @@ import (
|
|||
"github.com/wagoodman/go-partybus"
|
||||
)
|
||||
|
||||
type UI func(<-chan error, *partybus.Subscription) int
|
||||
type UI func(<-chan error, *partybus.Subscription) error
|
||||
|
|
36
internal/version/build.go
Normal file
36
internal/version/build.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
const valueNotProvided = "[not provided]"
|
||||
|
||||
var version = valueNotProvided
|
||||
var gitCommit = valueNotProvided
|
||||
var gitTreeState = valueNotProvided
|
||||
var buildDate = valueNotProvided
|
||||
var platform = fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)
|
||||
|
||||
type Version struct {
|
||||
Version string
|
||||
GitCommit string
|
||||
GitTreeState string
|
||||
BuildDate string
|
||||
GoVersion string
|
||||
Compiler string
|
||||
Platform string
|
||||
}
|
||||
|
||||
func FromBuild() Version {
|
||||
return Version{
|
||||
Version: version,
|
||||
GitCommit: gitCommit,
|
||||
GitTreeState: gitTreeState,
|
||||
BuildDate: buildDate,
|
||||
GoVersion: runtime.Version(),
|
||||
Compiler: runtime.Compiler,
|
||||
Platform: platform,
|
||||
}
|
||||
}
|
68
internal/version/update.go
Normal file
68
internal/version/update.go
Normal file
|
@ -0,0 +1,68 @@
|
|||
package version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
hashiVersion "github.com/anchore/go-version"
|
||||
)
|
||||
|
||||
var latestAppVersionURL = struct {
|
||||
host string
|
||||
path string
|
||||
}{
|
||||
// TODO: set me to release host/path before release
|
||||
host: "https://anchore.io",
|
||||
path: "/imgbom/releases/latest/VERSION",
|
||||
}
|
||||
|
||||
func IsUpdateAvailable() (bool, string, error) {
|
||||
currentVersionStr := FromBuild().Version
|
||||
currentVersion, err := hashiVersion.NewVersion(currentVersionStr)
|
||||
if err != nil {
|
||||
if currentVersionStr == valueNotProvided {
|
||||
// this is the default build arg and should be ignored (this is not an error case)
|
||||
return false, "", nil
|
||||
}
|
||||
return false, "", fmt.Errorf("failed to parse current application version: %w", err)
|
||||
}
|
||||
|
||||
latestVersion, err := fetchLatestApplicationVersion()
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
}
|
||||
|
||||
if latestVersion.GreaterThan(currentVersion) {
|
||||
return true, latestVersion.String(), nil
|
||||
}
|
||||
|
||||
return false, "", nil
|
||||
}
|
||||
|
||||
func fetchLatestApplicationVersion() (*hashiVersion.Version, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, latestAppVersionURL.host+latestAppVersionURL.path, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request for latest version: %w", err)
|
||||
}
|
||||
|
||||
client := http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch latest version: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("HTTP %d on fetching latest version: %s", resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
versionBytes, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read latest version: %w", err)
|
||||
}
|
||||
|
||||
versionStr := strings.TrimSuffix(string(versionBytes), "\n")
|
||||
return hashiVersion.NewVersion(versionStr)
|
||||
}
|
202
internal/version/update_test.go
Normal file
202
internal/version/update_test.go
Normal file
|
@ -0,0 +1,202 @@
|
|||
package version
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
hashiVersion "github.com/anchore/go-version"
|
||||
)
|
||||
|
||||
func TestIsUpdateAvailable(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
buildVersion string
|
||||
latestVersion string
|
||||
code int
|
||||
isAvailable bool
|
||||
newVersion string
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
name: "equal",
|
||||
buildVersion: "1.0.0",
|
||||
latestVersion: "1.0.0",
|
||||
code: 200,
|
||||
isAvailable: false,
|
||||
newVersion: "",
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
name: "hasUpdate",
|
||||
buildVersion: "1.0.0",
|
||||
latestVersion: "1.2.0",
|
||||
code: 200,
|
||||
isAvailable: true,
|
||||
newVersion: "1.2.0",
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
name: "aheadOfLatest",
|
||||
buildVersion: "1.2.0",
|
||||
latestVersion: "1.0.0",
|
||||
code: 200,
|
||||
isAvailable: false,
|
||||
newVersion: "",
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
name: "EmptyUpdate",
|
||||
buildVersion: "1.0.0",
|
||||
latestVersion: "",
|
||||
code: 200,
|
||||
isAvailable: false,
|
||||
newVersion: "",
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "GarbageUpdate",
|
||||
buildVersion: "1.0.0",
|
||||
latestVersion: "hdfjksdhfhkj",
|
||||
code: 200,
|
||||
isAvailable: false,
|
||||
newVersion: "",
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "BadUpdate",
|
||||
buildVersion: "1.0.0",
|
||||
latestVersion: "1.0.",
|
||||
code: 500,
|
||||
isAvailable: false,
|
||||
newVersion: "",
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "NoBuildVersion",
|
||||
buildVersion: valueNotProvided,
|
||||
latestVersion: "1.0.0",
|
||||
code: 200,
|
||||
isAvailable: false,
|
||||
newVersion: "",
|
||||
err: false,
|
||||
},
|
||||
{
|
||||
name: "BadUpdateValidVersion",
|
||||
buildVersion: "1.0.0",
|
||||
latestVersion: "2.0.0",
|
||||
code: 404,
|
||||
isAvailable: false,
|
||||
newVersion: "",
|
||||
err: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// setup mocks
|
||||
// local...
|
||||
version = test.buildVersion
|
||||
// remote...
|
||||
handler := http.NewServeMux()
|
||||
handler.HandleFunc(latestAppVersionURL.path, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(test.code)
|
||||
_, _ = w.Write([]byte(test.latestVersion))
|
||||
})
|
||||
mockSrv := httptest.NewServer(handler)
|
||||
latestAppVersionURL.host = mockSrv.URL
|
||||
defer mockSrv.Close()
|
||||
|
||||
isAvailable, newVersion, err := IsUpdateAvailable()
|
||||
if err != nil && !test.err {
|
||||
t.Fatalf("got error but expected none: %+v", err)
|
||||
} else if err == nil && test.err {
|
||||
t.Fatalf("expected error but got none")
|
||||
}
|
||||
|
||||
if newVersion != test.newVersion {
|
||||
t.Errorf("unexpected NEW version: %+v", newVersion)
|
||||
}
|
||||
|
||||
if isAvailable != test.isAvailable {
|
||||
t.Errorf("unexpected result: %+v", isAvailable)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFetchLatestApplicationVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
response string
|
||||
code int
|
||||
err bool
|
||||
expected *hashiVersion.Version
|
||||
}{
|
||||
{
|
||||
name: "gocase",
|
||||
response: "1.0.0",
|
||||
code: 200,
|
||||
expected: hashiVersion.Must(hashiVersion.NewVersion("1.0.0")),
|
||||
},
|
||||
{
|
||||
name: "garbage",
|
||||
response: "garbage",
|
||||
code: 200,
|
||||
expected: nil,
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "http 500",
|
||||
response: "1.0.0",
|
||||
code: 500,
|
||||
expected: nil,
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "http 404",
|
||||
response: "1.0.0",
|
||||
code: 404,
|
||||
expected: nil,
|
||||
err: true,
|
||||
},
|
||||
{
|
||||
name: "empty",
|
||||
response: "",
|
||||
code: 200,
|
||||
expected: nil,
|
||||
err: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
// setup mock
|
||||
handler := http.NewServeMux()
|
||||
handler.HandleFunc(latestAppVersionURL.path, func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(test.code)
|
||||
_, _ = w.Write([]byte(test.response))
|
||||
})
|
||||
mockSrv := httptest.NewServer(handler)
|
||||
latestAppVersionURL.host = mockSrv.URL
|
||||
defer mockSrv.Close()
|
||||
|
||||
actual, err := fetchLatestApplicationVersion()
|
||||
if err != nil && !test.err {
|
||||
t.Fatalf("got error but expected none: %+v", err)
|
||||
} else if err == nil && test.err {
|
||||
t.Fatalf("expected error but got none")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if actual.String() != test.expected.String() {
|
||||
t.Errorf("unexpected version: %+v", actual.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
12
main.go
12
main.go
|
@ -4,18 +4,6 @@ import (
|
|||
"github.com/anchore/imgbom/cmd"
|
||||
)
|
||||
|
||||
var (
|
||||
version = "No version provided"
|
||||
commit = "No commit provided"
|
||||
buildTime = "No build timestamp provided"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd.SetVersion(&cmd.Version{
|
||||
Version: version,
|
||||
Commit: commit,
|
||||
BuildTime: buildTime,
|
||||
})
|
||||
|
||||
cmd.Execute()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue