diff --git a/README.md b/README.md index df00417f..437109b3 100644 --- a/README.md +++ b/README.md @@ -51,11 +51,13 @@ Examples: nancy sleuth -p Gopkg.lock [flags] nancy iq -p Gopkg.lock [flags] + Available Commands: config Setup credentials to use when connecting to services help Help about any command iq Check for vulnerabilities in your Golang dependencies using 'Sonatype's Nexus IQ IQServer' sleuth Check for vulnerabilities in your Golang dependencies using Sonatype's OSS Index + update Check if there are any updates available Flags: -v, -- count Set log level, multiple v's is more verbose @@ -82,8 +84,8 @@ Examples: nancy sleuth -p Gopkg.lock --username your_user --token your_token Flags: - -e, --exclude-vulnerability CveListFlag Comma separated list of CVEs to exclude (default []) - -x, --exclude-vulnerability-file string Path to a file containing newline separated CVEs to be excluded (default "./.nancy-ignore") + -e, --exclude-vulnerability CveListFlag Comma separated list of CVEs or OSS Index IDs to exclude (default []) + -x, --exclude-vulnerability-file string Path to a file containing newline separated CVEs or OSS Index IDs to be excluded (default "./.nancy-ignore") -h, --help help for sleuth -n, --no-color indicate output should not be colorized -o, --output string Styling for output format. json, json-pretty, text, csv (default "text") @@ -485,7 +487,15 @@ Choose `iq` as an option and run through the rest of the config. Once you are do You can see an example of using `nancy` in Travis-CI at [this intentionally vulnerable repo we made](https://github.com/sonatype-nexus-community/intentionally-vulnerable-golang-project). -Nancy as well runs on it self (delicious dog food!) in CircleCI, in a myriad of fashions. You can see how we do that here in [our repo's CircleCI config](https://github.com/sonatype-nexus-community/nancy/blob/main/.circleci/config.yml). +Nancy as well runs on itself (delicious dog food!) in CircleCI, in a myriad of fashions. You can see how we do that here in [our repo's CircleCI config](https://github.com/sonatype-nexus-community/nancy/blob/main/.circleci/config.yml). + + #### Big CI Note: + Nancy will automatically check for newer releases of Nancy, and will prompt you when updates are detected. + The automatic update check will only occur once every 28 hours, and the date stamp of the last update check is stored + in the file: `~/.ossindex/.nancy-config/update_check.yml`. + + If you have a huge CI matrix build, and want to avoid all the builds performing the automatic update check, you may + want to configure your CI build to cache the above directory. ### DISCLAIMER diff --git a/buildversion/version.go b/buildversion/version.go index 458c721e..ab1d618e 100644 --- a/buildversion/version.go +++ b/buildversion/version.go @@ -22,3 +22,12 @@ var ( BuildTime = "" BuildCommit = "" ) + +// PackageManager defines the package manager which was used to install the CLI. +// You can override this value using -X flag to the compiler ldflags. This is +// overridden when we build for Homebrew. +var packageManager = "source" + +func PackageManager() string { + return packageManager +} diff --git a/go.mod b/go.mod index d08cbbd7..a27161e8 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ require ( github.com/Masterminds/semver v0.0.0-20190925130524-317e8cce5480 github.com/Masterminds/vcs v1.13.1 // indirect github.com/armon/go-radix v1.0.0 // indirect + github.com/blang/semver v3.5.1+incompatible github.com/boltdb/bolt v1.3.1 // indirect github.com/common-nighthawk/go-figure v0.0.0-20200609044655-c4b36f998cf2 github.com/fsnotify/fsnotify v1.4.9 // indirect @@ -18,7 +19,8 @@ require ( github.com/mitchellh/mapstructure v1.3.3 // indirect github.com/nightlyone/lockfile v1.0.0 // indirect github.com/pelletier/go-toml v1.8.0 // indirect - github.com/pkg/errors v0.9.1 // indirect + github.com/pkg/errors v0.9.1 + github.com/rhysd/go-github-selfupdate v1.2.2 github.com/sdboyer/constext v0.0.0-20170321163424-836a14457353 // indirect github.com/shopspring/decimal v1.2.0 github.com/sirupsen/logrus v1.6.0 @@ -34,6 +36,7 @@ require ( golang.org/x/sys v0.0.0-20200824131525-c12d262b63d8 // indirect google.golang.org/protobuf v1.25.0 // indirect gopkg.in/ini.v1 v1.60.1 // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c ) // fix vulnerability: CVE-2020-15114 in etcd v3.3.13+incompatible diff --git a/go.sum b/go.sum index 4271633c..bea479fc 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/boltdb/bolt v1.1.0/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= @@ -58,6 +60,7 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -100,6 +103,10 @@ github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo= +github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -134,6 +141,10 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7VjIE6z8dIvMsI4/s+1qr5EL+zoIGev1BQj1eoJ8= +github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jarcoal/httpmock v1.0.5 h1:cHtVEcTxRSX4J0je7mWPfc9BpDpqzXSJ5HbymZmyHck= @@ -194,6 +205,10 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/nightlyone/lockfile v1.0.0 h1:RHep2cFKK4PonZJDdEl4GmkabuhbsRMgk/k3uAmxBiA= github.com/nightlyone/lockfile v1.0.0/go.mod h1:rywoIealpdNse2r832aiD9jRk8ErCatROs6LzC841CI= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I= +github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/package-url/packageurl-go v0.1.0 h1:efWBc98O/dBZRg1pw2xiDzovnlMjCa9NPnfaiBduh8I= github.com/package-url/packageurl-go v0.1.0/go.mod h1:C/ApiuWpmbpni4DIOECf6WCjFUZV7O1Fx7VAzrZHgBw= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -222,7 +237,10 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/recoilme/pudge v1.0.3 h1:h/9dEv5fRqtzM4lnO69kUoN+k7ukxxrW9NGb9ug0grM= github.com/recoilme/pudge v1.0.3/go.mod h1:VMvxBLVkrSStldckzCsETBXox3pfovfrnEchafXk8qA= +github.com/rhysd/go-github-selfupdate v1.2.2 h1:G+mNzkc1wEtpmM6sFS/Ghkeq+ad4Yp6EZEHyp//wGEo= +github.com/rhysd/go-github-selfupdate v1.2.2/go.mod h1:khesvSyKcXDUxeySCedFh621iawCks0dS/QnHPcpCws= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -275,8 +293,12 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tcnksm/go-gitconfig v0.1.2 h1:iiDhRitByXAEyjgBqsKi9QU4o2TNtv9kPP3RgPgXBPw= +github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4UsLGSItWYCpE= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ulikunitz/xz v0.5.5 h1:pFrO0lVpTBXLpYw+pnLj6TbvHuyjXMfjGeCwSqCVwok= +github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -290,6 +312,7 @@ golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586 h1:7KByu05hhLed2MO29w7p1XfZvZ13m8mub3shuVftRs0= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -311,6 +334,7 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -323,9 +347,12 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -338,6 +365,7 @@ golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -379,6 +407,7 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8= golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= @@ -389,8 +418,10 @@ google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEn google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -427,6 +458,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= @@ -434,6 +467,8 @@ gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.60.1 h1:P5y5shSkb0CFe44qEeMBgn8JLow09MP17jlJHanke5g= gopkg.in/ini.v1 v1.60.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= diff --git a/internal/cmd/check.go b/internal/cmd/check.go new file mode 100644 index 00000000..1f5616ac --- /dev/null +++ b/internal/cmd/check.go @@ -0,0 +1,89 @@ +package cmd + +import ( + "fmt" + "github.com/sirupsen/logrus" + "github.com/sonatype-nexus-community/nancy/buildversion" + "github.com/sonatype-nexus-community/nancy/settings" + "github.com/sonatype-nexus-community/nancy/update" + "time" +) + +// For use in checking for newer release version during app startup (not during explicit command to check for update) +func checkForUpdates(gitHubAPI string) error { + updateCheck := &settings.UpdateCheck{ + LastUpdateCheck: time.Time{}, + } + + err := updateCheck.Load() + if err != nil { + return err + } + logLady.WithFields(logrus.Fields{ + "LastUpdateCheck": updateCheck.LastUpdateCheck, + "FileUsed": updateCheck.FileUsed, + }).Trace("updateCheck") + + if update.ShouldCheckForUpdates(updateCheck) { + logAndShowMessage("Checking for updates...") + + logLady.WithFields(logrus.Fields{ + "gitHubAPI": gitHubAPI, + "BuildVersion": buildversion.BuildVersion, + "current version": getVersionNumberSemver(), + "PackageManager": buildversion.PackageManager(), + }).Debug("before CheckForUpdates") + + check, err := update.CheckForUpdates(gitHubAPI, update.NancySlug, getVersionNumberSemver(), buildversion.PackageManager()) + + if err != nil { + logLady.Error("error checking for updates: " + err.Error()) + return err + } + + if !check.Found { + logAndShowMessage("No updates found.") + + updateCheck.LastUpdateCheck = time.Now() + err = updateCheck.WriteToDisk() + return err + } + + if update.IsLatestVersion(check) { + logAndShowMessage("Already up-to-date.") + + updateCheck.LastUpdateCheck = time.Now() + err = updateCheck.WriteToDisk() + return err + } + logLady.Debug(update.DebugVersion(check)) + + logAndShowMessage(update.ReportVersion(check)) + logAndShowMessage(update.HowToUpdate(check)) + logAndShowMessage("\n") // Print a new-line after all of that + + updateCheck.LastUpdateCheck = time.Now() + err = updateCheck.WriteToDisk() + if err != nil { + return err + } + } + + return nil +} + +func getVersionNumberSemver() (currentVersion string) { + // this value will be overridden during release, but for dev, we need a semver compliant value + if //goland:noinspection GoBoolExpressions + buildversion.BuildVersion == "development" { + currentVersion = "0.0.0" + } else { + currentVersion = buildversion.BuildVersion + } + return currentVersion +} + +func logAndShowMessage(message string) { + logLady.Info(message) + fmt.Println(message) +} diff --git a/internal/cmd/check_test.go b/internal/cmd/check_test.go new file mode 100644 index 00000000..a51f1a9c --- /dev/null +++ b/internal/cmd/check_test.go @@ -0,0 +1,35 @@ +package cmd + +import ( + "github.com/blang/semver" + "github.com/sirupsen/logrus/hooks/test" + "github.com/sonatype-nexus-community/nancy/buildversion" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestGetVersionNumberSemver(t *testing.T) { + origBuildVersion := buildversion.BuildVersion + defer func() { + buildversion.BuildVersion = origBuildVersion + }() + + // check default ("development") + semver.MustParse(getVersionNumberSemver()) + + // check explicit "development" + buildversion.BuildVersion = "development" + semver.MustParse(getVersionNumberSemver()) + + buildversion.BuildVersion = "1.2.3" + semver.MustParse(getVersionNumberSemver()) +} + +func TestCheckForUpdates(t *testing.T) { + logLady, _ = test.NewNullLogger() + + // NOTE: will not actually run check unless last_update_check is old or file is removed. + // Still, just having this test helped me find a slug bug. Hey, that rhymes. + // Can add real harness setup/teardown later if desired. + assert.Nil(t, checkForUpdates("")) +} diff --git a/internal/cmd/iq_test.go b/internal/cmd/iq_test.go index be99732a..79768c83 100644 --- a/internal/cmd/iq_test.go +++ b/internal/cmd/iq_test.go @@ -320,7 +320,7 @@ func TestDoIqWithIqServerError(t *testing.T) { typedError, ok := err.(customerrors.ErrorShowLogPath) assert.True(t, ok) - assert.Contains(t, typedError.Error(), "There was an error communicating with Nexus IQ Server to get your internal application ID", typedError.Error()) + assert.Contains(t, typedError.Error(), "There was an error communicating with Nexus IQ Server to get your internal application ID") } func TestDoIqHappyPath(t *testing.T) { diff --git a/internal/cmd/root.go b/internal/cmd/root.go index ef625bac..710ffac3 100644 --- a/internal/cmd/root.go +++ b/internal/cmd/root.go @@ -115,6 +115,10 @@ var rootCmd = &cobra.Command{ Long: `nancy is a tool to check for vulnerabilities in your Golang dependencies, powered by the 'Sonatype OSS Index', and as well, works with Nexus IQ Server, allowing you a smooth experience as a Golang developer, using the best tools in the market!`, + PersistentPreRunE: func(_ *cobra.Command, _ []string) error { + logLady = logger.GetLogger("", configOssi.LogLevel) + return checkForUpdates("") + }, RunE: doRoot, } @@ -131,7 +135,6 @@ func doRoot(cmd *cobra.Command, args []string) (err error) { } }() - logLady = logger.GetLogger("", configOssi.LogLevel) logLady.Info("Nancy parsing config for root command") if configOssi.CleanCache { diff --git a/internal/cmd/update.go b/internal/cmd/update.go new file mode 100644 index 00000000..fa67841a --- /dev/null +++ b/internal/cmd/update.go @@ -0,0 +1,79 @@ +package cmd + +import ( + "github.com/rhysd/go-github-selfupdate/selfupdate" + "github.com/sirupsen/logrus" + "github.com/sonatype-nexus-community/nancy/buildversion" + "github.com/sonatype-nexus-community/nancy/update" + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(updateCmd) +} + +var updateCmd = newUpdateCommand() + +func newUpdateCommand() *cobra.Command { + + updateCmd := &cobra.Command{ + Use: "update", + //Short: "Update the tool to the latest version", + Short: "Check if there are any updates available", + RunE: func(_ *cobra.Command, _ []string) error { + //return updateCLI("", true) + return updateCLI("", false) + }, + } + + return updateCmd +} + +func updateCLI(gitHubAPI string, performUpdate bool) error { + logAndShowMessage("Checking for updates...") + latest, found, err := selfupdate.DetectLatest(update.NancySlug) + if err != nil { + return err + } + if !found { + logLady.Info("did not find latest release for " + update.NancyAppName) + } else { + logLady.WithFields(logrus.Fields{ + "latest release": latest, + }).Debug() + } + + check, err := update.CheckForUpdates(gitHubAPI, update.NancySlug, getVersionNumberSemver(), buildversion.PackageManager()) + if err != nil { + return err + } + logLady.WithField("check results", check).Debug("") + + if !check.Found { + logAndShowMessage("No updates found.") + return nil + } + + if update.IsLatestVersion(check) { + logAndShowMessage("Already up-to-date.") + return nil + } + + logLady.Debug(update.DebugVersion(check)) + logAndShowMessage(update.ReportVersion(check)) + + if !performUpdate { + logAndShowMessage(update.HowToUpdate(check)) + return nil + } + + logAndShowMessage("Installing update...") + message, err := update.InstallLatest(check) + if err != nil { + return err + } + + logAndShowMessage(message) + + return nil +} diff --git a/settings/appsettings.go b/settings/appsettings.go new file mode 100644 index 00000000..29b0bcda --- /dev/null +++ b/settings/appsettings.go @@ -0,0 +1,101 @@ +package settings + +import ( + ossIndexTypes "github.com/sonatype-nexus-community/go-sona-types/ossindex/types" + "gopkg.in/yaml.v3" + "io/ioutil" + "os" + "path" + "path/filepath" + "time" +) + +// UpdateCheck is used to represent settings for checking for updates of the CLI. +type UpdateCheck struct { + LastUpdateCheck time.Time `yaml:"last_update_check"` + FileUsed string `yaml:"-"` +} + +// WriteToDisk will write the last update check to disk by serializing the YAML +func (upd *UpdateCheck) WriteToDisk() error { + enc, err := yaml.Marshal(&upd) + if err != nil { + return err + } + + err = ioutil.WriteFile(upd.FileUsed, enc, 0600) + return err +} + +// Load will read the update check settings from the user's disk and then deserialize it into the current instance. +func (upd *UpdateCheck) Load() error { + appSettingsPath := filepath.Join(AppSettingsPath(), updateCheckFilename()) + + if err := ensureSettingsFileExists(appSettingsPath); err != nil { + return err + } + + upd.FileUsed = appSettingsPath + + content, err := ioutil.ReadFile(appSettingsPath) // #nosec + if err != nil { + return err + } + + err = yaml.Unmarshal(content, &upd) + return err +} + +// Config is used to represent the current state of a CLI instance. +type Config struct { + GitHubAPI string `yaml:"-"` + SkipUpdateCheck bool `yaml:"-"` +} + +const ( + NancyConfigFileName = ".nancy-config" +) + +// settingsPath returns the path of the CLI settings directory +func AppSettingsPath() string { + // TODO: Make this configurable + home, _ := os.UserHomeDir() + return path.Join(home, ossIndexTypes.OssIndexDirName, NancyConfigFileName) +} + +// updateCheckFilename returns the name of the cli update checks file +func updateCheckFilename() string { + return "update_check.yml" +} + +// ensureSettingsFileExists does just that. +func ensureSettingsFileExists(path string) error { + // TODO - handle invalid YAML config files. + + _, err := os.Stat(path) + + if err == nil { + return nil + } + + if !os.IsNotExist(err) { + // Filesystem error + return err + } + + dir := filepath.Dir(path) + + // Create folder + if err = os.MkdirAll(dir, 0700); err != nil { + return err + } + + _, err = os.Create(path) + if err != nil { + return err + } + + err = os.Chmod(path, 0600) + + return err +} diff --git a/update/update.go b/update/update.go new file mode 100644 index 00000000..1faa33c5 --- /dev/null +++ b/update/update.go @@ -0,0 +1,236 @@ +package update + +import ( + "encoding/json" + "fmt" + "github.com/sonatype-nexus-community/nancy/settings" + "os/exec" + "strings" + "time" + + "github.com/blang/semver" + "github.com/pkg/errors" + "github.com/rhysd/go-github-selfupdate/selfupdate" +) + +const ( + //DefaultGithubEnterpriseAPI = "https://api.github.com/" + NancyAppName = "nancy" + NancySlug = "sonatype-nexus-community/" + NancyAppName +) + +// hoursBeforeCheck is used to configure the delay between auto-update checks +var hoursBeforeCheck = 28 + +// ShouldCheckForUpdates tell us if the last update check was more than a day ago +func ShouldCheckForUpdates(upd *settings.UpdateCheck) bool { + diff := time.Since(upd.LastUpdateCheck) + return diff.Hours() >= float64(hoursBeforeCheck) +} + +// CheckForUpdates will check for updates given the proper package manager +func CheckForUpdates(githubAPI, slug, current, packageManager string) (*Options, error) { + var ( + err error + check *Options + ) + + check = &Options{ + Current: semver.MustParse(current), + PackageManager: packageManager, + githubAPI: githubAPI, + slug: slug, + } + + switch check.PackageManager { + case "release": + err = checkFromSource(check) + case "source": + err = checkFromSource(check) + case "homebrew": + err = checkFromHomebrew(check) + } + + return check, err +} + +func checkFromSource(check *Options) error { + updater, err := selfupdate.NewUpdater(selfupdate.Config{ + EnterpriseBaseURL: check.githubAPI, + }) + if err != nil { + return err + } + + check.updater = updater + + err = latestRelease(check) + + return err +} + +func checkFromHomebrew(check *Options) error { + brew, err := findBrew() + if err != nil { + return errors.Wrap(err, "Expected to find `brew` in your $PATH but wasn't able to find it") + } + + command := exec.Command(brew, "outdated", "--json=v2") // #nosec + out, err := command.Output() + if err != nil { + return errors.Wrap(err, "failed to check for updates. `brew outdated --json=v2` returned an error") + } + + var outdated HomebrewOutdated + + err = json.Unmarshal(out, &outdated) + if err != nil { + return errors.Wrap(err, "failed to parse output of `brew outdated --json=v2`") + } + + for _, o := range outdated.Formulae { + if o.Name == NancyAppName { + if len(o.InstalledVersions) > 0 { + check.Current = semver.MustParse(o.InstalledVersions[0]) + } + + check.Latest = &selfupdate.Release{ + Version: semver.MustParse(o.CurrentVersion), + } + + // We found a release so update state of updates check + check.Found = true + } + } + + return nil +} + +func findBrew() (brew string, err error) { + return exec.LookPath("brew") +} + +// HomebrewOutdated wraps the JSON output from running `brew outdated --json=v2` +// We're specifically looking for this kind of structured data from the command: +// +// { +// "formulae": [ +// { +// "name": "nancy", +// "installed_versions": [ +// "0.1.1248" +// ], +// "current_version": "0.1.3923", +// "pinned": false, +// "pinned_version": null +// } +// ], +// "casks": [] +// } +type HomebrewOutdated struct { + Formulae []struct { + Name string `json:"name"` + InstalledVersions []string `json:"installed_versions"` + CurrentVersion string `json:"current_version"` + Pinned bool `json:"pinned"` + PinnedVersion string `json:"pinned_version"` + } `json:"formulae"` +} + +// Options contains everything we need to check for or perform updates of the CLI. +type Options struct { + Current semver.Version + Found bool + Latest *selfupdate.Release + PackageManager string + + updater *selfupdate.Updater + githubAPI string + slug string +} + +// latestRelease will set the last known release as a member on the Options instance. +// We also update options if any releases were found or not. +func latestRelease(opts *Options) error { + latest, found, err := opts.updater.DetectLatest(opts.slug) + opts.Latest = latest + opts.Found = found + + if err != nil { + return errors.Wrap(err, `Failed to query the GitHub API for updates. + +This is most likely due to GitHub rate-limiting on unauthenticated requests. + +To make authenticated requests please: + + 1. Generate a token at https://github.com/settings/tokens + 2. Set the token by either adding it to your ~/.gitconfig or + setting the GITHUB_TOKEN environment variable. + +Instructions for generating a token can be found at: +https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/ + +We call the GitHub releases API to look for new releases. +More information about that API can be found here: https://developer.github.com/v3/repos/releases/ + +`) + } + + return nil +} + +// IsLatestVersion will tell us if the current version is the latest version available +func IsLatestVersion(opts *Options) bool { + if opts.Current.String() == "" || opts.Latest == nil { + return true + } + + return opts.Latest.Version.Equals(opts.Current) +} + +// InstallLatest will execute the updater and replace the current CLI with the latest version available. +func InstallLatest(opts *Options) (string, error) { + release, err := opts.updater.UpdateSelf(opts.Current, opts.slug) + if err != nil { + return "", errors.Wrap(err, "failed to install update") + } + + return fmt.Sprintf("Updated to %s", release.Version), nil +} + +// DebugVersion returns a nicely formatted string representing the state of the current version. +// Intended to be printed to standard error for developers. +func DebugVersion(opts *Options) string { + return strings.Join([]string{ + fmt.Sprintf("Latest version: %s", opts.Latest.Version), + fmt.Sprintf("Published: %s", opts.Latest.PublishedAt), + fmt.Sprintf("Current Version: %s", opts.Current), + }, "\n") +} + +// ReportVersion returns a nicely formatted string representing the state of the current version. +// Intended to be printed to the user. +func ReportVersion(opts *Options) string { + return strings.Join([]string{ + fmt.Sprintf("You are running %s", opts.Current), + fmt.Sprintf("A new release is available (%s)", opts.Latest.Version), + }, "\n") +} + +// HowToUpdate returns a message teaching the user how to update to the latest version. +func HowToUpdate(opts *Options) string { + switch opts.PackageManager { + case "homebrew": + return "You can update with `brew upgrade " + NancyAppName + "`" + case "release": + return "You can update with `" + NancyAppName + " update install`" + case "source": + return strings.Join([]string{ + "You can visit the Github releases page for the CLI to manually download and install:", + "https://github.com/" + NancySlug + "/releases", + }, "\n") + } + + // Do nothing if we don't expect one of the supported package managers above + return "" +} diff --git a/update/update_test.go b/update/update_test.go new file mode 100644 index 00000000..705b80e0 --- /dev/null +++ b/update/update_test.go @@ -0,0 +1,94 @@ +// +// Copyright 2018-present Sonatype Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package update + +import ( + "github.com/blang/semver" + "github.com/rhysd/go-github-selfupdate/selfupdate" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestCheckForUpdatesPackageManagerUnknown(t *testing.T) { + currentSemver := "0.1.2" + check, err := CheckForUpdates("", "", currentSemver, "") + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + expectedSemver := semver.Version{Minor: 1, Patch: 2} + expectedCheck := &Options{Current: expectedSemver} + assert.Equal(t, expectedCheck, check) +} + +func TestCheckForUpdatesPackageManagerBrew(t *testing.T) { + // will fail if "brew" not installed + if _, err := findBrew(); err != nil { + t.Skipf("brew package manager not found: %+v", err) + } + + currentSemver := "0.1.2" + packageManager := "homebrew" + check, err := CheckForUpdates("", "", currentSemver, packageManager) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + expectedSemver := semver.Version{Minor: 1, Patch: 2} + expectedCheck := &Options{Current: expectedSemver, PackageManager: packageManager} + assert.Equal(t, expectedCheck, check) +} + +func TestCheckForUpdatesPackageManagerSourceWithInvalidSlug(t *testing.T) { + currentSemver := "0.1.2" + packageManager := "source" + check, err := CheckForUpdates("", "", currentSemver, packageManager) + assert.Error(t, err) + assert.Contains(t, err.Error(), "Failed to query the GitHub API for updates.") + + expectedSemver := semver.Version{Minor: 1, Patch: 2} + expectedUpdater, err := selfupdate.NewUpdater(selfupdate.Config{}) + assert.Nil(t, err) + expectedCheck := &Options{Current: expectedSemver, PackageManager: packageManager, updater: expectedUpdater} + assert.Equal(t, expectedCheck, check) +} + +func TestCheckForUpdatesPackageManagerSource(t *testing.T) { + currentSemver := "0.1.2" + packageManager := "source" + check, err := CheckForUpdates("", NancySlug, currentSemver, packageManager) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + + assert.True(t, check.Found) + assert.NotNil(t, check.Latest) + assert.NotNil(t, check.Latest.AssetURL) + assert.Equal(t, check.Latest.RepoName, NancyAppName) +} + +func TestCheckForUpdatesPackageManagerRelease(t *testing.T) { + currentSemver := "0.1.2" + packageManager := "release" + check, err := CheckForUpdates("", NancySlug, currentSemver, packageManager) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + + assert.True(t, check.Found) + assert.NotNil(t, check.Latest) + assert.NotNil(t, check.Latest.AssetURL) + assert.Equal(t, check.Latest.RepoName, NancyAppName) +}