Repository: falcosecurity/driverkit
Pull request #59 (2020-04-24 12:01:48)

docgen tool + markdown docs + cobra 1.0 + version flag + autocompletion for bash, zsh, fish
**What type of PR is this?**


/kind feature



**Any specific area of the project related to this PR?**



/area cmd

/area pkg




**What this PR does / why we need it**:

This PR does several things:

- introduces a `docgen` tool to automatically generate docs for driverkit commands
- introduces `make docs`
- pushes documentation for driverkit commands
- creates the `driverkit --version` flag to print the semver2 version obtained from git (via makefile build)
- creates the `driverkit completion [bash|fish|zsh]` command to obtain autocompletion scripts for your shell
- removes persistent flags from the root command
- strips sensitive info (eg., HOME directory) from generated docs and from help messages
- updates to cobra 1.0.x
- tests the introduced commands

**Which issue(s) this PR fixes**:

<!--
Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
If PR is `kind/failing-tests` or `kind/flaky-test`, please post the related issues/tests in a comment and do not use `Fixes`.
-->

Fixes #

**Special notes for your reviewer**:

**Does this PR introduce a user-facing change?**:

<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below.
If the PR requires additional action from users switching to the new release, prepend the string "action required:".
For example, `action required: change the API interface of the rule engine`.
-->

```release-note
new: version flag
new: completion command to obtain autocomplete for zsh, bash, fish shells
docs: generated documentation for driverkit commands
```


poiana: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by:
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **leodido**
You can assign the PR to them by writing `/assign @leodido` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=falcosecurity%2Fdriverkit).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/falcosecurity/driverkit/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["leodido"]} -->
poiana: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/falcosecurity/driverkit/pull/59#pullrequestreview-399939200" title="Approved">fntlnz</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=falcosecurity%2Fdriverkit).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/falcosecurity/driverkit/blob/master/OWNERS)~~ [fntlnz]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
poiana: LGTM label has been added.  <details>Git tree hash: 2c53586d87bb591a08434f1755bf09f364a38812</details>

Patch:
diff --git a/Makefile b/Makefile
index f7b9317..cccddc7 100644
--- a/Makefile
+++ b/Makefile
@@ -2,8 +2,10 @@ SHELL=/bin/bash -o pipefail
 
 DOCKER ?= docker
 
-COMMIT_NO := $(shell git rev-parse HEAD 2> /dev/null || true)
-GIT_COMMIT := $(if $(shell git status --porcelain --untracked-files=no),${COMMIT_NO}-dirty,${COMMIT_NO})
+GIT_TAG := $(shell git describe --tags --abbrev=0 2> /dev/null || echo "0.0.0")
+COMMITS_FROM_GIT_TAG := $(shell git rev-list ${GIT_TAG}.. --count 2> /dev/null || echo "0")
+COMMIT_NO := $(shell git rev-parse --short HEAD 2> /dev/null || true)
+GIT_COMMIT := $(if $(shell git status --porcelain --untracked-files=no),${COMMIT_NO}.dirty,${COMMIT_NO})
 GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
 GIT_BRANCH_CLEAN := $(shell echo $(GIT_BRANCH) | sed -e "s/[^[:alnum:]]/-/g")
 
@@ -19,7 +21,7 @@ IMAGE_NAME_DRIVERKIT_BRANCH := $(IMAGE_NAME_DRIVERKIT):$(GIT_BRANCH_CLEAN)
 IMAGE_NAME_DRIVERKIT_COMMIT := $(IMAGE_NAME_DRIVERKIT):$(GIT_COMMIT)
 IMAGE_NAME_DRIVERKIT_LATEST := $(IMAGE_NAME_DRIVERKIT):latest
 
-LDFLAGS := -ldflags '-X github.com/falcosecurity/driverkit/pkg/version.buildTime=$(shell date +%s) -X github.com/falcosecurity/driverkit/pkg/version.gitCommit=${GIT_COMMIT} -X github.com/falcosecurity/driverkit/pkg/driverbuilder.builderBaseImage=${IMAGE_NAME_BUILDER_COMMIT}'
+LDFLAGS := -ldflags '-X github.com/falcosecurity/driverkit/pkg/version.buildTime=$(shell date +%s) -X github.com/falcosecurity/driverkit/pkg/version.gitCommit=${GIT_COMMIT} -X github.com/falcosecurity/driverkit/pkg/version.gitTag=${GIT_TAG} -X github.com/falcosecurity/driverkit/pkg/version.commitsFromGitTag=${COMMITS_FROM_GIT_TAG} -X github.com/falcosecurity/driverkit/pkg/driverbuilder.builderBaseImage=${IMAGE_NAME_BUILDER_COMMIT}'
 
 OS_NAME := $(shell uname -s | tr A-Z a-z)
 SQLITE_TAGS := --tags
@@ -32,6 +34,7 @@ endif
 GOTAGS := ${SQLITE_TAGS}
 
 driverkit ?= _output/bin/driverkit
+driverkit_docgen ?= _output/bin/docgen
 
 .PHONY: build
 build: clean ${driverkit}
@@ -82,4 +85,14 @@ push/latest:
 .PHONY: test
 test:
 	go test -v -race ./...
