Skip to content

Commit

Permalink
container scanning implementation (#95)
Browse files Browse the repository at this point in the history
* working --containers arg; more testing to come

* fix linting issues

* go mod tidy

* bug on tag parsing

* change name of package to containers to avoid images.Image struct name

* refactor slightly to use errgroup and only run remote api calls concurrently; added more testing

* more docs

* move sync package to direct imports in go.mod

* move more stuff to direct import section

* Add license header to new files

* more testing

* allow the include-all flag to work with containers

* whoops

* Better cancelling of goroutines and improved docs
  • Loading branch information
lucasreed authored May 9, 2022
1 parent 6a293ff commit ff8ed04
Show file tree
Hide file tree
Showing 11 changed files with 1,009 additions and 26 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ Nova scans your cluster for installed Helm charts, then cross-checks them agains
all known Helm repositories. If it finds an updated version of the chart you're using,
or notices your current version is deprecated, it will let you know.

Nova can also scan your cluster for out of date container images. Find out more in the [docs](https://nova.docs.fairwinds.com).

## Documentation

Check out the [documentation at docs.fairwinds.com](https://nova.docs.fairwinds.com)
Expand Down
65 changes: 61 additions & 4 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,20 @@
package cmd

import (
"context"
"flag"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
"os/signal"
"strings"
"syscall"
"time"

"github.com/fairwindsops/nova/pkg/containers"
nova_helm "github.com/fairwindsops/nova/pkg/helm"
"github.com/fairwindsops/nova/pkg/output"
"github.com/spf13/cobra"
Expand All @@ -41,8 +46,10 @@ var (

func init() {
cobra.OnInitialize(initConfig)
rootCmd.AddCommand(clusterCmd)
rootCmd.AddCommand(genConfigCmd)
rootCmd.AddCommand(
findCmd,
genConfigCmd,
)

rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "Config file to use. If empty, flags will be used instead")
rootCmd.PersistentFlags().String("output-file", "", "Path on local filesystem to write file output to")
Expand Down Expand Up @@ -86,6 +93,23 @@ func init() {
klog.Fatalf("Failed to bind include-all flag: %v", err)
}

findCmd.Flags().Bool("containers", false, "Show old container image versions instead of helm chart versions. There will be no helm output if this flag is set.")
err = viper.BindPFlag("containers", findCmd.Flags().Lookup("containers"))
if err != nil {
klog.Fatalf("Failed to bind containers flag: %v", err)
}
findCmd.Flags().Bool("show-non-semver", false, "When finding container images, show all containers even if they don't follow semver.")
err = viper.BindPFlag("show-non-semver", findCmd.Flags().Lookup("show-non-semver"))
if err != nil {
klog.Fatalf("Failed to bind show-non-semver flag: %v", err)
}

findCmd.Flags().Bool("show-errored-containers", false, "When finding container images, show errors encountered when scanning.")
err = viper.BindPFlag("show-errored-containers", findCmd.Flags().Lookup("show-errored-containers"))
if err != nil {
klog.Fatalf("Failed to bind show-errored-containers flag: %v", err)
}

klog.InitFlags(nil)
_ = flag.Set("alsologtostderr", "true")
_ = flag.Set("logtostderr", "true")
Expand Down Expand Up @@ -172,7 +196,7 @@ var rootCmd = &cobra.Command{
},
}

var clusterCmd = &cobra.Command{
var findCmd = &cobra.Command{
Use: "find",
Short: "Find out-of-date deployed releases.",
Long: "Find deployed helm releases that have updated charts available in chart repos",
Expand All @@ -183,7 +207,40 @@ var clusterCmd = &cobra.Command{
klog.V(5).Infof("Settings: %v", viper.AllSettings())
klog.V(5).Infof("All Keys: %v", viper.AllKeys())

h := nova_helm.NewHelm(viper.GetString("context"))
kubeContext := viper.GetString("context")

if viper.GetBool("containers") {
// Set up a context we can use to cancel all operations to external container registries if we need to
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
signals := make(chan os.Signal, 1)
signal.Notify(signals, os.Interrupt, syscall.SIGTERM)
defer func() {
signal.Stop(signals)
cancel()
}()
go func() {
select {
case <-signals:
fmt.Print("\nCancelling operations to external container registries\n")
cancel()
case <-ctx.Done():
}
}()
showNonSemver := viper.GetBool("show-non-semver")
showErrored := viper.GetBool("show-errored-containers")
includeAll := viper.GetBool("include-all")
iClient := containers.NewClient(kubeContext)
containers, err := iClient.Find(ctx)
if err != nil {
fmt.Println("ERROR during images.Find()", err)
os.Exit(1)
}
out := output.NewContainersOutput(containers.Images, containers.ErrImages, showNonSemver, showErrored, includeAll)
out.Print()
return
}

h := nova_helm.NewHelm(kubeContext)
ahClient, err := nova_helm.NewArtifactHubPackageClient(version)
if err != nil {
klog.Fatalf("error setting up artifact hub client: %s", err)
Expand Down
15 changes: 15 additions & 0 deletions docs/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,18 @@ grafana 2.1.3 3.1.1 true false
metrics-server 2.8.8 2.11.1 true false
nginx-ingress 1.25.0 1.40.3 true false
```

To check for outdated container images, instead of helm releases:

```
$ nova find --containers
Container Name Current Version Old Latest Latest Minor Latest Patch
============== =============== === ====== ============= =============
k8s.gcr.io/coredns/coredns v1.8.0 true v1.8.6 v1.8.6 v1.8.6
k8s.gcr.io/etcd 3.4.13-0 true 3.5.3-0 3.4.13-0 3.4.13-0
k8s.gcr.io/kube-apiserver v1.21.1 true v1.23.6 v1.23.6 v1.21.12
k8s.gcr.io/kube-controller-manager v1.21.1 true v1.23.6 v1.23.6 v1.21.12
k8s.gcr.io/kube-proxy v1.21.1 true v1.23.6 v1.23.6 v1.21.12
k8s.gcr.io/kube-scheduler v1.21.1 true v1.23.6 v1.23.6 v1.21.12
```
71 changes: 60 additions & 11 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,25 @@ nova find --wide
```

## Options
* `-h`, `--help` - help for nova
* `--config` - Pass a config file that can control the remaining settings. Command-line arguments still take precedence
* `--context` - Sets a specific context in the kubeconfig. If blank, uses the currently set context.
* `-d`, `--desired-versions` - A map of `chart=override_version` to override the helm repository when checking.
* `-a`, `--include-all` - Show all charts even if no latest version is found.
* `--output-file` - output JSON to a file
* `-v Level`, `--v Level` - set the log verbosity level where `Level` is a number between 1 and 10.
* `--wide` - show `Chart Name`, `Namespace` and `HelmVersion`
* `--alsologtostderr` - log to standard error as well as files
* `--logtostderr` - log to standard error instead of files
```
Flags:
--containers Show old container image versions instead of helm chart versions. There will be no helm output if this flag is set.
-h, --help help for find
--show-non-semver When finding container images, show all containers even if they don't follow semver.
Global Flags:
--alsologtostderr log to standard error as well as files (default true)
--config string Config file to use. If empty, flags will be used instead
--context string A context to use in the kubeconfig.
-d, --desired-versions stringToString A map of chart=override_version to override the helm repository when checking. (default [])
-a, --include-all Show all charts even if no latest version is found.
--logtostderr log to standard error instead of files (default true)
--output-file string Path on local filesystem to write file output to
--poll-artifacthub When true, polls artifacthub to match against helm releases in the cluster. If false, you must provide a url list via --url/-u. Default is true. (default true)
-u, --url strings URL for a helm chart repo
-v, --v Level number for the log level verbosity
--wide Output chart name and namespace
```

## Generate Config

Expand All @@ -36,7 +45,7 @@ output-file: ""
wide: false
```

## Output
## Helm Scanning Output
Below is sample output for Nova

### CLI
Expand Down Expand Up @@ -84,3 +93,43 @@ redis redis redis 3 15.4.1
]
}
```

## Container Image Output
There are a couple flags that are unique to the container image output.
- `--show-non-semver` will also show any container tags running in the cluster that do not have valid semver versions. By default these are not shown.
- `--show-errored-containers` will show any containers that returned some sort of error when reaching out to the registry and/or when processing the tags.

Below is sample output for Nova when using the `--containers` flag

```
$ nova find --containers
Container Name Current Version Old Latest Latest Minor Latest Patch
============== =============== === ====== ============= =============
k8s.gcr.io/coredns/coredns v1.8.0 true v1.8.6 v1.8.6 v1.8.6
k8s.gcr.io/etcd 3.4.13-0 true 3.5.3-0 3.4.13-0 3.4.13-0
k8s.gcr.io/kube-apiserver v1.21.1 true v1.23.6 v1.23.6 v1.21.12
k8s.gcr.io/kube-controller-manager v1.21.1 true v1.23.6 v1.23.6 v1.21.12
k8s.gcr.io/kube-proxy v1.21.1 true v1.23.6 v1.23.6 v1.21.12
k8s.gcr.io/kube-scheduler v1.21.1 true v1.23.6 v1.23.6 v1.21.12
```

### Container output with errors
When scanning all containers, nova will capture any errors and move on. To show which containers had errors, use the `--show-errored-containers` flag. Output will look like:

```
$ nova find --containers --show-errored-containers
Container Name Current Version Old Latest Latest Minor Latest Patch
============== =============== === ====== ============= =============
k8s.gcr.io/coredns/coredns v1.8.0 true v1.8.6 v1.8.6 v1.8.6
k8s.gcr.io/etcd 3.4.13-0 true 3.5.3-0 3.4.13-0 3.4.13-0
k8s.gcr.io/kube-apiserver v1.21.1 true v1.23.6 v1.23.6 v1.21.12
k8s.gcr.io/kube-controller-manager v1.21.1 true v1.23.6 v1.23.6 v1.21.12
k8s.gcr.io/kube-proxy v1.21.1 true v1.23.6 v1.23.6 v1.21.12
k8s.gcr.io/kube-scheduler v1.21.1 true v1.23.6 v1.23.6 v1.21.12
Errors:
Container Name Error
============== =====
examplething.com/testing:v1.0.0 Get "https://examplething.com/v2/": dial tcp: lookup examplethingert.com: no such host =====
```
17 changes: 14 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ module github.com/fairwindsops/nova
go 1.17

require (
github.com/Masterminds/semver/v3 v3.1.1
github.com/google/go-containerregistry v0.8.0
github.com/mcuadros/go-version v0.0.0-20190830083331-035f6764e8d2
github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.4.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.10.1
github.com/stretchr/testify v1.7.1
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
gopkg.in/yaml.v2 v2.4.0
helm.sh/helm/v3 v3.8.1
k8s.io/api v0.23.5
k8s.io/apimachinery v0.23.5
k8s.io/client-go v0.23.5
k8s.io/klog/v2 v2.60.1
Expand All @@ -24,10 +29,14 @@ require (
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/Masterminds/squirrel v1.5.2 // indirect
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v20.10.12+incompatible // indirect
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v20.10.14+incompatible // indirect
github.com/docker/docker-credential-helpers v0.6.4 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-gorp/gorp/v3 v3.0.2 // indirect
github.com/go-logr/logr v1.2.3 // indirect
Expand All @@ -47,14 +56,17 @@ require (
github.com/lib/pq v1.10.4 // indirect
github.com/magiconair/properties v1.8.5 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rubenv/sql-migrate v1.1.1 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/spf13/afero v1.8.1 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
Expand All @@ -74,7 +86,6 @@ require (
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
k8s.io/api v0.23.5 // indirect
k8s.io/apiextensions-apiserver v0.23.4 // indirect
k8s.io/kube-openapi v0.0.0-20220124234850-424119656bbf // indirect
k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect
Expand Down
Loading

0 comments on commit ff8ed04

Please sign in to comment.