Skip to content

Commit

Permalink
Merge pull request #13 from ermetic-research/feat/import-scenarios
Browse files Browse the repository at this point in the history
Add functionality to import scenarios from a local directory
  • Loading branch information
igofman authored Aug 22, 2023
2 parents 29a8b26 + 600b371 commit 728e387
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 6 deletions.
46 changes: 46 additions & 0 deletions cmd/commands/commands/maintenance/commands/import_scenarios.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package commands

import (
"fmt"
cnappgoat "github.com/ermetic-research/CNAPPgoat"
"github.com/ermetic-research/CNAPPgoat/cmd/commands/common"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
)

var ImportScenariosCommand = &cli.Command{
Name: "import-scenarios",
Usage: "maintenance subcommand to import scenarios from a local directory to the CNAPPgoat registry",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "directory",
Usage: "Directory to import scenarios from",
Aliases: []string{"d"},
Value: "",
},
&cli.BoolFlag{
Name: "debug",
Usage: "Enable debug mode",
Value: false,
},
},
Before: func(c *cli.Context) error {
if err := common.CommandBefore(c); err != nil {
return err
}

if c.String("directory") == "" {
return fmt.Errorf("directory flag is required in import-scenarios subcommand")
}

return nil
},
Action: func(c *cli.Context) error {
registry := c.Context.Value("CNAPPgoatModuleRegistry").(*cnappgoat.Registry)
logrus.Infof("importing scenarios from %s", c.String("directory"))
if _, err := registry.ImportScenarios(c.String("directory")); err != nil {
return err
}
return nil
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ var MaintenanceCommand = &cli.Command{
plugins.PluginsCommand,
stacks.StacksCommand,
commands.GetScenariosCommand,
commands.ImportScenariosCommand,
},
Hidden: true,
}
14 changes: 12 additions & 2 deletions localstorage.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ func (l *LocalStorage) ReadCnappGoatConfig(scenario *Scenario) (map[string]strin
if err = yaml.Unmarshal(data, &cnappGoatConfig); err != nil {
return nil, fmt.Errorf("failed to parse Pulumi.yaml: %w", err)
}
if err := cnappGoatConfig.ScenarioParams.IsValid(); err != nil {
return nil, fmt.Errorf("scenarioParams field is empty for scenario %v", scenario.Name)
}

return cnappGoatConfig.ScenarioParams.Config, nil
}
Expand Down Expand Up @@ -179,6 +182,9 @@ func (l *LocalStorage) updateScenariosFromFolder(scenariosFullPath string) (map[
return nil, fmt.Errorf("scenario folder does not exist, cannot perform UpdateScenarioFolder: %s", scenariosFullPath)
}
scenariosFromScenarioDir, err := l.loadScenarios(scenariosFullPath)
if err != nil {
return nil, fmt.Errorf("unable to load scenarios from scenario directory: %w", err)
}
var scenariosFromWorkDir map[string]*Scenario
if l.WorkingDirectoryExists() {
scenariosFromWorkDir, err = l.loadScenarios(l.WorkingDir)
Expand All @@ -201,14 +207,15 @@ func (l *LocalStorage) updateScenariosFromFolder(scenariosFullPath string) (map[
return nil, fmt.Errorf("unable to copy scenario to working directory: %w", err)
}
scenariosFromWorkDir[scenarioFromScenariosDir.ScenarioParams.ID] = scenarioFromScenariosDir
logrus.Infof("scenario %v exists in the working directory but has changed. Updating.", scenarioFromWorkDir.ScenarioParams.ID)
} else {
logrus.Debugf("Scenario %v exists in the working directory and has not changed. Skipping.", scenarioFromWorkDir.ScenarioParams.ID)
logrus.Debugf("scenario %v exists in the working directory and has not changed. Skipping.", scenarioFromWorkDir.ScenarioParams.ID)
}
}
}
if !exists {
// if the scenario does not exist in the working directory, copy it over
logrus.Debugf("Scenario %v does not exist in the working directory. Copying over.", scenarioFromScenariosDir.ScenarioParams.ID)
logrus.Infof("scenario %v does not exist in the working directory. Copying over.", scenarioFromScenariosDir.ScenarioParams.ID)
err = l.copyScenario(scenarioFromScenariosDir)
if err != nil {
return nil, fmt.Errorf("unable to copy scenario to working directory: %w", err)
Expand Down Expand Up @@ -287,6 +294,9 @@ func (l *LocalStorage) createScenario(path string) (*Scenario, error) {
if err = yaml.Unmarshal(data, &scenario); err != nil {
return nil, fmt.Errorf("failed to parse Pulumi.yaml: %w", err)
}
if err = scenario.ScenarioParams.IsValid(); err != nil {
return nil, fmt.Errorf("scenarioParams field is empty for scenario %v", scenario.Name)
}

scenario.SrcDir = filepath.Dir(path)

Expand Down
4 changes: 4 additions & 0 deletions registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ func (r *Registry) SetState(scenario *Scenario, state State) error {
return r.storage.WriteStateToFile(r.scenarios[scenario.ScenarioParams.ID], state)
}

func (r *Registry) ImportScenarios(path string) (map[string]*Scenario, error) {
return r.storage.updateScenariosFromFolder(path)
}

func sortScenarios(scenarios []*Scenario) {
sort.Slice(scenarios, func(i, j int) bool {
return scenarios[i].ScenarioParams.ID < scenarios[j].ScenarioParams.ID
Expand Down
59 changes: 55 additions & 4 deletions scenario.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cnappgoat

import (
"errors"
"fmt"
"strings"

"gopkg.in/yaml.v3"
Expand Down Expand Up @@ -83,7 +84,7 @@ func (m Module) String() string {
case IAC:
return "IAC"
default:
panic("CNAPPgoat module name not formatted")
return ""
}
}

Expand All @@ -110,7 +111,7 @@ func (p Platform) String() string {
case GCP:
return "GCP"
default:
panic("platform name not formatted")
return ""
}
}

Expand Down Expand Up @@ -143,7 +144,9 @@ func (r *runtime) UnmarshalYAML(unmarshal func(interface{}) error) error {
if name, ok := runtimeObj["name"].(string); ok {
r.Name = name
}
r.Options = runtimeObj
if options, ok := runtimeObj["options"].(map[string]interface{}); ok {
r.Options = options
}

return nil
}
Expand All @@ -169,7 +172,7 @@ func ModuleFromString(name string) (Module, error) {
case strings.ToLower(IAC.String()):
return IAC, nil
default:
return "", errors.New("unknown CNAPPgoat module name: " + name)
return "", errors.New("unknown module name: " + name)
}
}

Expand Down Expand Up @@ -200,3 +203,51 @@ func StateFromString(state string) (State, error) {
return State{}, errors.New("unknown state: " + state)
}
}

func (p scenarioParams) IsValid() error {
if p.Module == "" && p.Platform == "" && p.ID == "" && p.FriendlyName == "" && p.Description == "" && p.ScenarioType == "" {
return errors.New("scenarioParams is empty, yaml is missing value cnappgoat-params")
}
if p.Module == "" {
return errors.New("module is required")
}
if p.Platform == "" {
return errors.New("platform is required")
}
if p.ID == "" {
return errors.New("id is required")
}
if len(strings.Split(p.ID, "-")) < 3 {
return fmt.Errorf("id must be in the format of <module>-<platform>-<id>, id is: %s", p.ID)
}
// module must be valid
if _, err := ModuleFromString(string(p.Module)); err != nil {
return err
}
// platform must be valid
if _, err := PlatformFromString(string(p.Platform)); err != nil {
return err
}
if !p.Module.Equals(Module(strings.Split(p.ID, "-")[0])) {
return fmt.Errorf("id must start with matching module name: %s, id is: %v", p.Module, p.ID)
}
if !p.Platform.Equals(Platform(strings.Split(p.ID, "-")[1])) {
return fmt.Errorf("id must include matching platform name, id is: %v", p.ID)
}
if p.FriendlyName == "" {
return errors.New("friendlyName is required")
}
if p.Description == "" {
return errors.New("description is required")
}
if p.ScenarioType == "" {
return errors.New("scenarioType is required")
}
if _, err := ModuleFromString(string(p.Module)); err != nil {
return err
}
if _, err := PlatformFromString(string(p.Platform)); err != nil {
return err
}
return nil
}

0 comments on commit 728e387

Please sign in to comment.