-	go test -v ./cmd
\ No newline at end of file
+	go test -v ./cmd
+
+.PHONY: ${driverkit_docgen}
+${driverkit_docgen}: ${PWD}/docgen
+	go build -v -o $@ $^
+
+.PHONY: docs
+docs: ${driverkit_docgen}
+	$(RM) -R docs/*
+	@mkdir -p docs
+	${driverkit_docgen}
\ No newline at end of file
diff --git a/cmd/completion.go b/cmd/completion.go
new file mode 100644
index 0000000..2a66ddf
--- /dev/null
+++ b/cmd/completion.go
@@ -0,0 +1,77 @@
+package cmd
+
+import (
+	"bytes"
+	"fmt"
+	"os"
+	"strings"
+	"text/template"
+
+	"github.com/spf13/cobra"
+)
+
+var validShells = []string{"bash", "zsh", "fish"}
+
+const (
+	longUsageTemplate = `Generates completion scripts for the following shells: {{.ValidShells}}.
+
+There are two ways to configure your bash shell to load completions for each session.
+
+1. Source the completion script in your ~/.bashrc file
+
+    echo 'source <(driverkit completion bash)' >> ~/.bashrc
+
+2. Add the completion script to /etc/bash_completion.d/ directory
+
+    driverkit completion bash > /etc/bash_completion.d/driverkit
+`
+)
+
+func validateArgs() cobra.PositionalArgs {
+	return func(c *cobra.Command, args []string) error {
+		if len(args) == 0 {
+			return nil
+		}
+		return cobra.ExactValidArgs(1)(c, args)
+	}
+}
+
+// NewCompletionCmd ...
+func NewCompletionCmd() *cobra.Command {
+	var long bytes.Buffer
+	tmpl := template.Must(template.New("long").Parse(longUsageTemplate))
+	tmpl.Execute(&long, map[string]interface{}{
+		"ValidShells": strings.Join(validShells, ", "),
+	})
+	cmdArgs := append(validShells, "help")
+	completionCmd := &cobra.Command{
+		Use:               fmt.Sprintf("completion (%s)", strings.Join(cmdArgs, "|")),
+		Short:             "Generates completion scripts.",
+		Long:              long.String(),
+		Args:              validateArgs(),
+		ValidArgs:         cmdArgs,
+		DisableAutoGenTag: true,
+		Run: func(c *cobra.Command, args []string) {
+			if len(args) == 0 {
+				c.Help()
+				return
+			}
+
+			arg := args[0]
+			switch arg {
+			case "bash":
+				c.Root().GenBashCompletion(os.Stdout)
+				break
+			case "zsh":
+				c.Root().GenZshCompletion(os.Stdout)
+				break
+			case "fish":
+				c.Root().GenFishCompletion(os.Stdout, true)
+			case "help":
+				c.Help()
+			}
+		},
+	}
+
+	return completionCmd
+}
diff --git a/cmd/config_options.go b/cmd/config_options.go
index ffe036d..8a1f114 100644
--- a/cmd/config_options.go
+++ b/cmd/config_options.go
@@ -10,6 +10,7 @@ import (
 )
 
 var validProcessors = []string{"docker", "kubernetes"}
+var aliasProcessors = []string{"docker", "k8s"}
 var configOptions *ConfigOptions
 
 // ConfigOptions represent the persistent configuration flags of driverkit.
diff --git a/cmd/docker.go b/cmd/docker.go
index d54284b..cdd758e 100644
--- a/cmd/docker.go
+++ b/cmd/docker.go
@@ -5,16 +5,17 @@ import (
 	"github.com/sirupsen/logrus"
 	logger "github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
 	"github.com/spf13/viper"
 )
 
-// NewDockerCmd ...
-func NewDockerCmd(rootOpts *RootOptions) *cobra.Command {
+// NewDockerCmd creates the `driverkit docker` command.
+func NewDockerCmd(rootOpts *RootOptions, rootFlags *pflag.FlagSet) *cobra.Command {
 	dockerCmd := &cobra.Command{
 		Use:   "docker",
 		Short: "Build Falco kernel modules and eBPF probes against a docker daemon.",
 		Run: func(c *cobra.Command, args []string) {
-			logrus.WithField("processor", c.Name()).Info("driver building, it will take a few seconds to complete")
+			logrus.WithField("processor", c.Name()).Info("driver building, it will take a few seconds")
 			if !configOptions.DryRun {
 				if err := driverbuilder.NewDockerBuildProcessor(viper.GetInt("timeout")).Start(rootOpts.toBuild()); err != nil {
 					logger.WithError(err).Fatal("exiting")
@@ -22,6 +23,8 @@ func NewDockerCmd(rootOpts *RootOptions) *cobra.Command {
 			}
 		},
 	}
+	// Add root flags
+	dockerCmd.PersistentFlags().AddFlagSet(rootFlags)
 
 	return dockerCmd
 }
diff --git a/cmd/kubernetes.go b/cmd/kubernetes.go
index eb85a94..3638c59 100644
--- a/cmd/kubernetes.go
+++ b/cmd/kubernetes.go
@@ -1,29 +1,47 @@
 package cmd
 
 import (
+	"regexp"
+	"strings"
+
 	"github.com/falcosecurity/driverkit/pkg/driverbuilder"
 	"github.com/falcosecurity/driverkit/pkg/kubernetes/factory"
 	"github.com/sirupsen/logrus"
 	logger "github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
+	"github.com/spf13/pflag"
 	"github.com/spf13/viper"
 	"k8s.io/cli-runtime/pkg/genericclioptions"
 )
 
 // NewKubernetesCmd creates the `driverkit kubernetes` command.
-func NewKubernetesCmd(rootOpts *RootOptions) *cobra.Command {
+func NewKubernetesCmd(rootOpts *RootOptions, rootFlags *pflag.FlagSet) *cobra.Command {
 	kubernetesCmd := &cobra.Command{
-		Use:   "kubernetes",
-		Short: "Build Falco kernel modules and eBPF probes against a Kubernetes cluster.",
+		Use:     "kubernetes",
+		Short:   "Build Falco kernel modules and eBPF probes against a Kubernetes cluster.",
+		Aliases: []string{"k8s"},
 	}
 
-	// Add Kubernetes client Flags
+	// Add Kubernetes client flags
 	configFlags := genericclioptions.NewConfigFlags(false)
 	configFlags.AddFlags(kubernetesCmd.PersistentFlags())
+	// Some styling to make Kubernetes client flags look like they were ours
+	dotEndingRegexp := regexp.MustCompile(`\.$`)
+	upperAfterPointRegexp := regexp.MustCompile(`\. ([A-Z0-9])`)
+	upperAfterCommaRegexp := regexp.MustCompile(`, ([A-Z0-9])`)
+	kubernetesCmd.PersistentFlags().VisitAll(func(f *pflag.Flag) {
+		f.Usage = strings.ToLower(f.Usage[:1]) + f.Usage[1:]
+		f.Usage = dotEndingRegexp.ReplaceAllString(f.Usage, "")
+		f.Usage = upperAfterPointRegexp.ReplaceAllString(f.Usage, ", ${1}")
+		f.Usage = upperAfterCommaRegexp.ReplaceAllStringFunc(f.Usage, strings.ToLower)
+	})
+	// Add root flags
+	kubernetesCmd.PersistentFlags().AddFlagSet(rootFlags)
+
 	kubefactory := factory.NewFactory(configFlags)
 
 	kubernetesCmd.Run = func(cmd *cobra.Command, args []string) {
-		logrus.WithField("processor", cmd.Name()).Info("driver building, it will take a few seconds to complete")
+		logrus.WithField("processor", cmd.Name()).Info("driver building, it will take a few seconds")
 		if !configOptions.DryRun {
 			if err := kubernetesRun(cmd, args, kubefactory, rootOpts); err != nil {
 				logger.WithError(err).Fatal("exiting")
diff --git a/cmd/root.go b/cmd/root.go
index bcf9356..27b978a 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -3,8 +3,12 @@ package cmd
 import (
 	"fmt"
 	"io"
+	"os"
+	"sort"
 	"strings"
 
+	"github.com/falcosecurity/driverkit/pkg/driverbuilder/builder"
+	"github.com/falcosecurity/driverkit/pkg/version"
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
 
@@ -13,7 +17,7 @@ import (
 	"github.com/spf13/viper"
 )
 
-func persistentValidateFunc(rootCommand *cobra.Command, rootOpts *RootOptions) func(c *cobra.Command, args []string) error {
+func persistentValidateFunc(rootCommand *RootCmd, rootOpts *RootOptions) func(c *cobra.Command, args []string) error {
 	return func(c *cobra.Command, args []string) error {
 		// Merge environment variables or config file values into the RootOptions instance
 		skip := map[string]bool{ // do not merge these
@@ -26,7 +30,7 @@ func persistentValidateFunc(rootCommand *cobra.Command, rootOpts *RootOptions) f
 			"output-module": "output.module",
 			"output-probe":  "output.probe",
 		}
-		rootCommand.PersistentFlags().VisitAll(func(f *pflag.Flag) {
+		rootCommand.c.Flags().VisitAll(func(f *pflag.Flag) {
 			if name := f.Name; !skip[name] {
 				value := viper.GetString(name)
 				if value == "" {
@@ -37,13 +41,16 @@ func persistentValidateFunc(rootCommand *cobra.Command, rootOpts *RootOptions) f
 				}
 				// set the value, if any, otherwise let the default
 				if value != "" {
-					rootCommand.PersistentFlags().Set(name, value)
+					rootCommand.c.Flags().Set(name, value)
 				}
 			}
 		})
 
-		// Do not block root or help command to exec disregarding the persistent flags validity
-		if c.Root() != c && c.Name() != "help" {
+		// Avoid sensitive info into default values help line
+		rootCommand.StripSensitive()
+
+		// Do not block root or help command to exec disregarding the root flags validity
+		if c.Root() != c && c.Name() != "help" && c.Name() != "__complete" && c.Name() != "__completeNoDesc" && c.Name() != "completion" {
 			if errs := rootOpts.Validate(); errs != nil {
 				for _, err := range errs {
 					logger.WithError(err).Error("error validating build options")
@@ -69,20 +76,26 @@ func NewRootCmd() *RootCmd {
 		Use:                   "driverkit",
 		Short:                 "A command line tool to build Falco kernel modules and eBPF probes.",
 		ValidArgs:             validProcessors,
+		ArgAliases:            aliasProcessors,
 		Args:                  cobra.OnlyValidArgs,
 		DisableFlagsInUseLine: true,
 		DisableAutoGenTag:     true,
+		Version:               version.String(),
 		Run: func(c *cobra.Command, args []string) {
 			if len(args) == 0 {
-				logger.WithField("processors", validProcessors).Info("specify the processor")
+				logger.WithField("processors", validProcessors).Info("specify a valid processor")
 			}
 			// Fallback to help
 			c.Help()
 		},
 	}
-	rootCmd.PersistentPreRunE = persistentValidateFunc(rootCmd, rootOpts)
+	ret := &RootCmd{
+		c: rootCmd,
+	}
+
+	rootCmd.PersistentPreRunE = persistentValidateFunc(ret, rootOpts)
 
-	flags := rootCmd.PersistentFlags()
+	flags := rootCmd.Flags()
 
 	flags.StringVarP(&configOptions.ConfigFile, "config", "c", configOptions.ConfigFile, "config file path (default $HOME/.driverkit.yaml if exists)")
 	flags.StringVarP(&configOptions.LogLevel, "loglevel", "l", configOptions.LogLevel, "log level")
@@ -99,15 +112,46 @@ func NewRootCmd() *RootCmd {
 
 	viper.BindPFlags(flags)
 
+	// Flag annotations and custom completions
+	rootCmd.MarkFlagFilename("config", viper.SupportedExts...)
+	rootCmd.RegisterFlagCompletionFunc("target", func(c *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+		targets := builder.BuilderByTarget.Targets()
+		sort.Strings(targets)
+		return targets, cobra.ShellCompDirectiveDefault
+	})
+
 	// Subcommands
-	rootCmd.AddCommand(NewKubernetesCmd(rootOpts))
-	rootCmd.AddCommand(NewDockerCmd(rootOpts))
+	rootCmd.AddCommand(NewKubernetesCmd(rootOpts, flags))
+	rootCmd.AddCommand(NewDockerCmd(rootOpts, flags))
+	rootCmd.AddCommand(NewCompletionCmd())
 
-	return &RootCmd{
-		c: rootCmd,
+	ret.StripSensitive()
+
+	return ret
+}
+
+// Sensitive is a list of sensitive environment variable to replace into the help outputs.
+var Sensitive = []string{
+	"HOME",
+}
+
+// StripSensitive removes sensistive info from default values printed into the help messages.
+func (r *RootCmd) StripSensitive() {
+	for _, s := range Sensitive {
+		homeDir := os.Getenv(s)
+		for _, childCommand := range r.c.Commands() {
+			childCommand.Flags().VisitAll(func(f *pflag.Flag) {
+				f.DefValue = strings.ReplaceAll(f.DefValue, homeDir, fmt.Sprintf("$%s", s))
+			})
+		}
 	}
 }
 
+// Command returns the underlying cobra.Command.
+func (r *RootCmd) Command() *cobra.Command {
+	return r.c
+}
+
 // SetOutput sets the main command output writer.
 func (r *RootCmd) SetOutput(w io.Writer) {
 	r.c.SetOut(w)
@@ -136,7 +180,7 @@ func Start() {
 func init() {
 	logger.SetFormatter(&logger.TextFormatter{
 		ForceColors:            true,
-		DisableLevelTruncation: true,
+		DisableLevelTruncation: false,
 		DisableTimestamp:       true,
 	})
 
diff --git a/cmd/root_options.go b/cmd/root_options.go
index 5ab47e6..677e14e 100644
--- a/cmd/root_options.go
+++ b/cmd/root_options.go
@@ -19,7 +19,7 @@ type OutputOptions struct {
 // RootOptions ...
 type RootOptions struct {
 	Architecture     string `default:"x86_64" validate:"required,oneof=x86_64" name:"architecture"`
-	DriverVersion    string `default:"dev" validate:"required,eq=dev|sha1|semver" name:"driver version"`
+	DriverVersion    string `default:"dev" validate:"eq=dev|sha1|semver" name:"driver version"`
 	KernelVersion    uint16 `default:"1" validate:"omitempty,number" name:"kernel version"`
 	KernelRelease    string `validate:"required,ascii" name:"kernel release"`
 	Target           string `validate:"required,target" name:"target"`
diff --git a/docgen/docgen.go b/docgen/docgen.go
new file mode 100644
index 0000000..d2cbf5e
--- /dev/null
+++ b/docgen/docgen.go
@@ -0,0 +1,119 @@
+package main
+
+import (
+	"bytes"
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path"
+	"strings"
+
+	"github.com/falcosecurity/driverkit/cmd"
+	logger "github.com/sirupsen/logrus"
+	"github.com/spf13/cobra/doc"
+)
+
+const outputDir = "docs"
+const websiteTemplate = `---
+title: %s
+weight: %d
+---
+
+`
+
+var (
+	targetWebsite    bool
+	websitePrepender = func(num int) func(filename string) string {
+		total := num
+		return func(filename string) string {
+			num = num - 1
+			title := strings.TrimPrefix(strings.TrimSuffix(strings.ReplaceAll(filename, "_", " "), ".md"), fmt.Sprintf("%s/", outputDir))
+			return fmt.Sprintf(websiteTemplate, title, total-num)
+		}
+	}
+	websiteLinker = func(filename string) string {
+		if filename == "driverkit.md" {
+			return "_index.md"
+		}
+		return filename
+	}
+)
+
+// docgen
+func main() {
+	// Get mode
+	flag.BoolVar(&targetWebsite, "website", targetWebsite, "")
+	flag.Parse()
+
+	// Get root command
+	driverkit := cmd.NewRootCmd()
+	root := driverkit.Command()
+	num := len(root.Commands()) + 1
+
+	// Setup prepender hook
+	prepender := func(num int) func(filename string) string {
+		return func(filename string) string {
+			return ""
+		}
+	}
+	if targetWebsite {
+		prepender = websitePrepender
+	}
+
+	// Setup links hook
+	linker := func(filename string) string {
+		return filename
+	}
+	if targetWebsite {
+		linker = websiteLinker
+	}
+
+	// Generate markdown docs
+	err := doc.GenMarkdownTreeCustom(root, outputDir, prepender(num), linker)
+	if err != nil {
+		logger.WithError(err).Fatal("markdown generation")
+	}
+
+	if targetWebsite {
+		err := os.Rename(path.Join(outputDir, "driverkit.md"), path.Join(outputDir, "_index.md"))
+		if err != nil {
+			logger.WithError(err).Fatal("renaming main docs page")
+		}
+	}
+
+	if err := stripSensitive(); err != nil {
+		logger.WithError(err).Fatal("error replacing sensitive data")
+	}
+}
+
+func stripSensitive() error {
+	f, err := os.Open(outputDir)
+	if err != nil {
+		return err
+	}
+	files, err := f.Readdir(-1)
+	f.Close()
+	if err != nil {
+		return err
+	}
+
+	for _, file := range files {
+		filePath := path.Join(outputDir, file.Name())
+		file, err := ioutil.ReadFile(filePath)
+		if err != nil {
+			return err
+		}
+
+		envMark := []byte{36} // $
+		for _, s := range cmd.Sensitive {
+			target := []byte(os.Getenv(s))
+			file = bytes.ReplaceAll(file, target, append(envMark, []byte(s)...))
+		}
+		if err = ioutil.WriteFile(filePath, file, 0666); err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
diff --git a/docs/driverkit.md b/docs/driverkit.md
new file mode 100644
index 0000000..8ed5dba
--- /dev/null
+++ b/docs/driverkit.md
@@ -0,0 +1,35 @@
+## driverkit
+
+A command line tool to build Falco kernel modules and eBPF probes.
+
+### Synopsis
+
+A command line tool to build Falco kernel modules and eBPF probes.
+
+```
+driverkit
+```
+
+### Options
+
+```
+  -c, --config string             config file path (default $HOME/.driverkit.yaml if exists)
+      --driverversion string      driver version as a git commit hash or as a git tag (default "dev")
+      --dryrun                    do not actually perform the action
+  -h, --help                      help for driverkit
+      --kernelconfigdata string   base64 encoded kernel config data: in some systems it can be found under the /boot directory, in other it is gzip compressed under /proc
+      --kernelrelease string      kernel release to build the module for, it can be found by executing 'uname -v'
+      --kernelversion uint16      kernel version to build the module for, it's the numeric value after the hash when you execute 'uname -v' (default 1)
+  -l, --loglevel string           log level (default "info")
+      --output-module string      filepath where to save the resulting kernel module
+      --output-probe string       filepath where to save the resulting eBPF probe
+  -t, --target string             the system to target the build for
+      --timeout int               timeout in seconds (default 60)
+```
+
+### SEE ALSO
+
+* [driverkit completion](driverkit_completion.md)	 - Generates completion scripts.
+* [driverkit docker](driverkit_docker.md)	 - Build Falco kernel modules and eBPF probes against a docker daemon.
+* [driverkit kubernetes](driverkit_kubernetes.md)	 - Build Falco kernel modules and eBPF probes against a Kubernetes cluster.
+
diff --git a/docs/driverkit_completion.md b/docs/driverkit_completion.md
new file mode 100644
index 0000000..b2fdbfb
--- /dev/null
+++ b/docs/driverkit_completion.md
@@ -0,0 +1,33 @@
+## driverkit completion
+
+Generates completion scripts.
+
+### Synopsis
+
+Generates completion scripts for the following shells: bash, zsh, fish.
+
+There are two ways to configure your bash shell to load completions for each session.
+
+1. Source the completion script in your ~/.bashrc file
+
+    echo 'source <(driverkit completion bash)' >> ~/.bashrc
+
+2. Add the completion script to /etc/bash_completion.d/ directory
+
+    driverkit completion bash > /etc/bash_completion.d/driverkit
+
+
+```
+driverkit completion (bash|zsh|fish|help) [flags]
+```
+
+### Options
+
+```
+  -h, --help   help for completion
+```
+
+### SEE ALSO
+
+* [driverkit](driverkit.md)	 - A command line tool to build Falco kernel modules and eBPF probes.
+
diff --git a/docs/driverkit_docker.md b/docs/driverkit_docker.md
new file mode 100644
index 0000000..c893e63
--- /dev/null
+++ b/docs/driverkit_docker.md
@@ -0,0 +1,33 @@
+## driverkit docker
+
+Build Falco kernel modules and eBPF probes against a docker daemon.
+
+### Synopsis
+
+Build Falco kernel modules and eBPF probes against a docker daemon.
+
+```
+driverkit docker [flags]
+```
+
+### Options
+
+```
+  -c, --config string             config file path (default $HOME/.driverkit.yaml if exists)
+      --driverversion string      driver version as a git commit hash or as a git tag (default "dev")
+      --dryrun                    do not actually perform the action
+  -h, --help                      help for docker
+      --kernelconfigdata string   base64 encoded kernel config data: in some systems it can be found under the /boot directory, in other it is gzip compressed under /proc
+      --kernelrelease string      kernel release to build the module for, it can be found by executing 'uname -v'
+      --kernelversion uint16      kernel version to build the module for, it's the numeric value after the hash when you execute 'uname -v' (default 1)
+  -l, --loglevel string           log level (default "info")
+      --output-module string      filepath where to save the resulting kernel module
+      --output-probe string       filepath where to save the resulting eBPF probe
+  -t, --target string             the system to target the build for
+      --timeout int               timeout in seconds (default 60)
+```
+
+### SEE ALSO
+
+* [driverkit](driverkit.md)	 - A command line tool to build Falco kernel modules and eBPF probes.
+
diff --git a/docs/driverkit_kubernetes.md b/docs/driverkit_kubernetes.md
new file mode 100644
index 0000000..d4bb061
--- /dev/null
+++ b/docs/driverkit_kubernetes.md
@@ -0,0 +1,48 @@
+## driverkit kubernetes
+
+Build Falco kernel modules and eBPF probes against a Kubernetes cluster.
+
+### Synopsis
+
+Build Falco kernel modules and eBPF probes against a Kubernetes cluster.
+
+```
+driverkit kubernetes [flags]
+```
+
+### Options
+
+```
+      --as string                      username to impersonate for the operation
+      --as-group stringArray           group to impersonate for the operation, this flag can be repeated to specify multiple groups
+      --cache-dir string               default HTTP cache directory (default "$HOME/.kube/http-cache")
+      --certificate-authority string   path to a cert file for the certificate authority
+      --client-certificate string      path to a client certificate file for TLS
+      --client-key string              path to a client key file for TLS
+      --cluster string                 the name of the kubeconfig cluster to use
+  -c, --config string                  config file path (default $HOME/.driverkit.yaml if exists)
+      --context string                 the name of the kubeconfig context to use
+      --driverversion string           driver version as a git commit hash or as a git tag (default "dev")
+      --dryrun                         do not actually perform the action
+  -h, --help                           help for kubernetes
+      --insecure-skip-tls-verify       if true, the server's certificate will not be checked for validity, this will make your HTTPS connections insecure
+      --kernelconfigdata string        base64 encoded kernel config data: in some systems it can be found under the /boot directory, in other it is gzip compressed under /proc
+      --kernelrelease string           kernel release to build the module for, it can be found by executing 'uname -v'
+      --kernelversion uint16           kernel version to build the module for, it's the numeric value after the hash when you execute 'uname -v' (default 1)
+      --kubeconfig string              path to the kubeconfig file to use for CLI requests
+  -l, --loglevel string                log level (default "info")
+  -n, --namespace string               if present, the namespace scope for this CLI request
+      --output-module string           filepath where to save the resulting kernel module
+      --output-probe string            filepath where to save the resulting eBPF probe
+      --request-timeout string         the length of time to wait before giving up on a single server request, non-zero values should contain a corresponding time unit (e.g, 1s, 2m, 3h), a value of zero means don't timeout requests (default "0")
+  -s, --server string                  the address and port of the Kubernetes API server
+  -t, --target string                  the system to target the build for
+      --timeout int                    timeout in seconds (default 60)
+      --token string                   bearer token for authentication to the API server
+      --user string                    the name of the kubeconfig user to use
+```
+
+### SEE ALSO
+
+* [driverkit](driverkit.md)	 - A command line tool to build Falco kernel modules and eBPF probes.
+
diff --git a/go.mod b/go.mod
index 1470377..0d55ec2 100644
--- a/go.mod
+++ b/go.mod
@@ -18,11 +18,11 @@ require (
 	github.com/mitchellh/go-homedir v1.1.0
 	github.com/morikuni/aec v1.0.0 // indirect
 	github.com/opencontainers/image-spec v1.0.1 // indirect
-	github.com/sirupsen/logrus v1.4.2
-	github.com/spf13/cobra v0.0.6
+	github.com/sirupsen/logrus v1.5.0
+	github.com/spf13/cobra v1.0.0
 	github.com/spf13/pflag v1.0.5
-	github.com/spf13/viper v1.6.2
-	golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 // indirect
+	github.com/spf13/viper v1.6.3
+	golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 // indirect
 	gotest.tools v2.2.0+incompatible
 	k8s.io/api v0.17.3
 	k8s.io/apimachinery v0.17.3
diff --git a/go.sum b/go.sum
index 88a6fc9..f880fe4 100644
--- a/go.sum
+++ b/go.sum
@@ -41,11 +41,14 @@ github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
 github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
 github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
+github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/creasty/defaults v1.3.0 h1:uG+RAxYbJgOPCOdKEcec9ZJXeva7Y6mj/8egdzwmLtw=
 github.com/creasty/defaults v1.3.0/go.mod h1:CIEEvs7oIVZm30R8VxtFJs+4k201gReYyuYHJxZc68I=
@@ -171,6 +174,8 @@ github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBv
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 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/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
+github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
 github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
 github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
@@ -202,6 +207,7 @@ github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM
 github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
 github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
 github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
 github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@@ -247,6 +253,7 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP
 github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
 github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
 github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
 github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
@@ -259,12 +266,16 @@ github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uY
 github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
 github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
 github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
+github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
 github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q=
+github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
 github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
 github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
@@ -278,8 +289,8 @@ github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
 github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
 github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
-github.com/spf13/cobra v0.0.6 h1:breEStsVwemnKh2/s6gMvSdMEkwW0sK8vGStnlVBMCs=
-github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
+github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
+github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
 github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
 github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@@ -288,8 +299,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
 github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
-github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E=
-github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k=
+github.com/spf13/viper v1.6.3 h1:pDDu1OyEDTKzpJwdq4TiuLyMsUgRa/BT5cn5O62NoHs=
+github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
@@ -371,8 +382,8 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg=
 golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4=
-golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ=
+golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/pkg/version/version.go b/pkg/version/version.go
index 6040f6c..bc1a29a 100644
--- a/pkg/version/version.go
+++ b/pkg/version/version.go
@@ -7,16 +7,31 @@ import (
 )
 
 // Populated by makefile
-var gitCommit string
-var buildTime string
-var versionFormat = "git commit: %s\nbuild date: %s"
+var (
+	gitCommit         string
+	commitsFromGitTag string
+	gitTag            string
+	buildTime         string
+)
+
+const versionFormat = "%s-%s+%s"
 
-// GitCommit returns the git commit
+// GitCommit returns the git commit of the current driverkit version.
 func GitCommit() string {
 	return gitCommit
 }
 
-// Time returns the build time
+// GitTag returns the git tag of the current driverkit version.
+func GitTag() string {
+	return gitTag
+}
+
+// CommitsSinceGitTag returns the number of git commits since the current driverkit version contains w.r.t to the previous version.
+func CommitsSinceGitTag() string {
+	return commitsFromGitTag
+}
+
+// Time returns the build time of the current driverkit version.
 func Time() *time.Time {
 	if len(buildTime) == 0 {
 		return nil
@@ -29,11 +44,7 @@ func Time() *time.Time {
 	return &t
 }
 
-// String returns version info as a string
+// String returns current driverkit version info as a string.
 func String() string {
-	ts := Time()
-	if ts == nil {
-		return fmt.Sprintf(versionFormat, GitCommit(), "undefined")
-	}
-	return fmt.Sprintf(versionFormat, GitCommit(), ts.String())
-}
\ No newline at end of file
+	return fmt.Sprintf(versionFormat, GitTag(), CommitsSinceGitTag(), GitCommit())
+}


Test patch:
diff --git a/cmd/cli_test.go b/cmd/cli_test.go
index 4bdee9f..e1c0cc8 100644
--- a/cmd/cli_test.go
+++ b/cmd/cli_test.go
@@ -34,16 +34,16 @@ var tests = []testCase{
 		},
 	},
 	{
-		descr: "empty",
-		args:  []string{},
+		args: []string{"-h"},
 		expect: expect{
-			out: "testdata/autohelp.txt",
+			out: "testdata/help-flag.txt",
 		},
 	},
 	{
-		args: []string{"help", "--loglevel", "debug"},
+		descr: "empty",
+		args:  []string{},
 		expect: expect{
-			out: "testdata/help-debug.txt",
+			out: "testdata/autohelp.txt",
 		},
 	},
 	{
@@ -147,6 +147,71 @@ var tests = []testCase{
 			out: "testdata/docker-override-from-config-debug.txt",
 		},
 	},
+	{
+		descr: "complete/docker/targets",
+		args: []string{
+			"__complete",
+			"docker",
+			"--target",
+			"ENTER",
+		},
+		expect: expect{
+			out: "testdata/completion-targets.txt",
+		},
+	},
+	{
+		descr: "complete/kubernetes-alias/targets",
+		args: []string{
+			"__complete",
+			"k8s",
+			"--target",
+			"ENTER",
+		},
+		expect: expect{
+			out: "testdata/completion-targets.txt",
+		},
+	},
+	{
+		descr: "complete/kubernetes/targets",
+		args: []string{
+			"__complete",
+			"kubernetes",
+			"--target",
+			"ENTER",
+		},
+		expect: expect{
+			out: "testdata/completion-targets.txt",
+		},
+	},
+	{
+		descr: "completion/empty",
+		args: []string{
+			"completion",
+		},
+		expect: expect{
+			out: "testdata/completion-noargs.txt",
+		},
+	},
+	{
+		descr: "completion/help",
+		args: []string{
+			"completion",
+			"help",
+		},
+		expect: expect{
+			out: "testdata/completion-noargs.txt",
+		},
+	},
+	{
+		descr: "completion/help-short-flag",
+		args: []string{
+			"completion",
+			"-h",
+		},
+		expect: expect{
+			out: "testdata/completion-noargs.txt",
+		},
+	},
 }
 
 func run(t *testing.T, test testCase) {
@@ -154,7 +219,9 @@ func run(t *testing.T, test testCase) {
 	c := NewRootCmd()
 	b := bytes.NewBufferString("")
 	c.SetOutput(b)
-	test.args = append(test.args, "--dryrun")
+	if len(test.args) == 0 || (test.args[0] != "__complete" && test.args[0] != "__completeNoDesc" && test.args[0] != "help" && test.args[0] != "completion") {
+		test.args = append(test.args, "--dryrun")
+	}
 	c.SetArgs(test.args)
 	for k, v := range test.env {
 		if err := os.Setenv(k, v); err != nil {
diff --git a/cmd/testdata/autohelp.txt b/cmd/testdata/autohelp.txt
index 386b82e..a66ef9c 100644
--- a/cmd/testdata/autohelp.txt
+++ b/cmd/testdata/autohelp.txt
@@ -1,4 +1,4 @@
-INFO specify the processor                         processors="[docker kubernetes]"
+INFO specify a valid processor                     processors="[docker kubernetes]"
 A command line tool to build Falco kernel modules and eBPF probes.
 
 Usage:
@@ -6,6 +6,7 @@ Usage:
   driverkit [command]
 
 Available Commands:
+  completion  Generates completion scripts.
   docker      Build Falco kernel modules and eBPF probes against a docker daemon.
   help        Help about any command
   kubernetes  Build Falco kernel modules and eBPF probes against a Kubernetes cluster.
@@ -23,5 +24,6 @@ Flags:
       --output-probe string       filepath where to save the resulting eBPF probe
   -t, --target string             the system to target the build for
       --timeout int               timeout in seconds (default 60)
+  -v, --version                   version for driverkit
 
 Use "driverkit [command] --help" for more information about a command.
diff --git a/cmd/testdata/completion-noargs.txt b/cmd/testdata/completion-noargs.txt
new file mode 100644
index 0000000..1a2a3a6
--- /dev/null
+++ b/cmd/testdata/completion-noargs.txt
@@ -0,0 +1,17 @@
+Generates completion scripts for the following shells: bash, zsh, fish.
+
+There are two ways to configure your bash shell to load completions for each session.
+
+1. Source the completion script in your ~/.bashrc file
+
+    echo 'source <(driverkit completion bash)' >> ~/.bashrc
+
+2. Add the completion script to /etc/bash_completion.d/ directory
+
+    driverkit completion bash > /etc/bash_completion.d/driverkit
+
+Usage:
+  driverkit completion (bash|zsh|fish|help) [flags]
+
+Flags:
+  -h, --help   help for completion
diff --git a/cmd/testdata/completion-targets.txt b/cmd/testdata/completion-targets.txt
new file mode 100644
index 0000000..df7ea42
--- /dev/null
+++ b/cmd/testdata/completion-targets.txt
@@ -0,0 +1,9 @@
+amazonlinux
+amazonlinux2
+centos
+debian
+ubuntu-aws
+ubuntu-generic
+vanilla
+:0
+Completion ended with directive: ShellCompDirectiveDefault
diff --git a/cmd/testdata/docker-from-config-debug.txt b/cmd/testdata/docker-from-config-debug.txt
index 74957e3..99ea3fb 100644
--- a/cmd/testdata/docker-from-config-debug.txt
+++ b/cmd/testdata/docker-from-config-debug.txt
@@ -1,3 +1,3 @@
 INFO using config file                             file=testdata/configs/1.yaml
-DEBUG running with options                          driverversion=dev kernelrelease=4.15.0-1057-aws kernelversion=59 output-module=/tmp/falco-ubuntu-aws.ko target=ubuntu-aws
-INFO driver building, it will take a few seconds to complete  processor=docker
+DEBU running with options                          driverversion=dev kernelrelease=4.15.0-1057-aws kernelversion=59 output-module=/tmp/falco-ubuntu-aws.ko target=ubuntu-aws
+INFO driver building, it will take a few seconds   processor=docker
diff --git a/cmd/testdata/docker-override-from-config-debug.txt b/cmd/testdata/docker-override-from-config-debug.txt
index 1be682c..abcb7a5 100644
--- a/cmd/testdata/docker-override-from-config-debug.txt
+++ b/cmd/testdata/docker-override-from-config-debug.txt
@@ -1,3 +1,3 @@
 INFO using config file                             file=testdata/configs/1.yaml
-DEBUG running with options                          driverversion=dev kernelrelease=4.15.0-1057-aws kernelversion=229 output-module=/tmp/override.ko target=ubuntu-aws
-INFO driver building, it will take a few seconds to complete  processor=docker
+DEBU running with options                          driverversion=dev kernelrelease=4.15.0-1057-aws kernelversion=229 output-module=/tmp/override.ko target=ubuntu-aws
+INFO driver building, it will take a few seconds   processor=docker
diff --git a/cmd/testdata/docker-with-flags-debug.txt b/cmd/testdata/docker-with-flags-debug.txt
index 4a9ac65..1383c36 100644
--- a/cmd/testdata/docker-with-flags-debug.txt
+++ b/cmd/testdata/docker-with-flags-debug.txt
@@ -1,3 +1,3 @@
-DEBUG running without a configuration file         
-DEBUG running with options                          driverversion=dev kernelrelease=4.15.0-1057-aws kernelversion=59 output-module=/tmp/falco-ubuntu-aws.ko target=ubuntu-aws
-INFO driver building, it will take a few seconds to complete  processor=docker
+DEBU running without a configuration file         
+DEBU running with options                          driverversion=dev kernelrelease=4.15.0-1057-aws kernelversion=59 output-module=/tmp/falco-ubuntu-aws.ko target=ubuntu-aws
+INFO driver building, it will take a few seconds   processor=docker
diff --git a/cmd/testdata/docker-with-flags.txt b/cmd/testdata/docker-with-flags.txt
index 8e7c979..8288e6d 100644
--- a/cmd/testdata/docker-with-flags.txt
+++ b/cmd/testdata/docker-with-flags.txt
@@ -1,1 +1,1 @@
-INFO driver building, it will take a few seconds to complete  processor=docker
+INFO driver building, it will take a few seconds   processor=docker
diff --git a/cmd/testdata/dockernoopts.txt b/cmd/testdata/dockernoopts.txt
index 2d706e6..466097c 100644
--- a/cmd/testdata/dockernoopts.txt
+++ b/cmd/testdata/dockernoopts.txt
@@ -1,18 +1,16 @@
-ERROR error validating build options                error="kernel release is a required field"
-ERROR error validating build options                error="target is a required field"
-ERROR error validating build options                error="output module path is required when probe is missing"
-ERROR error validating build options                error="output probe path is required when module is missing"
+ERRO error validating build options                error="kernel release is a required field"
+ERRO error validating build options                error="target is a required field"
+ERRO error validating build options                error="output module path is required when probe is missing"
+ERRO error validating build options                error="output probe path is required when module is missing"
 Error: exiting for validation errors
 Usage:
   driverkit docker [flags]
 
 Flags:
-  -h, --help   help for docker
-
-Global Flags:
   -c, --config string             config file path (default $HOME/.driverkit.yaml if exists)
       --driverversion string      driver version as a git commit hash or as a git tag (default "dev")
       --dryrun                    do not actually perform the action
+  -h, --help                      help for docker
       --kernelconfigdata string   base64 encoded kernel config data: in some systems it can be found under the /boot directory, in other it is gzip compressed under /proc
       --kernelrelease string      kernel release to build the module for, it can be found by executing 'uname -v'
       --kernelversion uint16      kernel version to build the module for, it's the numeric value after the hash when you execute 'uname -v' (default 1)
diff --git a/cmd/testdata/help-debug.txt b/cmd/testdata/help-flag.txt
similarity index 93%
rename from cmd/testdata/help-debug.txt
rename to cmd/testdata/help-flag.txt
index e9b959f..2225df3 100644
--- a/cmd/testdata/help-debug.txt
+++ b/cmd/testdata/help-flag.txt
@@ -1,4 +1,3 @@
-DEBUG running without a configuration file         
 A command line tool to build Falco kernel modules and eBPF probes.
 
 Usage:
@@ -6,6 +5,7 @@ Usage:
   driverkit [command]
 
 Available Commands:
+  completion  Generates completion scripts.
   docker      Build Falco kernel modules and eBPF probes against a docker daemon.
   help        Help about any command
   kubernetes  Build Falco kernel modules and eBPF probes against a Kubernetes cluster.
@@ -23,5 +23,6 @@ Flags:
       --output-probe string       filepath where to save the resulting eBPF probe
   -t, --target string             the system to target the build for
       --timeout int               timeout in seconds (default 60)
+  -v, --version                   version for driverkit
 
 Use "driverkit [command] --help" for more information about a command.
diff --git a/cmd/testdata/help.txt b/cmd/testdata/help.txt
index ced80a0..2152e50 100644
--- a/cmd/testdata/help.txt
+++ b/cmd/testdata/help.txt
@@ -5,6 +5,7 @@ Usage:
   driverkit [command]
 
 Available Commands:
+  completion  Generates completion scripts.
   docker      Build Falco kernel modules and eBPF probes against a docker daemon.
   help        Help about any command
   kubernetes  Build Falco kernel modules and eBPF probes against a Kubernetes cluster.
diff --git a/cmd/testdata/non-existent-processor.txt b/cmd/testdata/non-existent-processor.txt
index 00c0121..142cc51 100644
--- a/cmd/testdata/non-existent-processor.txt
+++ b/cmd/testdata/non-existent-processor.txt
@@ -4,6 +4,7 @@ Usage:
   driverkit [command]
 
 Available Commands:
+  completion  Generates completion scripts.
   docker      Build Falco kernel modules and eBPF probes against a docker daemon.
   help        Help about any command
   kubernetes  Build Falco kernel modules and eBPF probes against a Kubernetes cluster.
@@ -21,6 +22,7 @@ Flags:
       --output-probe string       filepath where to save the resulting eBPF probe
   -t, --target string             the system to target the build for
       --timeout int               timeout in seconds (default 60)
+  -v, --version                   version for driverkit
 
 Use "driverkit [command] --help" for more information about a command.