Skip to content

Commit

Permalink
Implement --dry-run Flag for ttpforge run (#371)
Browse files Browse the repository at this point in the history
* The `--dry-run` flag will instruct `ttpforge run` to load and validate (but not actually run) a specified TTP
* This is useful for "best effort" integration testing of TTPs with side effects
  • Loading branch information
d3sch41n authored Oct 5, 2023
1 parent bbfefe4 commit c0d93cd
Show file tree
Hide file tree
Showing 28 changed files with 288 additions and 216 deletions.
23 changes: 13 additions & 10 deletions cmd/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,29 @@ The `cmd` package is a part of the TTPForge.

## Functions

### BuildRootCommand()
### BuildRootCommand(*Config)

```go
BuildRootCommand() *cobra.Command
BuildRootCommand(*Config) *cobra.Command
```

BuildRootCommand constructs a fully-initialized root cobra
command including all flags and sub-commands.
This function is principally used for tests.
This function is called from main(), but
otherwise is principally used for tests.

---
The cfg parameter is used to control certain aspect of execution
in unit tests. Note that this should usually just be set to nil,
and many of the fields you could set may be overwritten when
cfg.init is subsequently called.

### Execute()
**Parameters:**

```go
Execute() error
```
cfg: a Config struct used to control certain aspects of execution

**Returns:**

Execute sets up runtime configuration for the root command
and adds formatted error handling
*cobra.Command: The initialized root cobra command

---

Expand Down
54 changes: 51 additions & 3 deletions cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ package cmd
import (
// 'go lint': need blank import for embedding default config
"bytes"
"io"

// needed for embedded filesystem
_ "embed"
"fmt"
Expand All @@ -35,9 +37,19 @@ import (
)

// Config stores the variables from the TTPForge global config file
// we export it for use in tests, but packages besides `cmd` probably
// should not touch it
type Config struct {
RepoSpecs []repos.Spec `yaml:"repos"`

// used for capturing output in tests
// note: these are not supported for every single command.
// they will only receive output if you use `cmd.Prinln(...)`
// and such
// https://stackoverflow.com/questions/66802459/how-to-call-setout-on-subcommands-in-cobra
Stdout io.Writer
Stderr io.Writer

repoCollection repos.RepoCollection
cfgFile string
}
Expand All @@ -48,9 +60,6 @@ var (
defaultConfigFileName = "config.yaml"
defaultResourceDir = ".ttpforge"

// Conf refers to the configuration used throughout TTPForge.
Conf = &Config{}

logConfig logging.Config
)

Expand Down Expand Up @@ -94,3 +103,42 @@ func (cfg *Config) save() error {
err = os.WriteFile(cfg.cfgFile, []byte(cfgStr), 0)
return err
}

func (cfg *Config) init() error {
// find config file
if cfg.cfgFile == "" {
defaultConfigFilePath, err := getDefaultConfigFilePath()
if err != nil {
return fmt.Errorf("could not lookup default config file path: %v", err)
}
exists, err := afero.Exists(afero.NewOsFs(), defaultConfigFilePath)
if err != nil {
return fmt.Errorf("could not check existence of file %v: %v", defaultConfigFilePath, err)
}
if exists {
cfg.cfgFile = defaultConfigFilePath
} else {
logging.L().Warn("No config file specified and default configuration file not found!")
logging.L().Warn("You probably want to run `ttpforge init`!")
logging.L().Warn("However, if you know what you are doing, then carry on :)")
}
}

// load config file if we found one
if cfg.cfgFile != "" {
cfgContents, err := os.ReadFile(cfg.cfgFile)
if err != nil {
return err
}
if err = yaml.Unmarshal(cfgContents, cfg); err != nil {
return err
}
}
var err error
if cfg.repoCollection, err = cfg.loadRepoCollection(); err != nil {
return err
}

// setup logging
return logging.InitLog(logConfig)
}
4 changes: 2 additions & 2 deletions cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ import (
"github.com/spf13/cobra"
)

func buildInstallCommand() *cobra.Command {
func buildInstallCommand(cfg *Config) *cobra.Command {
installCmd := &cobra.Command{
Use: "install",
Short: "install various types of resources used by TTPForge",
Long: "For now, you just want to use the 'ttpforge install repo' subcommand",
TraverseChildren: true,
}
installCmd.AddCommand(buildInstallRepoCommand())
installCmd.AddCommand(buildInstallRepoCommand(cfg))
return installCmd
}
8 changes: 4 additions & 4 deletions cmd/installrepo.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import (
"github.com/spf13/cobra"
)

func buildInstallRepoCommand() *cobra.Command {
func buildInstallRepoCommand(cfg *Config) *cobra.Command {
var newRepoSpec repos.Spec
installRepoCommand := &cobra.Command{
Use: "repo --name repo_name [repo_url]",
Expand All @@ -43,13 +43,13 @@ func buildInstallRepoCommand() *cobra.Command {
}
newRepoSpec.Git.URL = u.String()
newRepoSpec.Path = filepath.Join("repos", newRepoSpec.Name)
Conf.RepoSpecs = append(Conf.RepoSpecs, newRepoSpec)
_, err = Conf.loadRepoCollection()
cfg.RepoSpecs = append(cfg.RepoSpecs, newRepoSpec)
_, err = cfg.loadRepoCollection()
if err != nil {
return fmt.Errorf("failed to add new repo: %v", err)
}

err = Conf.save()
err = cfg.save()
if err != nil {
return fmt.Errorf("failed to save updated configuration: %v", err)
}
Expand Down
6 changes: 3 additions & 3 deletions cmd/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ import (
"github.com/spf13/cobra"
)

func buildListCommand() *cobra.Command {
func buildListCommand(cfg *Config) *cobra.Command {
listCmd := &cobra.Command{
Use: "list",
Short: "list various resources available to TTPForge",
Long: "Use this command to list repos, TTPs, etc.",
TraverseChildren: true,
}
listCmd.AddCommand(buildListTTPsCommand())
listCmd.AddCommand(buildListReposCommand())
listCmd.AddCommand(buildListTTPsCommand(cfg))
listCmd.AddCommand(buildListReposCommand(cfg))
return listCmd
}
4 changes: 2 additions & 2 deletions cmd/listrepos.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@ import (
"github.com/spf13/cobra"
)

func buildListReposCommand() *cobra.Command {
func buildListReposCommand(cfg *Config) *cobra.Command {
return &cobra.Command{
Use: "repos",
Short: "list TTPForge repos (in which TTPs live) that you have installed",
TraverseChildren: true,
RunE: func(cmd *cobra.Command, args []string) error {
for _, spec := range Conf.RepoSpecs {
for _, spec := range cfg.RepoSpecs {
fmt.Printf("%v\t%v\n", spec.Name, spec.Path)
}
return nil
Expand Down
4 changes: 2 additions & 2 deletions cmd/listttps.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ import (
"github.com/spf13/cobra"
)

func buildListTTPsCommand() *cobra.Command {
func buildListTTPsCommand(cfg *Config) *cobra.Command {
var repoFilter string
listTTPsCommand := &cobra.Command{
Use: "ttps",
Short: "list TTPForge repos (in which TTPs live) that you have installed",
TraverseChildren: true,
RunE: func(cmd *cobra.Command, args []string) error {
ttpRefs, err := Conf.repoCollection.ListTTPs()
ttpRefs, err := cfg.repoCollection.ListTTPs()
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/listttps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func TestListTTPs(t *testing.T) {

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
rc := cmd.BuildRootCommand()
rc := cmd.BuildRootCommand(nil)
rc.SetArgs([]string{"list", "ttps", "-c", testConfigFilePath})
err := rc.Execute()
if tc.wantError {
Expand Down
4 changes: 2 additions & 2 deletions cmd/remove.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ import (
"github.com/spf13/cobra"
)

func buildRemoveCommand() *cobra.Command {
func buildRemoveCommand(cfg *Config) *cobra.Command {
removeCmd := &cobra.Command{
Use: "remove",
Short: "remove (uninstall) various types of resources used by TTPForge",
Long: "For now, you just want to use the 'ttpforge remove repo' subcommand",
TraverseChildren: true,
}
removeCmd.AddCommand(buildRemoveRepoCommand())
removeCmd.AddCommand(buildRemoveRepoCommand(cfg))
return removeCmd
}
10 changes: 5 additions & 5 deletions cmd/removerepo.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import (
"github.com/spf13/cobra"
)

func buildRemoveRepoCommand() *cobra.Command {
func buildRemoveRepoCommand(cfg *Config) *cobra.Command {
var newRepoSpec repos.Spec
removeRepoCommand := &cobra.Command{
Use: "repo",
Expand All @@ -38,7 +38,7 @@ func buildRemoveRepoCommand() *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
repoToRemove := args[0]
logging.L().Infof("will attempt to delete repo: %v", repoToRemove)
r, err := Conf.repoCollection.GetRepo(repoToRemove)
r, err := cfg.repoCollection.GetRepo(repoToRemove)
if err != nil {
return err
}
Expand All @@ -53,15 +53,15 @@ func buildRemoveRepoCommand() *cobra.Command {

// write the new config with that repo removed
var newRepoSpecs []repos.Spec
for _, spec := range Conf.RepoSpecs {
for _, spec := range cfg.RepoSpecs {
if spec.Name == repoToRemove {
break
}
newRepoSpecs = append(newRepoSpecs, spec)
}
Conf.RepoSpecs = newRepoSpecs
cfg.RepoSpecs = newRepoSpecs
logging.L().Infof("writing updated configuration...", repoToRemove)
err = Conf.save()
err = cfg.save()
if err != nil {
return fmt.Errorf("failed to save updated configuration: %v", err)
}
Expand Down
Loading

0 comments on commit c0d93cd

Please sign in to comment.