Skip to content

Commit

Permalink
Merge pull request #2 from anchore/initial-bootstrap
Browse files Browse the repository at this point in the history
  • Loading branch information
bradleyjones committed Dec 15, 2022
2 parents e3d6ecd + ac4ac79 commit ee4ab4a
Show file tree
Hide file tree
Showing 26 changed files with 2,093 additions and 2 deletions.
82 changes: 82 additions & 0 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package cmd

import (
"fmt"
"os"

"github.com/anchore/elastic-container-gatherer/ecg"
"github.com/anchore/elastic-container-gatherer/internal/config"
"github.com/anchore/elastic-container-gatherer/internal/logger"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

var appConfig *config.Application
var log *logrus.Logger
var cliOnlyOpts config.CliOnlyOptions

func init() {
setGlobalCliOptions()

cobra.OnInitialize(
InitAppConfig,
initLogging,
logAppConfig,
)
}

func setGlobalCliOptions() {
// setup global CLI options (available on all CLI commands)
rootCmd.PersistentFlags().StringVarP(&cliOnlyOpts.ConfigPath, "config", "c", "", "application config file")

flag := "quiet"
rootCmd.PersistentFlags().BoolP(
flag, "q", false,
"suppress all logging output",
)
if err := viper.BindPFlag(flag, rootCmd.PersistentFlags().Lookup(flag)); err != nil {
fmt.Printf("unable to bind flag '%s': %+v", flag, err)
os.Exit(1)
}

rootCmd.PersistentFlags().CountVarP(&cliOnlyOpts.Verbosity, "verbose", "v", "increase verbosity (-v = info, -vv = debug)")
}

func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}
}

func InitAppConfig() {
cfg, err := config.LoadConfigFromFile(viper.GetViper(), &cliOnlyOpts)
if err != nil {
fmt.Printf("failed to load application config: \n\t%+v\n", err)
os.Exit(1)
}
appConfig = cfg
}

func GetAppConfig() *config.Application {
return appConfig
}

func initLogging() {
cfg := logger.LogrusConfig{
EnableConsole: (appConfig.Log.FileLocation == "" || appConfig.CliOptions.Verbosity > 0) && !appConfig.Quiet,
EnableFile: appConfig.Log.FileLocation != "",
Level: appConfig.Log.LevelOpt,
Structured: appConfig.Log.Structured,
FileLocation: appConfig.Log.FileLocation,
}

logWrapper := logger.NewLogrusLogger(cfg)
log = logWrapper.Logger
ecg.SetLogger(logWrapper)
}

func logAppConfig() {
log.Debugf("Application config:\n%s", appConfig)
}
65 changes: 65 additions & 0 deletions cmd/completion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package cmd

import (
"os"

"github.com/spf13/cobra"
)

// completionCmd represents the completion command
var completionCmd = &cobra.Command{
Use: "completion [bash|zsh|fish]",
Short: "Generate Completion script",
Long: `To load completions:
Bash:
$ source <(ecg completion bash)
# To load completions for each session, execute once:
Linux:
$ ecg completion bash > /etc/bash_completion.d/ecg
MacOS:
$ ecg completion bash > /usr/local/etc/bash_completion.d/ecg
Zsh:
# If shell completion is not already enabled in your environment you will need
# to enable it. You can execute the following once:
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
# To load completions for each session, execute once:
$ ecg completion zsh > "${fpath[1]}/_ecg"
# You will need to start a new shell for this setup to take effect.
Fish:
$ ecg completion fish | source
# To load completions for each session, execute once:
$ ecg completion fish > ~/.config/fish/completions/ecg.fish
`,
DisableFlagsInUseLine: true,
ValidArgs: []string{"bash", "zsh", "fish"},
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
Run: func(cmd *cobra.Command, args []string) {
var err error
switch args[0] {
case "bash":
err = cmd.Root().GenBashCompletion(os.Stdout)
case "zsh":
err = cmd.Root().GenZshCompletion(os.Stdout)
case "fish":
err = cmd.Root().GenFishCompletion(os.Stdout, true)
}
if err != nil {
panic(err)
}
},
}

func init() {
rootCmd.AddCommand(completionCmd)
}
104 changes: 104 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package cmd

import (
"fmt"
"os"
"runtime/pprof"

"github.com/anchore/elastic-container-gatherer/ecg/mode"

"github.com/anchore/elastic-container-gatherer/ecg"
"github.com/anchore/elastic-container-gatherer/ecg/presenter"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)

// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "ecg",
Short: "ECG tells Anchore which images are in use in your ECS clusters",
Long: "ECG (Elastic Container Gatherer) can poll Amazon ECS (Elastic Container Service) APIs to tell Anchore which Images are currently in-use",
Args: cobra.MaximumNArgs(0),
Run: func(cmd *cobra.Command, args []string) {
if appConfig.Dev.ProfileCPU {
f, err := os.Create("cpu.profile")
if err != nil {
log.Errorf("unable to create CPU profile: %+v", err)
} else {
err := pprof.StartCPUProfile(f)
if err != nil {
log.Errorf("unable to start CPU profile: %+v", err)
}
}
}

if len(args) > 0 {
err := cmd.Help()
if err != nil {
log.Errorf(err.Error())
os.Exit(1)
}
os.Exit(1)
}

// TODO(bradjones) Validate anchore connection details here
//if appConfig.AnchoreDetails.IsValid() {
//dummyReport := inventory.Report{
//Results: []inventory.ReportItem{},
//}
//err := reporter.Post(dummyReport, appConfig.AnchoreDetails, appConfig)
//if err != nil {
//log.Errorf("Failed to validate connection to Anchore: %+v", err)
//}
//} else {
//log.Debug("Anchore details not specified, will not report inventory")
//}

switch appConfig.RunMode {
case mode.PeriodicPolling:
ecg.PeriodicallyGetInventoryReport(appConfig)
default:
report, err := ecg.GetInventoryReport(appConfig)
if appConfig.Dev.ProfileCPU {
pprof.StopCPUProfile()
}
if err != nil {
log.Errorf("Failed to get Image Results: %+v", err)
os.Exit(1)
} else {
err := ecg.HandleReport(report, appConfig)
if err != nil {
log.Errorf("Failed to handle Image Results: %+v", err)
os.Exit(1)
}
}
}
},
}

func init() {
// output & formatting options
opt := "output"
rootCmd.Flags().StringP(
opt, "o", presenter.JSONPresenter.String(),
fmt.Sprintf("report output formatter, options=%v", presenter.Options),
)
if err := viper.BindPFlag(opt, rootCmd.Flags().Lookup(opt)); err != nil {
fmt.Printf("unable to bind flag '%s': %+v", opt, err)
os.Exit(1)
}

opt = "mode"
rootCmd.Flags().StringP(opt, "m", mode.AdHoc.String(), fmt.Sprintf("execution mode, options=%v", mode.Modes))
if err := viper.BindPFlag(opt, rootCmd.Flags().Lookup(opt)); err != nil {
fmt.Printf("unable to bind flag '%s': %+v", opt, err)
os.Exit(1)
}

opt = "polling-interval-seconds"
rootCmd.Flags().StringP(opt, "p", "300", "If mode is 'periodic', this specifies the interval")
if err := viper.BindPFlag(opt, rootCmd.Flags().Lookup(opt)); err != nil {
fmt.Printf("unable to bind flag '%s': %+v", opt, err)
os.Exit(1)
}
}
59 changes: 59 additions & 0 deletions cmd/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package cmd

import (
"encoding/json"
"fmt"
"os"

"github.com/anchore/elastic-container-gatherer/internal"
"github.com/anchore/elastic-container-gatherer/internal/version"
"github.com/spf13/cobra"
)

var outputFormat string

var versionCmd = &cobra.Command{
Use: "version",
Short: "show the version",
Run: printVersion,
}

func init() {
versionCmd.Flags().StringVarP(&outputFormat, "output", "o", "text", "format to show version information (available=[text, json])")

rootCmd.AddCommand(versionCmd)
}

func printVersion(_ *cobra.Command, _ []string) {
versionInfo := version.FromBuild()
switch outputFormat {
case "text":
fmt.Println("Application: ", internal.ApplicationName)
fmt.Println("Version: ", versionInfo.Version)
fmt.Println("BuildDate: ", versionInfo.BuildDate)
fmt.Println("GitCommit: ", versionInfo.GitCommit)
fmt.Println("GitTreeState: ", versionInfo.GitTreeState)
fmt.Println("Platform: ", versionInfo.Platform)
fmt.Println("GoVersion: ", versionInfo.GoVersion)
fmt.Println("Compiler: ", versionInfo.Compiler)
case "json":

enc := json.NewEncoder(os.Stdout)
enc.SetEscapeHTML(false)
enc.SetIndent("", " ")
err := enc.Encode(&struct {
version.Version
Application string `json:"application"`
}{
Version: versionInfo,
Application: internal.ApplicationName,
})
if err != nil {
fmt.Printf("failed to show version information: %+v\n", err)
os.Exit(1)
}
default:
fmt.Printf("unsupported output format: %s\n", outputFormat)
os.Exit(1)
}
}
8 changes: 8 additions & 0 deletions ecg/inventory/report.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package inventory

type Report struct {
Timestamp string `json:"timestamp,omitempty"` // Should be generated using time.Now.UTC() and formatted according to RFC Y-M-DTH:M:SZ
Results []ReportItem `json:"results"`
ClusterName string `json:"cluster_name"`
InventoryType string `json:"inventory_type"`
}
27 changes: 27 additions & 0 deletions ecg/inventory/reportitem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package inventory

import (
"fmt"
)

// ReportItem represents a cluster and all it's unique images
type ReportItem struct {
Cluster string `json:"cluster,omitempty"`
Images []ReportImage `json:"images"`
}

// ReportImage represents a unique image in a cluster
type ReportImage struct {
Tag string `json:"tag,omitempty"`
RepoDigest string `json:"repoDigest,omitempty"`
}

// String represent the ReportItem as a string
func (r *ReportItem) String() string {
return fmt.Sprintf("ReportItem(cluster=%s, images=%v)", r.Cluster, r.Images)
}

// key will return a unique key for a ReportImage
func (i *ReportImage) key() string {
return fmt.Sprintf("%s@%s", i.Tag, i.RepoDigest)
}

0 comments on commit ee4ab4a

Please sign in to comment.