Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dkirillov/gh-26: Renderer and Executor determined by the shore config #73

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
28 changes: 18 additions & 10 deletions cmd/shore/shore.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -22,23 +20,35 @@ var version = "local"
var logVerbosity int
var logger *logrus.Logger

var commonDependencies *command.Dependencies

var rootCmd = &cobra.Command{
Use: "shore",
Short: "Shore - Pipeline Framework",
Long: "A Pipeline development framework for integrated pipelines.",
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
},
}

Expand Down Expand Up @@ -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")
Expand Down
73 changes: 69 additions & 4 deletions pkg/command/dependencies.go
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +34 to +35
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside from tests, where are these fields used?

Copy link
Collaborator Author

@dkirillov dkirillov Apr 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, they're not being used anywhere.

The intent is to have them there early so when render/save/execute/test-remote commands start making use of the shore config the information will already be there.

For instance, when the render command is ran it will have the shore config loaded in the dependencies as well as the chosen profile - and so it will be able to use the correct config based on the profile (all of which it will have access to).

}

// 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))
}
}
146 changes: 146 additions & 0 deletions pkg/command/dependencies_test.go
Original file line number Diff line number Diff line change
@@ -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)
})
}
}
6 changes: 6 additions & 0 deletions pkg/config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down