Skip to content

Commit

Permalink
new(cmd,pkg,docs,docgen,validate): switched from slog go library to…
Browse files Browse the repository at this point in the history
… use rich-text falcoctl log library.

Signed-off-by: Federico Di Pierro <[email protected]>
  • Loading branch information
FedeDP committed Apr 11, 2024
1 parent 8ea62ad commit 9723857
Show file tree
Hide file tree
Showing 27 changed files with 691 additions and 422 deletions.
5 changes: 3 additions & 2 deletions cmd/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package cmd
import (
"bytes"
"fmt"
"github.com/spf13/pflag"
"os"
"strings"
"text/template"
Expand Down Expand Up @@ -46,12 +47,12 @@ func validateArgs() cobra.PositionalArgs {
if len(args) == 0 {
return nil
}
return cobra.ExactValidArgs(1)(c, args)
return cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs)(c, args)
}
}

// NewCompletionCmd ...
func NewCompletionCmd() *cobra.Command {
func NewCompletionCmd(_ *ConfigOptions, _ *RootOptions, _ *pflag.FlagSet) *cobra.Command {
var long bytes.Buffer
tmpl := template.Must(template.New("long").Parse(longUsageTemplate))
tmpl.Execute(&long, map[string]interface{}{
Expand Down
100 changes: 86 additions & 14 deletions cmd/config_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,51 +15,123 @@ limitations under the License.
package cmd

import (
"errors"
"fmt"
"log/slog"
"github.com/falcosecurity/falcoctl/pkg/options"
"github.com/falcosecurity/falcoctl/pkg/output"
"github.com/mitchellh/go-homedir"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"io"
"os"
"strings"

"github.com/creasty/defaults"
"github.com/falcosecurity/driverkit/validate"
"github.com/go-playground/validator/v10"
"github.com/pterm/pterm"
)

var validProcessors = []string{"docker", "kubernetes", "kubernetes-in-cluster", "local"}
var aliasProcessors = []string{"docker", "k8s", "k8s-ic"}
var configOptions *ConfigOptions

// ConfigOptions represent the persistent configuration flags of driverkit.
type ConfigOptions struct {
ConfigFile string
LogLevel string `validate:"loglevel" name:"log level" default:"INFO"`
Timeout int `validate:"number,min=30" default:"120" name:"timeout"`
ProxyURL string `validate:"omitempty,proxy" name:"proxy url"`
DryRun bool

configErrors bool
// Printer used by all commands to output messages.
Printer *output.Printer
// Writer is used to write the output of the printer.
Writer io.Writer
logLevel *options.LogLevel
}

func (co *ConfigOptions) initPrinter() {
logLevel := co.logLevel.ToPtermLogLevel()
co.Printer = output.NewPrinter(logLevel, pterm.LogFormatterColorful, co.Writer)
}

// NewConfigOptions creates an instance of ConfigOptions.
func NewConfigOptions() *ConfigOptions {
o := &ConfigOptions{}
func NewConfigOptions() (*ConfigOptions, error) {
o := &ConfigOptions{
Writer: os.Stdout,
logLevel: options.NewLogLevel(),
}
o.initPrinter()
if err := defaults.Set(o); err != nil {
slog.With("err", err.Error(), "options", "ConfigOptions").Error("error setting driverkit options defaults")
os.Exit(1)
// Return ConfigOptions anyway because we need the logger
return o, err
}
return o
return o, nil
}

// Validate validates the ConfigOptions fields.
func (co *ConfigOptions) Validate() []error {
func (co *ConfigOptions) validate() []error {
if err := validate.V.Struct(co); err != nil {
errors := err.(validator.ValidationErrors)
errArr := []error{}
for _, e := range errors {
var errs validator.ValidationErrors
errors.As(err, &errs)
var errArr []error
for _, e := range errs {
// Translate each error one at a time
errArr = append(errArr, fmt.Errorf(e.Translate(validate.T)))
}
co.configErrors = true
return errArr
}
return nil
}

// AddFlags registers the common flags.
func (co *ConfigOptions) AddFlags(flags *pflag.FlagSet) {
flags.StringVarP(&co.ConfigFile, "config", "c", co.ConfigFile, "config file path (default $HOME/.driverkit.yaml if exists)")
flags.VarP(co.logLevel, "loglevel", "l", "Set level for logs "+co.logLevel.Allowed())
flags.IntVar(&co.Timeout, "timeout", co.Timeout, "timeout in seconds")
flags.StringVar(&co.ProxyURL, "proxy", co.ProxyURL, "the proxy to use to download data")
flags.BoolVar(&co.DryRun, "dryrun", co.DryRun, "do not actually perform the action")
}

// Init reads in config file and ENV variables if set.
func (co *ConfigOptions) Init() bool {
configErr := false
if errs := co.validate(); errs != nil {
for _, err := range errs {
co.Printer.Logger.Error("error validating config options",
co.Printer.Logger.Args("err", err.Error()))
}
configErr = true
}
if co.ConfigFile != "" {
viper.SetConfigFile(co.ConfigFile)
} else {
// Find home directory.
home, err := homedir.Dir()
if err != nil {
co.Printer.Logger.Error("error getting the home directory",
co.Printer.Logger.Args("err", err.Error()))
// not setting configErr = true because we fallback to `$HOME/.driverkit.yaml` and try with it
}

viper.AddConfigPath(home)
viper.SetConfigName(".driverkit")
}

viper.AutomaticEnv()
viper.SetEnvPrefix("driverkit")
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))

// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
co.Printer.Logger.Info("using config file",
co.Printer.Logger.Args("file", viper.ConfigFileUsed()))
} else {
var configFileNotFoundError viper.ConfigFileNotFoundError
if errors.As(err, &configFileNotFoundError) {
// Config file not found, ignore ...
co.Printer.Logger.Debug("running without a configuration file")
}
}
co.initPrinter()
return configErr
}
34 changes: 20 additions & 14 deletions cmd/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,32 +15,38 @@ limitations under the License.
package cmd

import (
"log/slog"
"os"

"bytes"
"github.com/falcosecurity/driverkit/pkg/driverbuilder"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)

// NewDockerCmd creates the `driverkit docker` command.
func NewDockerCmd(rootOpts *RootOptions, rootFlags *pflag.FlagSet) *cobra.Command {
func NewDockerCmd(configOpts *ConfigOptions, 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) {
slog.With("processor", c.Name()).Info("driver building, it will take a few seconds")
if !configOptions.DryRun {
b := rootOpts.ToBuild()
RunE: func(c *cobra.Command, args []string) error {
configOpts.Printer.Logger.Info("starting build",
configOpts.Printer.Logger.Args("processor", c.Name()))
if !configOpts.DryRun {
// Since we use a spinner, cache log data to a bytesbuffer;
// we will later print it once we stop the spinner.
var buf bytes.Buffer
b := rootOpts.ToBuild(configOpts.Printer.WithWriter(&buf))
defer func() {
configOpts.Printer.DefaultText.Print(buf.String())
}()
if !b.HasOutputs() {
return
}
if err := driverbuilder.NewDockerBuildProcessor(viper.GetInt("timeout"), viper.GetString("proxy")).Start(b); err != nil {
slog.With("err", err.Error()).Error("exiting")
os.Exit(1)
return nil
}
configOpts.Printer.Spinner, _ = configOpts.Printer.Spinner.Start("driver building, it will take a few seconds")
defer func() {
_ = configOpts.Printer.Spinner.Stop()
}()
return driverbuilder.NewDockerBuildProcessor(configOpts.Timeout, configOpts.ProxyURL).Start(b)
}
return nil
},
}
// Add root flags
Expand Down
15 changes: 11 additions & 4 deletions cmd/images.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ limitations under the License.
package cmd

import (
"log/slog"
"bytes"
"os"

"github.com/olekukonko/tablewriter"
Expand All @@ -24,14 +24,21 @@ import (
)

// NewImagesCmd creates the `driverkit images` command.
func NewImagesCmd(rootOpts *RootOptions, rootFlags *pflag.FlagSet) *cobra.Command {
func NewImagesCmd(configOpts *ConfigOptions, rootOpts *RootOptions, rootFlags *pflag.FlagSet) *cobra.Command {
imagesCmd := &cobra.Command{
Use: "images",
Short: "List builder images",
Run: func(c *cobra.Command, args []string) {
slog.With("processor", c.Name()).Info("listing images")
b := rootOpts.ToBuild()
configOpts.Printer.Logger.Info("starting loading images",
configOpts.Printer.Logger.Args("processor", c.Name()))
// Since we use a spinner, cache log data to a bytesbuffer;
// we will later print it once we stop the spinner.
var buf bytes.Buffer
b := rootOpts.ToBuild(configOpts.Printer.WithWriter(&buf))
configOpts.Printer.Spinner, _ = configOpts.Printer.Spinner.Start("listing images, it will take a few seconds")
b.LoadImages()
_ = configOpts.Printer.Spinner.Stop()
configOpts.Printer.DefaultText.Print(buf.String())

table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Image", "Target", "Arch", "GCC"})
Expand Down
59 changes: 33 additions & 26 deletions cmd/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,20 @@ limitations under the License.
package cmd

import (
"log/slog"
"os"
"bytes"
"github.com/falcosecurity/driverkit/pkg/driverbuilder/builder"
"regexp"
"strings"

"github.com/falcosecurity/driverkit/pkg/driverbuilder"
"github.com/falcosecurity/driverkit/pkg/kubernetes/factory"
"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, rootFlags *pflag.FlagSet) *cobra.Command {
func NewKubernetesCmd(configOpts *ConfigOptions, rootOpts *RootOptions, rootFlags *pflag.FlagSet) *cobra.Command {
kubernetesCmd := &cobra.Command{
Use: "kubernetes",
Short: "Build Falco kernel modules and eBPF probes against a Kubernetes cluster.",
Expand Down Expand Up @@ -58,34 +57,36 @@ func NewKubernetesCmd(rootOpts *RootOptions, rootFlags *pflag.FlagSet) *cobra.Co

kubefactory := factory.NewFactory(configFlags)

kubernetesCmd.Run = func(cmd *cobra.Command, args []string) {
slog.With("processor", cmd.Name()).Info("driver building, it will take a few seconds")
if !configOptions.DryRun {
if err := kubernetesRun(cmd, args, kubefactory, rootOpts); err != nil {
slog.With("err", err.Error()).Error("exiting")
os.Exit(1)
kubernetesCmd.RunE = func(c *cobra.Command, args []string) error {
configOpts.Printer.Logger.Info("starting build",
configOpts.Printer.Logger.Args("processor", c.Name()))
if !configOpts.DryRun {
// Since we use a spinner, cache log data to a bytesbuffer;
// we will later print it once we stop the spinner.
var buf bytes.Buffer
b := rootOpts.ToBuild(configOpts.Printer.WithWriter(&buf))
defer func() {
configOpts.Printer.DefaultText.Print(buf.String())
}()
if !b.HasOutputs() {
return nil
}
configOpts.Printer.Spinner, _ = configOpts.Printer.Spinner.Start("driver building, it will take a few seconds")
defer func() {
_ = configOpts.Printer.Spinner.Stop()
}()
return kubernetesRun(kubefactory, b, configOpts)
}
return nil
}

return kubernetesCmd
}

func kubernetesRun(cmd *cobra.Command, args []string, kubefactory factory.Factory, rootOpts *RootOptions) error {
f := cmd.Flags()
b := rootOpts.ToBuild()
if !b.HasOutputs() {
return nil
}

namespaceStr, err := f.GetString("namespace")
if err != nil {
return err
}
if len(namespaceStr) == 0 {
namespaceStr = "default"
}

func kubernetesRun(kubefactory factory.Factory,
b *builder.Build,
configOpts *ConfigOptions,
) error {
kc, err := kubefactory.KubernetesClientSet()
if err != nil {
return err
Expand All @@ -98,6 +99,12 @@ func kubernetesRun(cmd *cobra.Command, args []string, kubefactory factory.Factor
return err
}

buildProcessor := driverbuilder.NewKubernetesBuildProcessor(kc.CoreV1(), clientConfig, kubernetesOptions.RunAsUser, kubernetesOptions.Namespace, kubernetesOptions.ImagePullSecret, viper.GetInt("timeout"), viper.GetString("proxy"))
buildProcessor := driverbuilder.NewKubernetesBuildProcessor(kc.CoreV1(),
clientConfig,
kubernetesOptions.RunAsUser,
kubernetesOptions.Namespace,
kubernetesOptions.ImagePullSecret,
configOpts.Timeout,
configOpts.ProxyURL)
return buildProcessor.Start(b)
}
Loading

0 comments on commit 9723857

Please sign in to comment.