diff --git a/cmd/shore/shore.go b/cmd/shore/shore.go index dfadbf8..424d9ce 100644 --- a/cmd/shore/shore.go +++ b/cmd/shore/shore.go @@ -4,11 +4,9 @@ import ( "fmt" "os" - "github.com/Autodesk/shore/pkg/backend/spinnaker" "github.com/Autodesk/shore/pkg/cleanup_command" "github.com/Autodesk/shore/pkg/command" "github.com/Autodesk/shore/pkg/project" - "github.com/Autodesk/shore/pkg/renderer/jsonnet" "github.com/sirupsen/logrus" "github.com/spf13/afero" "github.com/spf13/cobra" @@ -22,6 +20,8 @@ var version = "local" var logVerbosity int var logger *logrus.Logger +var commonDependencies *command.Dependencies + var rootCmd = &cobra.Command{ Use: "shore", Short: "Shore - Pipeline Framework", @@ -29,16 +29,26 @@ var rootCmd = &cobra.Command{ SilenceUsage: true, SilenceErrors: true, Version: version, - PersistentPreRun: func(cmd *cobra.Command, args []string) { + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { logLevel := logrus.WarnLevel + logrus.Level(logVerbosity) logger.SetLevel(logLevel) logger.SetFormatter(&logrus.TextFormatter{}) + if cmd.Name() == "help" { + return nil // No need to do anything, just printing help + } + profileName := GetProfileName(cmd) - ExecConfigName := GetExecutorConfigName(cmd) + execConfigName := GetExecutorConfigName(cmd) logger.Debug("Profile set to - ", profileName) - logger.Debug("Executor configuration set to - ", ExecConfigName) + logger.Debug("Executor configuration set to - ", execConfigName) + + if err := commonDependencies.Load(profileName, execConfigName); err != nil { + return err + } + + return nil }, } @@ -73,11 +83,9 @@ func init() { fs := afero.NewOsFs() logger = logrus.New() - commonDependencies := &command.Dependencies{ - Project: project.NewShoreProject(fs, logger), - Renderer: jsonnet.NewRenderer(fs, logger), - Backend: spinnaker.NewClient(logger), - Logger: logger, + commonDependencies = &command.Dependencies{ + Project: project.NewShoreProject(fs, logger), + Logger: logger, } rootCmd.PersistentFlags().CountVarP(&logVerbosity, "verbose", "v", "Logging verbosity") diff --git a/pkg/command/dependencies.go b/pkg/command/dependencies.go index 0ffafb2..551befe 100644 --- a/pkg/command/dependencies.go +++ b/pkg/command/dependencies.go @@ -1,16 +1,81 @@ package command import ( + "fmt" + "strings" + "github.com/Autodesk/shore/pkg/backend" + "github.com/Autodesk/shore/pkg/backend/spinnaker" + "github.com/Autodesk/shore/pkg/config" "github.com/Autodesk/shore/pkg/project" "github.com/Autodesk/shore/pkg/renderer" + "github.com/Autodesk/shore/pkg/renderer/jsonnet" "github.com/sirupsen/logrus" ) +// Renderer Enum +const ( + // JSONNET - Jsonnet Renderer + JSONNET string = "jsonnet" +) + +// Backend Enum +const ( + // SPINNAKER - Spinnaker Backend + SPINNAKER string = "spinnaker" +) + // Dependencies - Shared dependencies all controller MAY require type Dependencies struct { - Renderer renderer.Renderer - Backend backend.Backend - Logger logrus.FieldLogger - Project *project.Project + Renderer renderer.Renderer + Backend backend.Backend + Logger logrus.FieldLogger + Project *project.Project + ShoreConfig config.ShoreConfig + ShoreConfigOpts config.ShoreConfigOpts +} + +// Load - loads the shore config and sets the renderer and backend +func (d *Dependencies) Load(profileName, execConfigName string) error { + d.ShoreConfigOpts = config.ShoreConfigOpts{ + ProfileName: profileName, + ExecutorConfigName: execConfigName, + } + + var err error + d.ShoreConfig, err = config.LoadShoreConfig(d.Project) + if err != nil { + return err + } + + d.Renderer, err = d.initRenderer(d.ShoreConfig) + if err != nil { + return err + } + + d.Backend, err = d.initBackend(d.ShoreConfig) + return err +} + +// initRenderer initializes the Renderer based on the shore config +func (d *Dependencies) initRenderer(shoreConfig config.ShoreConfig) (renderer.Renderer, error) { + switch strings.ToLower(shoreConfig.Renderer[`type`].(string)) { + case JSONNET: + d.Logger.Debug("Using the Jsonnet Renderer") + return jsonnet.NewRenderer(d.Project.FS, d.Project.Log), nil + default: + return nil, fmt.Errorf("the following Renderer is undefined: %s", shoreConfig.Renderer[`type`].(string)) + } + +} + +// initRenderer initializes the Backend based on the shore config +func (d *Dependencies) initBackend(shoreConfig config.ShoreConfig) (backend.Backend, error) { + switch strings.ToLower(shoreConfig.Executor[`type`].(string)) { + case SPINNAKER: + d.Logger.Debug("Using the Spinnaker Backend") + return spinnaker.NewClient(d.Project.Log), nil + default: + return nil, fmt.Errorf("the following Executor is undefined: %s", shoreConfig.Executor[`type`].(string)) + } } diff --git a/pkg/command/dependencies_test.go b/pkg/command/dependencies_test.go new file mode 100644 index 0000000..119e360 --- /dev/null +++ b/pkg/command/dependencies_test.go @@ -0,0 +1,146 @@ +package command + +import ( + "fmt" + "os" + "path" + "testing" + + "github.com/Autodesk/shore/pkg/backend/spinnaker" + "github.com/Autodesk/shore/pkg/config" + "github.com/Autodesk/shore/pkg/project" + "github.com/Autodesk/shore/pkg/renderer/jsonnet" + "github.com/sirupsen/logrus/hooks/test" + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" +) + +var testPath = "/test" +var shoreConfigTemplate = `{ + "renderer": { + "type": "%s" + }, + "executor": { + "type": "%s", + "config": { + "default": "~/.spin/sb-config", + "prodSpin": "~/.spin/prod-config" + } + }, + "profiles": { + "default": { + "application": "test1test2test3", + "pipeline": "simple-pipeline-test", + "render": "render.yaml", + "exec": "exec.yaml", + "e2e": "e2e.yaml" + } + } +}` + +func SetupTestDependencies() *Dependencies { + os.Setenv("LOCAL", "true") + os.Setenv("SHORE_PROJECT_PATH", testPath) + + memFs := afero.NewMemMapFs() + memFs.Mkdir(testPath, os.ModePerm) + + logger, _ := test.NewNullLogger() + + return &Dependencies{ + Project: project.NewShoreProject(memFs, logger), + Logger: logger, + ShoreConfigOpts: config.ShoreConfigOpts{ + ProfileName: "default", + ExecutorConfigName: "default", + }, + } +} + +func TestPassingLoad(t *testing.T) { + // Given + deps := SetupTestDependencies() + + tests := []struct { + name string + configuredBackend string + configuredRenderer string + expectedBackend interface{} + expectedRenderer interface{} + }{ + { + name: "spinnaker/jsonnet", + configuredBackend: "spinnaker", + configuredRenderer: "jsonnet", + expectedBackend: &spinnaker.SpinClient{}, + expectedRenderer: &jsonnet.Jsonnet{}, + }, + { + name: "insensetive-spinnaker/jsonnet", + configuredBackend: "sPiNnAkEr", + configuredRenderer: "JsOnNeT", + expectedBackend: &spinnaker.SpinClient{}, + expectedRenderer: &jsonnet.Jsonnet{}, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + shoreConfig := fmt.Sprintf(shoreConfigTemplate, test.configuredRenderer, test.configuredBackend) + afero.WriteFile(deps.Project.FS, path.Join(testPath, "shore.json"), []byte(shoreConfig), os.ModePerm) + + // When + err := deps.Load("default", "default") + + // Then + assert.NoError(t, err) + assert.IsType(t, test.expectedRenderer, deps.Renderer) + assert.IsType(t, test.expectedBackend, deps.Backend) + assert.NotEmpty(t, deps.ShoreConfig) + assert.NotEmpty(t, deps.ShoreConfigOpts) + }) + } +} + +func TestFailingLoad(t *testing.T) { + // Given + deps := SetupTestDependencies() + + tests := []struct { + name string + configuredBackend string + configuredRenderer string + expectedError string + }{ + { + name: "wrong-backend", + configuredBackend: "yolo", + configuredRenderer: "jsonnet", + expectedError: "Executor is undefined", + }, + { + name: "wrong-renderer", + configuredBackend: "spinnaker", + configuredRenderer: "yolo", + expectedError: "Renderer is undefined", + }, + { + name: "malformed-config", + configuredBackend: "\"", + configuredRenderer: "yolo", + expectedError: "object not ended with", + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + shoreConfig := fmt.Sprintf(shoreConfigTemplate, test.configuredRenderer, test.configuredBackend) + afero.WriteFile(deps.Project.FS, path.Join(testPath, "shore.json"), []byte(shoreConfig), os.ModePerm) + + // When + err := deps.Load("default", "default") + + // Then + assert.Error(t, err) + assert.ErrorContains(t, err, test.expectedError) + }) + } +} diff --git a/pkg/config/configuration.go b/pkg/config/configuration.go index 8b61d1d..c0ebcf3 100644 --- a/pkg/config/configuration.go +++ b/pkg/config/configuration.go @@ -21,6 +21,12 @@ type ShoreConfig struct { Profiles map[string]interface{} `json:"profiles"` } +// ShoreConfigOpts - A struct presenting the options sets for the Shore Config. +type ShoreConfigOpts struct { + ProfileName string + ExecutorConfigName string +} + // ProfileMetaData - Metadata for a given profile. type ProfileMetaData struct { Application string