Skip to content

Commit

Permalink
feat: added the ability to inherit another biome with inherit_from (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeff-roche authored Jun 17, 2022
1 parent f3cc23a commit 5e82e60
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 40 deletions.
55 changes: 55 additions & 0 deletions src/lib/types/biome.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package types

import (
"fmt"
)

type Biome struct {
Config *BiomeConfig
SourceFile string
Expand All @@ -11,4 +15,55 @@ type BiomeConfig struct {
Commands []string `yaml:"commands"`
ExternalEnvFile string `yaml:"load_env"`
Environment map[string]interface{} `yaml:"environment"`
Inheritance string `yaml:"inherit_from"`
}

func (bc *BiomeConfig) Inherit(genepool map[string]*BiomeConfig) error {

parents := make([]string, 0, len(genepool))
unique := make(map[string]bool, len(genepool))
inherits_from := bc.Inheritance

for inherits_from != "" {
// Does it exist?
if _, exists := genepool[inherits_from]; !exists {
return fmt.Errorf("can not find inherited biome %s", inherits_from)
}

// Ciruclar inheritance check
if _, exists := unique[inherits_from]; exists {
return fmt.Errorf("circular biome inheritance found, %s inherited cyclicly", inherits_from)
}

unique[inherits_from] = true

parents = append(parents, inherits_from)
inherits_from = genepool[inherits_from].Inheritance
}

// Now do the inheritance
for _, i := range parents {
biome := genepool[i]
// AWS Profile (if one hasn't been set)
if bc.AwsProfile == "" {
bc.AwsProfile = biome.AwsProfile
}

// Load in any external env files (if one isn't specified yet)
if bc.ExternalEnvFile == "" {
bc.ExternalEnvFile = biome.ExternalEnvFile
}

// Envs (only if they don't already exist)
for env, val := range biome.Environment {
if _, exists := bc.Environment[env]; !exists {
bc.Environment[env] = val
}
}

// Commands (prepend to the commands array)
bc.Commands = append(biome.Commands, bc.Commands...)
}

return nil
}
51 changes: 42 additions & 9 deletions src/repos/biome_file_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
)

type BiomeFileParserIfc interface {
FindBiome(biomeName string, searchFiles []string) (*types.Biome, error)
FindBiome(biomeName string, searchFiles []string) (*types.BiomeConfig, error)
}

type BiomeFileParser struct{}
Expand All @@ -21,9 +21,7 @@ func NewBiomeFileParser() *BiomeFileParser {
return &BiomeFileParser{}
}

func (parser BiomeFileParser) FindBiome(biomeName string, searchFiles []string) (*types.Biome, error) {
var biome types.Biome // Setup a new biome

func (parser BiomeFileParser) FindBiome(biomeName string, searchFiles []string) (*types.BiomeConfig, error) {
// If we have any errors with the file, continue on to the next one
for _, fPath := range searchFiles {
if !fileio.FileExists(fPath) {
Expand All @@ -36,18 +34,53 @@ func (parser BiomeFileParser) FindBiome(biomeName string, searchFiles []string)
continue
}

biomeConfig := parser.loadBiomeFromFile(biomeName, freader)
if biomeConfig != nil {
biome.Config = biomeConfig
biome.SourceFile = fPath
biomes := parser.loadBiomes(freader)
if _, exists := biomes[biomeName]; exists {
biome := biomes[biomeName]

if err := biome.Inherit(biomes); err != nil {
return nil, err
}

return &biome, nil
return biome, nil
}
}

return nil, fmt.Errorf("unable to locate the '%s' biome", biomeName)
}

// Load biomes will load in all biomes from the given io.Reader
func (parser BiomeFileParser) loadBiomes(fcontents io.Reader) map[string]*types.BiomeConfig {
buff := new(bytes.Buffer)
buff.ReadFrom(fcontents)

// Loop over the documents in the .biome.yaml config
reader := bytes.NewReader(buff.Bytes())
decoder := yaml.NewDecoder(reader)

biomes := make(map[string]*types.BiomeConfig)

for {
var biomeCfg types.BiomeConfig

if err := decoder.Decode(&biomeCfg); err != nil {
if err != io.EOF {
continue
}

break
}

if biomeCfg.Name == "" {
continue
}

biomes[biomeCfg.Name] = &biomeCfg
}

return biomes
}

// loadBiomeFromFile will search for the biome in the file and if it finds it will parse and return it
func (parser BiomeFileParser) loadBiomeFromFile(biomeName string, fcontents io.Reader) *types.BiomeConfig {
buff := new(bytes.Buffer)
Expand Down
4 changes: 2 additions & 2 deletions src/repos/testing_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ type MockBiomeFileParser struct {
mock.Mock
}

func (m MockBiomeFileParser) FindBiome(biomeName string, searchFiles []string) (*types.Biome, error) {
func (m MockBiomeFileParser) FindBiome(biomeName string, searchFiles []string) (*types.BiomeConfig, error) {
args := m.Called(biomeName, searchFiles)
return args.Get(0).(*types.Biome), args.Error(1)
return args.Get(0).(*types.BiomeConfig), args.Error(1)
}

type MockAwsStsRepository struct {
Expand Down
18 changes: 9 additions & 9 deletions src/services/biome_configuration_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ var defaultFileNames = []string{".biome.yaml", ".biome.yml"}

// BiomeConfigurationService handles the loading and activation of biomes
type BiomeConfigurationService struct {
ActiveBiome *types.Biome
ActiveBiome *types.BiomeConfig
configFileRepo repos.BiomeFileParserIfc
awsStsRepo repos.AwsStsRepositoryIfc
}
Expand Down Expand Up @@ -85,7 +85,7 @@ func (svc *BiomeConfigurationService) ActivateBiome() error {
}

// Dot Env
if err := svc.loadFromEnv(svc.ActiveBiome.Config.ExternalEnvFile); err != nil {
if err := svc.loadFromEnv(svc.ActiveBiome.ExternalEnvFile); err != nil {
return err
}

Expand All @@ -104,8 +104,8 @@ func (svc *BiomeConfigurationService) ActivateBiome() error {

// loadAws will load in the AWS profile if one was specified
func (svc *BiomeConfigurationService) loadAws() error {
if svc.ActiveBiome.Config.AwsProfile != "" {
envCfg, err := svc.awsStsRepo.ConfigureSession(svc.ActiveBiome.Config.AwsProfile)
if svc.ActiveBiome.AwsProfile != "" {
envCfg, err := svc.awsStsRepo.ConfigureSession(svc.ActiveBiome.AwsProfile)
if err != nil {
return err
}
Expand All @@ -128,8 +128,8 @@ func (svc *BiomeConfigurationService) loadFromEnv(fname string) error {
for key, val := range loadedEnvs {

// Only save the key if one wasn't specified in the biome config
if _, exists := svc.ActiveBiome.Config.Environment[key]; !exists {
svc.ActiveBiome.Config.Environment[key] = val
if _, exists := svc.ActiveBiome.Environment[key]; !exists {
svc.ActiveBiome.Environment[key] = val
}
}
}
Expand All @@ -140,7 +140,7 @@ func (svc *BiomeConfigurationService) loadFromEnv(fname string) error {
// loadEnvs will parse all the envs in the Environment map and load them into memory
func (svc *BiomeConfigurationService) loadEnvs() error {
// Loop over the envs and set them
for env, val := range svc.ActiveBiome.Config.Environment {
for env, val := range svc.ActiveBiome.Environment {
setter, err := setters.GetEnvironmentSetter(env, val)
if err != nil {
return fmt.Errorf("error setting '%s': %v", env, err)
Expand All @@ -157,8 +157,8 @@ func (svc *BiomeConfigurationService) loadEnvs() error {

// runSetupCommands will run any command line commands specified in the biome configuration
func (svc *BiomeConfigurationService) runSetupCommands() error {
if len(svc.ActiveBiome.Config.Commands) > 0 {
for _, cmd := range svc.ActiveBiome.Config.Commands {
if len(svc.ActiveBiome.Commands) > 0 {
for _, cmd := range svc.ActiveBiome.Commands {
parts := strings.Split(cmd, " ")

if err := cmdr.Run(parts[0], parts[1:]...); err != nil {
Expand Down
35 changes: 15 additions & 20 deletions src/services/biome_configuration_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,12 @@ func TestBiomeConfigurationService(t *testing.T) {
biomeName := "myBiome"
testEnv := "MY_TEST_ENV"

getTestBiome := func() types.Biome {
return types.Biome{
SourceFile: sourceFilePath,
Config: &types.BiomeConfig{
Name: biomeName,
AwsProfile: "myProfile",
Environment: map[string]interface{}{
testEnv: "my_test_env_var",
},
getTestBiome := func() types.BiomeConfig {
return types.BiomeConfig{
Name: biomeName,
AwsProfile: "myProfile",
Environment: map[string]interface{}{
testEnv: "my_test_env_var",
},
}
}
Expand All @@ -47,8 +44,7 @@ func TestBiomeConfigurationService(t *testing.T) {

// Assert
assert.Nil(t, err)
assert.True(t, assert.ObjectsAreEqual(testBiome.Config, testSvc.ActiveBiome.Config))
assert.Equal(t, testBiome.SourceFile, testSvc.ActiveBiome.SourceFile)
assert.True(t, assert.ObjectsAreEqual(testBiome, *testSvc.ActiveBiome))
})

t.Run("should report an error if the biome can not be found", func(t *testing.T) {
Expand All @@ -59,7 +55,7 @@ func TestBiomeConfigurationService(t *testing.T) {
}
testErr := fmt.Errorf("biome not found")

mockRepo.On("FindBiome", biomeName, mock.Anything).Return(&types.Biome{}, testErr)
mockRepo.On("FindBiome", biomeName, mock.Anything).Return(&types.BiomeConfig{}, testErr)

// Act
err := testSvc.LoadBiomeFromDefaults(biomeName)
Expand Down Expand Up @@ -88,8 +84,7 @@ func TestBiomeConfigurationService(t *testing.T) {

// Assert
assert.Nil(t, err)
assert.True(t, assert.ObjectsAreEqual(testBiome.Config, testSvc.ActiveBiome.Config))
assert.Equal(t, testBiome.SourceFile, testSvc.ActiveBiome.SourceFile)
assert.True(t, assert.ObjectsAreEqual(testBiome, *testSvc.ActiveBiome))
})

t.Run("should report an error if the biome can not be found", func(t *testing.T) {
Expand All @@ -100,7 +95,7 @@ func TestBiomeConfigurationService(t *testing.T) {
}
testErr := fmt.Errorf("biome not found")

mockRepo.On("FindBiome", biomeName, mock.Anything).Return(&types.Biome{}, testErr)
mockRepo.On("FindBiome", biomeName, mock.Anything).Return(&types.BiomeConfig{}, testErr)

// Act
err := testSvc.LoadBiomeFromFile(biomeName, sourceFilePath)
Expand All @@ -122,7 +117,7 @@ func TestBiomeConfigurationService(t *testing.T) {
ActiveBiome: &b,
}

b.Config.AwsProfile = ""
b.AwsProfile = ""

t.Cleanup(func() {
os.Unsetenv(testEnv)
Expand All @@ -133,21 +128,21 @@ func TestBiomeConfigurationService(t *testing.T) {

// Assert
assert.Nil(t, err)
assert.Equal(t, b.Config.Environment[testEnv], os.Getenv(testEnv))
assert.Equal(t, b.Environment[testEnv], os.Getenv(testEnv))
})

t.Run("should load the AWS environment", func(t *testing.T) {
// Assemble
b := getTestBiome()
b.Config.Environment = map[string]interface{}{}
b.Environment = map[string]interface{}{}
mockRepo := repos.MockAwsStsRepository{}

testSvc := &BiomeConfigurationService{
ActiveBiome: &b,
awsStsRepo: &mockRepo,
}

mockRepo.On("ConfigureSession", b.Config.AwsProfile).Return(&types.AwsEnvConfig{}, nil)
mockRepo.On("ConfigureSession", b.AwsProfile).Return(&types.AwsEnvConfig{}, nil)
mockRepo.On("SetAwsEnvs", mock.Anything).Return()

// Act
Expand All @@ -173,7 +168,7 @@ func TestBiomeConfigurationService(t *testing.T) {
b := getTestBiome()
mockRepo := repos.MockAwsStsRepository{}
testError := fmt.Errorf("my dummy error")
mockRepo.On("ConfigureSession", b.Config.AwsProfile).Return(&types.AwsEnvConfig{}, testError)
mockRepo.On("ConfigureSession", b.AwsProfile).Return(&types.AwsEnvConfig{}, testError)
mockRepo.On("SetAwsEnvs", mock.Anything).Return()

testSvc := &BiomeConfigurationService{
Expand Down

0 comments on commit 5e82e60

Please sign in to comment.