167 lines
3.4 KiB
Go
167 lines
3.4 KiB
Go
package runtime
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
log "github.com/sirupsen/logrus"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// Package represents a package
|
|
type Package struct {
|
|
Version string `yaml:"version"`
|
|
Name string `yaml:"name"`
|
|
Imports []string `yaml:"imports"`
|
|
Tasks []Task `yaml:"tasks"`
|
|
importTasks []Task `yaml:"-"`
|
|
Main struct {
|
|
Hosts []string `yaml:"hosts"`
|
|
Tasks []string `yaml:"tasks"`
|
|
} `yaml:"main"`
|
|
}
|
|
|
|
// NewPackageFromFile load a package from file
|
|
func NewPackageFromFile(fPath string) (*Package, error) {
|
|
bs, err := ioutil.ReadFile(fPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var pkg Package
|
|
if err := yaml.Unmarshal(bs, &pkg); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, imp := range pkg.Imports {
|
|
fields := strings.Fields(imp)
|
|
if len(fields) == 1 {
|
|
subPkgPath := filepath.Join(filepath.Dir(fPath), fields[0]+".yml")
|
|
subPkg, err := NewPackageFromFile(subPkgPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pkg.importTasks = append(pkg.importTasks, subPkg.Tasks...)
|
|
} else if len(fields) == 3 && fields[1] == "from" {
|
|
subPkgPath := filepath.Join(filepath.Dir(fPath), fields[2]+".yml")
|
|
subPkg, err := NewPackageFromFile(subPkgPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var found bool
|
|
for _, t := range subPkg.Tasks {
|
|
if t.Name == fields[0] {
|
|
pkg.importTasks = append(pkg.importTasks, t)
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return nil, fmt.Errorf("load %s failed", imp)
|
|
}
|
|
}
|
|
}
|
|
|
|
return &pkg, nil
|
|
}
|
|
|
|
type ErrTaskNotExist struct {
|
|
TaskName string
|
|
}
|
|
|
|
func (err ErrTaskNotExist) Error() string {
|
|
return fmt.Sprintf("%s is not exist", err.TaskName)
|
|
}
|
|
|
|
func IsErrTaskNotExist(err error) bool {
|
|
_, ok := err.(ErrTaskNotExist)
|
|
return ok
|
|
}
|
|
|
|
// FindTask find a task from the package
|
|
func (pkg *Package) FindTask(taskname string) (*Task, error) {
|
|
for _, t := range pkg.Tasks {
|
|
if t.Name == taskname {
|
|
return &t, nil
|
|
}
|
|
}
|
|
|
|
for _, t := range pkg.importTasks {
|
|
if t.Name == taskname {
|
|
return &t, nil
|
|
}
|
|
}
|
|
return nil, ErrTaskNotExist{taskname}
|
|
}
|
|
|
|
// Run the package with special hosts and tasks
|
|
func (pkg *Package) Run(hosts []string, tasks []string) error {
|
|
if len(hosts) == 0 {
|
|
hosts = pkg.Main.Hosts
|
|
}
|
|
if len(hosts) == 0 {
|
|
return errors.New("A runnable package must have a main section")
|
|
}
|
|
|
|
if len(tasks) == 0 {
|
|
tasks = pkg.Main.Tasks
|
|
}
|
|
if len(tasks) == 0 {
|
|
return errors.New("No tasks to run")
|
|
}
|
|
|
|
log.Infof("Run package %s with %v", pkg.Name, hosts)
|
|
|
|
var hostTasks = make(map[string][]*Work)
|
|
|
|
for _, taskName := range tasks {
|
|
t, args, err := splitTaskCmd(taskName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
task, err := pkg.FindTask(t)
|
|
if err != nil {
|
|
if IsErrTaskNotExist(err) {
|
|
log.Warnf("No task named %s", taskName)
|
|
continue
|
|
}
|
|
return err
|
|
}
|
|
task.Pkg = pkg
|
|
for _, host := range hosts {
|
|
hostTasks[host] = append(hostTasks[host], &Work{
|
|
host: host,
|
|
task: task,
|
|
args: args,
|
|
})
|
|
}
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
for _, works := range hostTasks {
|
|
wg.Add(1)
|
|
go func(works []*Work) {
|
|
for _, work := range works {
|
|
if err := work.Run(); err != nil {
|
|
log.Errorf("[%s] run task %s failed: %v", work.host,
|
|
work.task.Name, err)
|
|
break
|
|
} else {
|
|
log.Infof("[%s] run task %s successfully.", work.host,
|
|
work.task.Name)
|
|
}
|
|
}
|
|
wg.Done()
|
|
}(works)
|
|
}
|
|
wg.Wait()
|
|
return nil
|
|
}
|