2016-04-07 14:49:34 +02:00
|
|
|
package tasks
|
|
|
|
|
|
|
|
import (
|
|
|
|
"database/sql"
|
2016-04-08 21:41:20 +02:00
|
|
|
"encoding/json"
|
2016-04-07 14:49:34 +02:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"strconv"
|
2016-05-17 21:12:54 +02:00
|
|
|
"time"
|
2016-04-07 14:49:34 +02:00
|
|
|
|
2016-05-24 11:55:48 +02:00
|
|
|
database "github.com/ansible-semaphore/semaphore/db"
|
2016-04-07 14:49:34 +02:00
|
|
|
"github.com/ansible-semaphore/semaphore/models"
|
|
|
|
"github.com/ansible-semaphore/semaphore/util"
|
|
|
|
)
|
|
|
|
|
|
|
|
type task struct {
|
|
|
|
task models.Task
|
|
|
|
template models.Template
|
|
|
|
sshKey models.AccessKey
|
|
|
|
inventory models.Inventory
|
|
|
|
repository models.Repository
|
|
|
|
environment models.Environment
|
2016-04-17 12:41:36 +02:00
|
|
|
users []int
|
2016-04-17 20:01:51 +02:00
|
|
|
projectID int
|
2016-04-07 14:49:34 +02:00
|
|
|
}
|
|
|
|
|
2016-04-17 02:20:23 +02:00
|
|
|
func (t *task) fail() {
|
|
|
|
t.task.Status = "error"
|
2016-05-17 21:12:54 +02:00
|
|
|
t.updateStatus()
|
2016-04-17 02:20:23 +02:00
|
|
|
}
|
|
|
|
|
2016-04-07 14:49:34 +02:00
|
|
|
func (t *task) run() {
|
|
|
|
pool.running = t
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
fmt.Println("Stopped running tasks")
|
|
|
|
pool.running = nil
|
2016-04-17 02:20:23 +02:00
|
|
|
|
2016-05-17 21:12:54 +02:00
|
|
|
now := time.Now()
|
|
|
|
t.task.End = &now
|
|
|
|
t.updateStatus()
|
2016-04-17 20:01:51 +02:00
|
|
|
|
|
|
|
objType := "task"
|
|
|
|
desc := "Task ID " + strconv.Itoa(t.task.ID) + " finished"
|
|
|
|
if err := (models.Event{
|
|
|
|
ProjectID: &t.projectID,
|
|
|
|
ObjectType: &objType,
|
|
|
|
ObjectID: &t.task.ID,
|
|
|
|
Description: &desc,
|
|
|
|
}.Insert()); err != nil {
|
|
|
|
t.log("Fatal error inserting an event")
|
|
|
|
panic(err)
|
|
|
|
}
|
2016-04-07 14:49:34 +02:00
|
|
|
}()
|
|
|
|
|
2016-05-17 21:12:54 +02:00
|
|
|
if err := t.populateDetails(); err != nil {
|
|
|
|
t.log("Error: " + err.Error())
|
|
|
|
t.fail()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
fmt.Println(t.users)
|
|
|
|
now := time.Now()
|
|
|
|
t.task.Status = "running"
|
|
|
|
t.task.Start = &now
|
2016-06-02 20:26:51 +02:00
|
|
|
|
2016-05-17 21:12:54 +02:00
|
|
|
t.updateStatus()
|
2016-04-17 02:20:23 +02:00
|
|
|
}
|
|
|
|
|
2016-04-17 20:01:51 +02:00
|
|
|
objType := "task"
|
|
|
|
desc := "Task ID " + strconv.Itoa(t.task.ID) + " is running"
|
|
|
|
if err := (models.Event{
|
|
|
|
ProjectID: &t.projectID,
|
|
|
|
ObjectType: &objType,
|
|
|
|
ObjectID: &t.task.ID,
|
|
|
|
Description: &desc,
|
|
|
|
}.Insert()); err != nil {
|
|
|
|
t.log("Fatal error inserting an event")
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2016-04-07 14:49:34 +02:00
|
|
|
t.log("Started: " + strconv.Itoa(t.task.ID) + "\n")
|
|
|
|
|
|
|
|
if err := t.installKey(t.repository.SshKey); err != nil {
|
|
|
|
t.log("Failed installing ssh key for repository access: " + err.Error())
|
2016-04-17 02:20:23 +02:00
|
|
|
t.fail()
|
2016-04-07 14:49:34 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := t.updateRepository(); err != nil {
|
|
|
|
t.log("Failed updating repository: " + err.Error())
|
2016-04-17 02:20:23 +02:00
|
|
|
t.fail()
|
2016-04-07 14:49:34 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
if err := t.installInventory(); err != nil {
|
|
|
|
t.log("Failed to install inventory: " + err.Error())
|
2016-04-17 02:20:23 +02:00
|
|
|
t.fail()
|
2016-04-08 21:41:20 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// todo: write environment
|
|
|
|
|
|
|
|
if err := t.runPlaybook(); err != nil {
|
|
|
|
t.log("Running playbook failed: " + err.Error())
|
2016-04-17 02:20:23 +02:00
|
|
|
t.fail()
|
2016-04-08 21:41:20 +02:00
|
|
|
return
|
|
|
|
}
|
2016-04-17 02:20:23 +02:00
|
|
|
|
|
|
|
t.task.Status = "success"
|
2016-05-17 21:12:54 +02:00
|
|
|
t.updateStatus()
|
2016-04-07 14:49:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *task) fetch(errMsg string, ptr interface{}, query string, args ...interface{}) error {
|
|
|
|
err := database.Mysql.SelectOne(ptr, query, args...)
|
|
|
|
if err == sql.ErrNoRows {
|
|
|
|
t.log(errMsg)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
2016-04-17 02:20:23 +02:00
|
|
|
t.fail()
|
2016-04-07 14:49:34 +02:00
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *task) populateDetails() error {
|
|
|
|
// get template
|
|
|
|
if err := t.fetch("Template not found!", &t.template, "select * from project__template where id=?", t.task.TemplateID); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-04-17 12:41:36 +02:00
|
|
|
// get project users
|
|
|
|
var users []struct {
|
|
|
|
ID int `db:"id"`
|
|
|
|
}
|
|
|
|
if _, err := database.Mysql.Select(&users, "select user_id as id from project__user where project_id=?", t.template.ProjectID); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
t.users = []int{}
|
|
|
|
for _, user := range users {
|
|
|
|
t.users = append(t.users, user.ID)
|
|
|
|
}
|
|
|
|
|
2016-04-07 14:49:34 +02:00
|
|
|
// get access key
|
|
|
|
if err := t.fetch("Template Access Key not found!", &t.sshKey, "select * from access_key where id=?", t.template.SshKeyID); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if t.sshKey.Type != "ssh" {
|
|
|
|
t.log("Non ssh-type keys are currently not supported: " + t.sshKey.Type)
|
|
|
|
return errors.New("Unsupported SSH Key")
|
|
|
|
}
|
|
|
|
|
|
|
|
// get inventory
|
|
|
|
if err := t.fetch("Template Inventory not found!", &t.inventory, "select * from project__inventory where id=?", t.template.InventoryID); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
// get inventory services key
|
|
|
|
if t.inventory.KeyID != nil {
|
|
|
|
if err := t.fetch("Inventory AccessKey not found!", &t.inventory.Key, "select * from access_key where id=?", *t.inventory.KeyID); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// get inventory ssh key
|
|
|
|
if t.inventory.SshKeyID != nil {
|
|
|
|
if err := t.fetch("Inventory Ssh Key not found!", &t.inventory.SshKey, "select * from access_key where id=?", *t.inventory.SshKeyID); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-07 14:49:34 +02:00
|
|
|
// get repository
|
|
|
|
if err := t.fetch("Repository not found!", &t.repository, "select * from project__repository where id=?", t.template.RepositoryID); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// get repository access key
|
|
|
|
if err := t.fetch("Repository Access Key not found!", &t.repository.SshKey, "select * from access_key where id=?", t.repository.SshKeyID); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if t.repository.SshKey.Type != "ssh" {
|
|
|
|
t.log("Repository Access Key is not 'SSH': " + t.repository.SshKey.Type)
|
|
|
|
return errors.New("Unsupported SSH Key")
|
|
|
|
}
|
|
|
|
|
|
|
|
// get environment
|
|
|
|
if len(t.task.Environment) == 0 && t.template.EnvironmentID != nil {
|
|
|
|
err := t.fetch("Environment not found", &t.environment, "select * from project__environment where id=?", *t.template.EnvironmentID)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else if len(t.task.Environment) > 0 {
|
|
|
|
t.environment.JSON = t.task.Environment
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *task) installKey(key models.AccessKey) error {
|
2016-04-08 21:41:20 +02:00
|
|
|
t.log("access key " + key.Name + " installed")
|
|
|
|
err := ioutil.WriteFile(key.GetPath(), []byte(*key.Secret), 0600)
|
2016-04-07 14:49:34 +02:00
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
return err
|
|
|
|
}
|
2016-04-07 14:49:34 +02:00
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
func (t *task) updateRepository() error {
|
|
|
|
repoName := "repository_" + strconv.Itoa(t.repository.ID)
|
|
|
|
_, err := os.Stat(util.Config.TmpPath + "/" + repoName)
|
|
|
|
|
|
|
|
cmd := exec.Command("git")
|
2016-05-17 21:12:54 +02:00
|
|
|
cmd.Dir = util.Config.TmpPath
|
2016-04-08 21:41:20 +02:00
|
|
|
cmd.Env = []string{
|
|
|
|
"HOME=" + util.Config.TmpPath,
|
|
|
|
"PWD=" + util.Config.TmpPath,
|
|
|
|
"GIT_SSH_COMMAND=ssh -o StrictHostKeyChecking=no -i " + t.repository.SshKey.GetPath(),
|
|
|
|
// "GIT_FLUSH=1",
|
2016-04-07 14:49:34 +02:00
|
|
|
}
|
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
if err != nil && os.IsNotExist(err) {
|
|
|
|
t.log("Cloning repository")
|
|
|
|
cmd.Args = append(cmd.Args, "clone", t.repository.GitUrl, repoName)
|
|
|
|
} else if err != nil {
|
2016-04-07 14:49:34 +02:00
|
|
|
return err
|
2016-04-08 21:41:20 +02:00
|
|
|
} else {
|
|
|
|
t.log("Updating repository")
|
2016-05-17 21:12:54 +02:00
|
|
|
cmd.Dir += "/" + repoName
|
2016-04-08 21:41:20 +02:00
|
|
|
cmd.Args = append(cmd.Args, "pull", "origin", "master")
|
2016-04-07 14:49:34 +02:00
|
|
|
}
|
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
t.logCmd(cmd)
|
|
|
|
return cmd.Run()
|
2016-04-07 14:49:34 +02:00
|
|
|
}
|
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
func (t *task) runPlaybook() error {
|
|
|
|
playbookName := t.task.Playbook
|
|
|
|
if len(playbookName) == 0 {
|
|
|
|
playbookName = t.template.Playbook
|
|
|
|
}
|
2016-04-07 14:49:34 +02:00
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
args := []string{
|
|
|
|
"-i", util.Config.TmpPath + "/inventory_" + strconv.Itoa(t.task.ID),
|
|
|
|
}
|
2016-04-07 14:49:34 +02:00
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
if t.inventory.SshKeyID != nil {
|
|
|
|
args = append(args, "--private-key="+t.inventory.SshKey.GetPath())
|
|
|
|
}
|
2016-04-07 14:49:34 +02:00
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
if t.task.Debug {
|
|
|
|
args = append(args, "-vvvv")
|
|
|
|
}
|
2016-04-07 14:49:34 +02:00
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
if len(t.environment.JSON) > 0 {
|
|
|
|
args = append(args, "--extra-vars", t.environment.JSON)
|
|
|
|
}
|
|
|
|
|
|
|
|
var extraArgs []string
|
|
|
|
if t.template.Arguments != nil {
|
|
|
|
err := json.Unmarshal([]byte(*t.template.Arguments), &extraArgs)
|
|
|
|
if err != nil {
|
|
|
|
t.log("Could not unmarshal arguments to []string")
|
|
|
|
return err
|
|
|
|
}
|
2016-04-07 14:49:34 +02:00
|
|
|
}
|
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
if t.template.OverrideArguments {
|
|
|
|
args = extraArgs
|
|
|
|
} else {
|
|
|
|
args = append(args, extraArgs...)
|
|
|
|
args = append(args, playbookName)
|
|
|
|
}
|
2016-04-07 14:49:34 +02:00
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
cmd := exec.Command("ansible-playbook", args...)
|
|
|
|
cmd.Dir = util.Config.TmpPath + "/repository_" + strconv.Itoa(t.repository.ID)
|
2016-04-07 14:49:34 +02:00
|
|
|
cmd.Env = []string{
|
|
|
|
"HOME=" + util.Config.TmpPath,
|
2016-04-08 21:41:20 +02:00
|
|
|
"PWD=" + cmd.Dir,
|
|
|
|
"PYTHONUNBUFFERED=1",
|
|
|
|
// "GIT_FLUSH=1",
|
2016-04-07 14:49:34 +02:00
|
|
|
}
|
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
t.logCmd(cmd)
|
|
|
|
return cmd.Run()
|
2016-04-07 14:49:34 +02:00
|
|
|
}
|