plugin/step/group/group.go (102 lines of code) (raw):
package group
/*
Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
*/
import (
"fmt"
"log"
"sync"
"github.com/facebookincubator/go2chef"
"github.com/mitchellh/mapstructure"
)
// TypeName is the name of this step plugin
const TypeName = "go2chef.step.group"
// StepGroup defines a step that consists of other steps
//
// StepGroups download all resources in parallel and then execute
// steps sequentially. If you're doing a bunch of steps you
// probably want to use a `step_group` for it.
type StepGroup struct {
GroupName string `mapstructure:"name"`
logger go2chef.Logger
Steps []go2chef.Step
}
func (g *StepGroup) String() string {
return "<Step.Group:" + g.GroupName + ">"
}
// Name gets the name of this step instance
func (g *StepGroup) Name() string {
return g.GroupName
}
// Type returns the type of this step instance
func (g *StepGroup) Type() string {
return TypeName
}
// SetName sets the name of this step instance
func (g *StepGroup) SetName(n string) {
g.GroupName = n
}
// Download runs the Download function of each substep in parallel
func (g *StepGroup) Download() (err error) {
g.logger.WriteEvent(go2chef.NewEvent("STEP_GROUP_DOWNLOAD_START", TypeName, g.GroupName))
defer func() {
event := "STEP_GROUP_DOWNLOAD_COMPLETE"
if err != nil {
event = "STEP_GROUP_DOWNLOAD_FAILURE"
}
g.logger.WriteEvent(go2chef.NewEvent(event, TypeName, g.GroupName))
}()
var wg sync.WaitGroup
errs := make(chan error, len(g.Steps))
for _, s := range g.Steps {
wg.Add(1)
go func(st go2chef.Step, errs chan<- error) {
defer wg.Done()
if err := st.Download(); err != nil {
errs <- err
}
}(s, errs)
}
wg.Wait()
close(errs)
count := 0
for err := range errs {
log.Printf("caught error: %s", err)
count++
}
if count != 0 {
return fmt.Errorf("errors during step group execution")
}
return nil
}
// Execute runs the Execute function of each substep in sequence
func (g *StepGroup) Execute() (err error) {
g.logger.WriteEvent(go2chef.NewEvent("STEP_GROUP_EXECUTE_START", TypeName, g.GroupName))
defer func() {
event := "STEP_GROUP_EXECUTE_COMPLETE"
if err != nil {
event = "STEP_GROUP_EXECUTE_FAILURE"
}
g.logger.WriteEvent(go2chef.NewEvent(event, TypeName, g.GroupName))
}()
for _, s := range g.Steps {
if err := s.Execute(); err != nil {
return err
}
}
return nil
}
// Loader provides an instantiation function for this step plugin
func Loader(config map[string]interface{}) (go2chef.Step, error) {
// parse interior steps here
structure := struct {
Name string `mapstructure:"name"`
Steps []map[string]interface{} `mapstructure:"steps"`
}{
Steps: make([]map[string]interface{}, 0),
}
logger := go2chef.GetGlobalLogger()
if err := mapstructure.Decode(config, &structure); err != nil {
logger.Errorf("failed to parse configuration for %s: %s", TypeName, err)
return nil, err
}
steps, err := go2chef.GetSteps(config)
if err != nil {
return nil, err
}
g := StepGroup{
GroupName: structure.Name,
logger: go2chef.GetGlobalLogger(),
Steps: steps,
}
return &g, nil
}
var _ go2chef.Step = &StepGroup{}
var _ go2chef.StepLoader = Loader
func init() {
go2chef.RegisterStep(TypeName, Loader)
}