2016-04-07 14:49:34 +02:00
|
|
|
package tasks
|
|
|
|
|
|
|
|
import (
|
2018-03-16 02:26:25 +01:00
|
|
|
"bytes"
|
2016-04-08 21:41:20 +02:00
|
|
|
"encoding/json"
|
2016-04-07 14:49:34 +02:00
|
|
|
"fmt"
|
2021-08-25 17:37:19 +02:00
|
|
|
"github.com/ansible-semaphore/semaphore/api/sockets"
|
2016-04-07 14:49:34 +02:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
2017-08-19 10:45:01 +02:00
|
|
|
"regexp"
|
2016-04-07 14:49:34 +02:00
|
|
|
"strconv"
|
2016-11-22 02:01:57 +01:00
|
|
|
"strings"
|
2016-05-17 21:12:54 +02:00
|
|
|
"time"
|
2016-04-07 14:49:34 +02:00
|
|
|
|
2021-04-15 18:45:00 +02:00
|
|
|
"github.com/ansible-semaphore/semaphore/api/helpers"
|
|
|
|
"github.com/ansible-semaphore/semaphore/db"
|
|
|
|
|
2018-06-08 03:58:08 +02:00
|
|
|
log "github.com/Sirupsen/logrus"
|
2021-04-15 18:45:00 +02:00
|
|
|
|
2016-04-07 14:49:34 +02:00
|
|
|
"github.com/ansible-semaphore/semaphore/util"
|
|
|
|
)
|
|
|
|
|
2018-03-27 22:12:47 +02:00
|
|
|
const (
|
2021-08-25 17:37:19 +02:00
|
|
|
taskRunningStatus = "running"
|
|
|
|
taskWaitingStatus = "waiting"
|
|
|
|
taskStoppingStatus = "stopping"
|
|
|
|
taskStoppedStatus = "stopped"
|
|
|
|
taskSuccessStatus = "success"
|
|
|
|
taskFailStatus = "error"
|
|
|
|
taskTypeID = "task"
|
2018-03-27 22:12:47 +02:00
|
|
|
)
|
|
|
|
|
2016-04-07 14:49:34 +02:00
|
|
|
type task struct {
|
2021-08-25 17:37:19 +02:00
|
|
|
store db.Store
|
|
|
|
task db.Task
|
|
|
|
template db.Template
|
|
|
|
inventory db.Inventory
|
|
|
|
repository db.Repository
|
|
|
|
environment db.Environment
|
|
|
|
users []int
|
|
|
|
projectID int
|
|
|
|
hosts []string
|
|
|
|
alertChat string
|
|
|
|
alert bool
|
|
|
|
prepared bool
|
|
|
|
process *os.Process
|
2016-04-07 14:49:34 +02:00
|
|
|
}
|
|
|
|
|
2021-08-31 14:03:52 +02:00
|
|
|
func (t *task) getRepoName() string {
|
|
|
|
return "repository_" + strconv.Itoa(t.repository.ID) + "_" + strconv.Itoa(t.template.ID)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *task) getRepoPath() string {
|
|
|
|
return util.Config.TmpPath + "/" + t.getRepoName()
|
|
|
|
}
|
|
|
|
|
2021-08-25 17:37:19 +02:00
|
|
|
func (t *task) setStatus(status string) {
|
|
|
|
if t.task.Status == taskStoppingStatus {
|
|
|
|
switch status {
|
|
|
|
case taskFailStatus:
|
|
|
|
status = taskStoppedStatus
|
|
|
|
case taskStoppedStatus:
|
|
|
|
default:
|
|
|
|
panic("stopping task cannot be " + status)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
t.task.Status = status
|
2016-05-17 21:12:54 +02:00
|
|
|
t.updateStatus()
|
2021-08-25 17:37:19 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t *task) updateStatus() {
|
|
|
|
for _, user := range t.users {
|
|
|
|
b, err := json.Marshal(&map[string]interface{}{
|
|
|
|
"type": "update",
|
|
|
|
"start": t.task.Start,
|
|
|
|
"end": t.task.End,
|
|
|
|
"status": t.task.Status,
|
|
|
|
"task_id": t.task.ID,
|
|
|
|
"project_id": t.projectID,
|
|
|
|
})
|
|
|
|
|
|
|
|
util.LogPanic(err)
|
|
|
|
|
|
|
|
sockets.Message(user, b)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := t.store.UpdateTask(t.task); err != nil {
|
|
|
|
t.panicOnError(err, "Failed to update task status")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *task) fail() {
|
|
|
|
t.setStatus(taskFailStatus)
|
2017-02-22 09:46:42 +01:00
|
|
|
t.sendMailAlert()
|
2017-03-22 08:22:09 +01:00
|
|
|
t.sendTelegramAlert()
|
2016-04-17 02:20:23 +02:00
|
|
|
}
|
|
|
|
|
2021-08-31 00:12:33 +02:00
|
|
|
func (t *task) destroyKeys() {
|
|
|
|
err := t.destroyKey(t.repository.SSHKey)
|
|
|
|
if err != nil {
|
|
|
|
t.log("Can't destroy repository SSH key, error: " + err.Error())
|
|
|
|
}
|
|
|
|
err = t.destroyKey(t.inventory.SSHKey)
|
|
|
|
if err != nil {
|
|
|
|
t.log("Can't destroy inventory SSH key, error: " + err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-30 21:42:11 +02:00
|
|
|
func (t *task) createTaskEvent() {
|
|
|
|
objType := taskTypeID
|
|
|
|
desc := "Task ID " + strconv.Itoa(t.task.ID) + " (" + t.template.Alias + ")" + " finished - " + strings.ToUpper(t.task.Status)
|
|
|
|
|
|
|
|
_, err := t.store.CreateEvent(db.Event{
|
|
|
|
UserID: t.task.UserID,
|
|
|
|
ProjectID: &t.projectID,
|
|
|
|
ObjectType: &objType,
|
|
|
|
ObjectID: &t.task.ID,
|
|
|
|
Description: &desc,
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
t.panicOnError(err, "Fatal error inserting an event")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
func (t *task) prepareRun() {
|
|
|
|
t.prepared = false
|
2016-04-07 14:49:34 +02:00
|
|
|
|
|
|
|
defer func() {
|
2018-06-08 03:58:08 +02:00
|
|
|
log.Info("Stopped preparing task " + strconv.Itoa(t.task.ID))
|
2021-03-12 21:20:18 +01:00
|
|
|
log.Info("Release resource locker with task " + strconv.Itoa(t.task.ID))
|
2018-06-07 09:29:55 +02:00
|
|
|
resourceLocker <- &resourceLock{lock: false, holder: t}
|
2016-04-17 20:01:51 +02:00
|
|
|
|
2021-08-30 21:42:11 +02:00
|
|
|
t.createTaskEvent()
|
2016-04-07 14:49:34 +02:00
|
|
|
}()
|
|
|
|
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
t.log("Preparing: " + strconv.Itoa(t.task.ID))
|
|
|
|
|
2018-02-15 21:29:03 +01:00
|
|
|
err := checkTmpDir(util.Config.TmpPath)
|
|
|
|
if err != nil {
|
|
|
|
t.log("Creating tmp dir failed: " + err.Error())
|
|
|
|
t.fail()
|
|
|
|
return
|
|
|
|
}
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
|
2016-05-17 21:12:54 +02:00
|
|
|
if err := t.populateDetails(); err != nil {
|
|
|
|
t.log("Error: " + err.Error())
|
|
|
|
t.fail()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-03-27 22:12:47 +02:00
|
|
|
objType := taskTypeID
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
desc := "Task ID " + strconv.Itoa(t.task.ID) + " (" + t.template.Alias + ")" + " is preparing"
|
2020-12-20 19:00:59 +01:00
|
|
|
_, err = t.store.CreateEvent(db.Event{
|
2021-08-25 17:37:19 +02:00
|
|
|
UserID: t.task.UserID,
|
2016-04-17 20:01:51 +02:00
|
|
|
ProjectID: &t.projectID,
|
|
|
|
ObjectType: &objType,
|
|
|
|
ObjectID: &t.task.ID,
|
|
|
|
Description: &desc,
|
2020-12-01 20:06:49 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
2016-04-17 20:01:51 +02:00
|
|
|
t.log("Fatal error inserting an event")
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
t.log("Prepare task with template: " + t.template.Alias + "\n")
|
2016-04-07 14:49:34 +02:00
|
|
|
|
2018-03-27 22:12:47 +02:00
|
|
|
if err := t.installKey(t.repository.SSHKey); err != nil {
|
2016-04-07 14:49:34 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-04-15 18:06:22 +02:00
|
|
|
if err := t.installRequirements(); err != nil {
|
2017-11-22 02:31:29 +01:00
|
|
|
t.log("Running galaxy failed: " + err.Error())
|
|
|
|
t.fail()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-09-01 23:14:32 +02:00
|
|
|
if err := t.installVaultPassFile(); err != nil {
|
|
|
|
t.log("Failed to install vault password file: " + err.Error())
|
|
|
|
t.fail()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
// todo: write environment
|
|
|
|
|
2018-03-16 03:00:14 +01:00
|
|
|
if stderr, err := t.listPlaybookHosts(); err != nil {
|
2018-03-16 02:26:25 +01:00
|
|
|
t.log("Listing playbook hosts failed: " + err.Error() + "\n" + stderr)
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
t.fail()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
t.prepared = true
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *task) run() {
|
|
|
|
defer func() {
|
2018-06-08 03:58:08 +02:00
|
|
|
log.Info("Stopped running task " + strconv.Itoa(t.task.ID))
|
2020-12-01 20:06:49 +01:00
|
|
|
log.Info("Release resource locker with task " + strconv.Itoa(t.task.ID))
|
2017-08-19 10:45:01 +02:00
|
|
|
resourceLocker <- &resourceLock{lock: false, holder: t}
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
t.task.End = &now
|
|
|
|
t.updateStatus()
|
2021-08-31 00:12:33 +02:00
|
|
|
t.createTaskEvent()
|
|
|
|
t.destroyKeys()
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
}()
|
|
|
|
|
2021-08-25 17:37:19 +02:00
|
|
|
if t.task.Status == taskStoppingStatus {
|
|
|
|
t.setStatus(taskStoppedStatus)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
{
|
|
|
|
now := time.Now()
|
|
|
|
t.task.Start = &now
|
2021-08-25 17:37:19 +02:00
|
|
|
t.setStatus(taskRunningStatus)
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
}
|
|
|
|
|
2018-03-27 22:12:47 +02:00
|
|
|
objType := taskTypeID
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
desc := "Task ID " + strconv.Itoa(t.task.ID) + " (" + t.template.Alias + ")" + " is running"
|
2020-12-01 20:06:49 +01:00
|
|
|
|
2020-12-20 19:00:59 +01:00
|
|
|
_, err := t.store.CreateEvent(db.Event{
|
2021-08-25 17:37:19 +02:00
|
|
|
UserID: t.task.UserID,
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
ProjectID: &t.projectID,
|
|
|
|
ObjectType: &objType,
|
|
|
|
ObjectID: &t.task.ID,
|
|
|
|
Description: &desc,
|
2020-12-01 20:06:49 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
t.log("Fatal error inserting an event")
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
t.log("Started: " + strconv.Itoa(t.task.ID))
|
|
|
|
t.log("Run task with template: " + t.template.Alias + "\n")
|
|
|
|
|
2021-08-25 17:37:19 +02:00
|
|
|
if t.task.Status == taskStoppingStatus {
|
|
|
|
t.setStatus(taskStoppedStatus)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
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
|
|
|
|
2021-08-25 17:37:19 +02:00
|
|
|
t.setStatus(taskSuccessStatus)
|
2016-04-07 14:49:34 +02:00
|
|
|
}
|
|
|
|
|
2020-12-20 19:00:59 +01:00
|
|
|
func (t *task) prepareError(err error, errMsg string) error {
|
2021-08-30 18:04:18 +02:00
|
|
|
if err == db.ErrNotFound {
|
2020-12-20 19:00:59 +01:00
|
|
|
t.log(errMsg)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
t.fail()
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2018-03-27 22:12:47 +02:00
|
|
|
//nolint: gocyclo
|
2016-04-07 14:49:34 +02:00
|
|
|
func (t *task) populateDetails() error {
|
|
|
|
// get template
|
2020-12-20 19:00:59 +01:00
|
|
|
var err error
|
|
|
|
|
|
|
|
t.template, err = t.store.GetTemplate(t.projectID, t.task.TemplateID)
|
|
|
|
if err != nil {
|
|
|
|
return t.prepareError(err, "Template not found!")
|
2016-04-07 14:49:34 +02:00
|
|
|
}
|
|
|
|
|
2017-04-18 16:36:09 +02:00
|
|
|
// get project alert setting
|
2020-12-20 19:00:59 +01:00
|
|
|
project, err := t.store.GetProject(t.template.ProjectID)
|
|
|
|
if err != nil {
|
|
|
|
return t.prepareError(err, "Project not found!")
|
2017-03-10 07:25:42 +01:00
|
|
|
}
|
2020-12-20 19:00:59 +01:00
|
|
|
|
2017-05-03 06:27:58 +02:00
|
|
|
t.alert = project.Alert
|
2018-03-27 22:12:47 +02:00
|
|
|
t.alertChat = project.AlertChat
|
2017-03-10 01:12:55 +01:00
|
|
|
|
2016-04-17 12:41:36 +02:00
|
|
|
// get project users
|
2020-12-20 19:00:59 +01:00
|
|
|
users, err := t.store.GetProjectUsers(t.template.ProjectID, db.RetrieveQueryParams{})
|
2021-03-12 21:30:17 +01:00
|
|
|
if err != nil {
|
|
|
|
return t.prepareError(err, "Users not found!")
|
|
|
|
}
|
2016-04-17 12:41:36 +02:00
|
|
|
|
|
|
|
t.users = []int{}
|
|
|
|
for _, user := range users {
|
|
|
|
t.users = append(t.users, user.ID)
|
|
|
|
}
|
|
|
|
|
2016-04-07 14:49:34 +02:00
|
|
|
// get inventory
|
2020-12-20 19:00:59 +01:00
|
|
|
t.inventory, err = t.store.GetInventory(t.template.ProjectID, t.template.InventoryID)
|
|
|
|
if err != nil {
|
|
|
|
return t.prepareError(err, "Template Inventory not found!")
|
2016-04-07 14:49:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// get repository
|
2021-05-06 10:32:13 +02:00
|
|
|
t.repository, err = t.store.GetRepository(t.template.ProjectID, t.template.RepositoryID)
|
2016-04-07 14:49:34 +02:00
|
|
|
|
2021-05-06 10:32:13 +02:00
|
|
|
if err != nil {
|
2016-04-07 14:49:34 +02:00
|
|
|
return err
|
|
|
|
}
|
2021-05-06 10:32:13 +02:00
|
|
|
|
2021-08-31 14:03:52 +02:00
|
|
|
//if t.repository.SSHKey.Type != db.AccessKeySSH {
|
|
|
|
// t.log("Repository Access Key is not 'SSH': " + t.repository.SSHKey.Type)
|
|
|
|
// return errors.New("unsupported SSH Key")
|
|
|
|
//}
|
2016-04-07 14:49:34 +02:00
|
|
|
|
|
|
|
// get environment
|
|
|
|
if len(t.task.Environment) == 0 && t.template.EnvironmentID != nil {
|
2021-05-06 10:32:13 +02:00
|
|
|
t.environment, err = t.store.GetEnvironment(t.template.ProjectID, *t.template.EnvironmentID)
|
2016-04-07 14:49:34 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else if len(t.task.Environment) > 0 {
|
|
|
|
t.environment.JSON = t.task.Environment
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-08-31 00:12:33 +02:00
|
|
|
func (t *task) destroyKey(key db.AccessKey) error {
|
|
|
|
path := key.GetPath()
|
|
|
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return os.Remove(path)
|
|
|
|
}
|
|
|
|
|
2021-09-01 23:14:32 +02:00
|
|
|
func (t *task) installVaultPassFile() error {
|
|
|
|
if t.template.VaultPassID == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
path := t.template.VaultPass.GetPath()
|
|
|
|
|
|
|
|
return ioutil.WriteFile(path, []byte(t.template.VaultPass.LoginPassword.Password), 0600)
|
|
|
|
}
|
|
|
|
|
2020-12-20 19:00:59 +01:00
|
|
|
func (t *task) installKey(key db.AccessKey) error {
|
2021-09-01 20:11:24 +02:00
|
|
|
if key.Type != db.AccessKeySSH {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
t.log("access key " + key.Name + " installed")
|
2017-04-18 16:21:20 +02:00
|
|
|
|
|
|
|
path := key.GetPath()
|
|
|
|
|
2021-09-01 16:38:28 +02:00
|
|
|
if key.SshKey.Passphrase != "" {
|
|
|
|
return fmt.Errorf("ssh key with passphrase not supported")
|
2021-08-31 01:02:41 +02:00
|
|
|
}
|
|
|
|
|
2021-09-01 16:38:28 +02:00
|
|
|
return ioutil.WriteFile(path, []byte(key.SshKey.PrivateKey), 0600)
|
2016-04-08 21:41:20 +02:00
|
|
|
}
|
2016-04-07 14:49:34 +02:00
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
func (t *task) updateRepository() error {
|
2021-08-31 14:03:52 +02:00
|
|
|
t.getRepoPath()
|
|
|
|
repoName := t.getRepoName()
|
|
|
|
_, err := os.Stat(t.getRepoPath())
|
2016-04-08 21:41:20 +02:00
|
|
|
|
2018-06-07 09:29:55 +02:00
|
|
|
cmd := exec.Command("git") //nolint: gas
|
2016-05-17 21:12:54 +02:00
|
|
|
cmd.Dir = util.Config.TmpPath
|
2017-02-08 13:26:15 +01:00
|
|
|
|
2021-08-31 14:03:52 +02:00
|
|
|
switch t.repository.SSHKey.Type {
|
|
|
|
case db.AccessKeySSH:
|
|
|
|
gitSSHCommand := "ssh -o StrictHostKeyChecking=no -i " + t.repository.SSHKey.GetPath()
|
|
|
|
cmd.Env = t.envVars(util.Config.TmpPath, util.Config.TmpPath, &gitSSHCommand)
|
|
|
|
case db.AccessKeyNone:
|
|
|
|
cmd.Env = t.envVars(util.Config.TmpPath, util.Config.TmpPath, nil)
|
|
|
|
default:
|
|
|
|
return fmt.Errorf("unsupported access key type: " + t.repository.SSHKey.Type)
|
|
|
|
}
|
2016-04-07 14:49:34 +02:00
|
|
|
|
2018-03-27 22:12:47 +02:00
|
|
|
repoURL, repoTag := t.repository.GitURL, "master"
|
2016-11-22 02:07:00 +01:00
|
|
|
if split := strings.Split(repoURL, "#"); len(split) > 1 {
|
|
|
|
repoURL, repoTag = split[0], split[1]
|
|
|
|
}
|
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
if err != nil && os.IsNotExist(err) {
|
2016-11-22 02:07:00 +01:00
|
|
|
t.log("Cloning repository " + repoURL)
|
|
|
|
cmd.Args = append(cmd.Args, "clone", "--recursive", "--branch", repoTag, repoURL, repoName)
|
2016-04-08 21:41:20 +02:00
|
|
|
} else if err != nil {
|
2016-04-07 14:49:34 +02:00
|
|
|
return err
|
2016-04-08 21:41:20 +02:00
|
|
|
} else {
|
2016-11-22 02:07:00 +01:00
|
|
|
t.log("Updating repository " + repoURL)
|
2016-05-17 21:12:54 +02:00
|
|
|
cmd.Dir += "/" + repoName
|
2016-11-22 02:07:00 +01:00
|
|
|
cmd.Args = append(cmd.Args, "pull", "origin", repoTag)
|
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
|
|
|
}
|
|
|
|
|
2021-04-15 18:06:22 +02:00
|
|
|
func (t *task) installRequirements() error {
|
2021-09-01 18:18:49 +02:00
|
|
|
requirementsFilePath := fmt.Sprintf("%s/roles/requirements.yml", t.getRepoPath())
|
|
|
|
requirementsHashFilePath := fmt.Sprintf("%s/requirements.md5", t.getRepoPath())
|
2021-04-15 18:40:16 +02:00
|
|
|
|
2021-04-15 18:07:49 +02:00
|
|
|
if _, err := os.Stat(requirementsFilePath); err != nil {
|
2021-04-15 18:51:44 +02:00
|
|
|
t.log("No roles/requirements.yml file found. Skip galaxy install process.\n")
|
2021-04-15 18:07:49 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-04-15 18:40:16 +02:00
|
|
|
if hasRequirementsChanges(requirementsFilePath, requirementsHashFilePath) {
|
|
|
|
if err := t.runGalaxy([]string{
|
|
|
|
"install",
|
|
|
|
"-r",
|
|
|
|
"roles/requirements.yml",
|
|
|
|
"--force",
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := writeMD5Hash(requirementsFilePath, requirementsHashFilePath); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
t.log("roles/requirements.yml has no changes. Skip galaxy install process.\n")
|
2021-04-15 18:06:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-11-20 21:48:25 +01:00
|
|
|
func (t *task) runGalaxy(args []string) error {
|
2018-06-07 09:29:55 +02:00
|
|
|
cmd := exec.Command("ansible-galaxy", args...) //nolint: gas
|
2021-08-31 14:03:52 +02:00
|
|
|
cmd.Dir = t.getRepoPath()
|
2017-02-08 13:26:15 +01:00
|
|
|
|
2018-03-27 22:12:47 +02:00
|
|
|
gitSSHCommand := "ssh -o StrictHostKeyChecking=no -i " + t.repository.SSHKey.GetPath()
|
2017-04-18 16:21:20 +02:00
|
|
|
cmd.Env = t.envVars(util.Config.TmpPath, cmd.Dir, &gitSSHCommand)
|
2016-06-30 17:34:35 +02:00
|
|
|
|
|
|
|
t.logCmd(cmd)
|
|
|
|
return cmd.Run()
|
|
|
|
}
|
|
|
|
|
2018-03-16 03:00:14 +01:00
|
|
|
func (t *task) listPlaybookHosts() (string, error) {
|
2018-06-07 09:29:55 +02:00
|
|
|
|
|
|
|
if util.Config.ConcurrencyMode == "project" {
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
args, err := t.getPlaybookArgs()
|
|
|
|
if err != nil {
|
2018-03-16 03:00:14 +01:00
|
|
|
return "", err
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
}
|
|
|
|
args = append(args, "--list-hosts")
|
|
|
|
|
2018-06-07 09:29:55 +02:00
|
|
|
cmd := exec.Command("ansible-playbook", args...) //nolint: gas
|
2021-08-31 14:03:52 +02:00
|
|
|
cmd.Dir = t.getRepoPath()
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
cmd.Env = t.envVars(util.Config.TmpPath, cmd.Dir, nil)
|
|
|
|
|
2018-03-16 02:26:25 +01:00
|
|
|
var errb bytes.Buffer
|
|
|
|
cmd.Stderr = &errb
|
|
|
|
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
out, err := cmd.Output()
|
2018-03-16 02:26:25 +01:00
|
|
|
|
2018-03-27 22:12:47 +02:00
|
|
|
re := regexp.MustCompile(`(?m)^\\s{6}(.*)$`)
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
matches := re.FindAllSubmatch(out, 20)
|
|
|
|
hosts := make([]string, len(matches))
|
2018-03-27 22:12:47 +02:00
|
|
|
for i := range matches {
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
hosts[i] = string(matches[i][1])
|
|
|
|
}
|
|
|
|
t.hosts = hosts
|
2018-03-20 01:12:43 +01:00
|
|
|
return errb.String(), err
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
}
|
|
|
|
|
2021-08-25 17:37:19 +02:00
|
|
|
func (t *task) runPlaybook() (err error) {
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
args, err := t.getPlaybookArgs()
|
|
|
|
if err != nil {
|
2021-08-25 17:37:19 +02:00
|
|
|
return
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
}
|
2018-06-07 09:29:55 +02:00
|
|
|
cmd := exec.Command("ansible-playbook", args...) //nolint: gas
|
2021-08-31 14:03:52 +02:00
|
|
|
cmd.Dir = t.getRepoPath()
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
cmd.Env = t.envVars(util.Config.TmpPath, cmd.Dir, nil)
|
|
|
|
|
|
|
|
t.logCmd(cmd)
|
2018-02-05 19:51:14 +01:00
|
|
|
cmd.Stdin = strings.NewReader("")
|
2021-08-25 17:37:19 +02:00
|
|
|
err = cmd.Start()
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
t.process = cmd.Process
|
|
|
|
err = cmd.Wait()
|
|
|
|
return
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
}
|
|
|
|
|
2021-09-01 23:14:32 +02:00
|
|
|
func (t *task) getExtraVars() (string, error) {
|
|
|
|
extraVars := make(map[string]interface{})
|
|
|
|
|
|
|
|
if t.inventory.SSHKey.Type == db.AccessKeyLoginPassword {
|
|
|
|
if t.inventory.SSHKey.LoginPassword.Login != "" {
|
|
|
|
extraVars["ansible_user"] = t.inventory.SSHKey.LoginPassword.Login
|
|
|
|
}
|
|
|
|
extraVars["ansible_password"] = t.inventory.SSHKey.LoginPassword.Password
|
|
|
|
}
|
|
|
|
|
|
|
|
if t.inventory.BecomeKey.Type == db.AccessKeyLoginPassword {
|
|
|
|
if t.inventory.SSHKey.LoginPassword.Login != "" {
|
|
|
|
extraVars["ansible_become_user"] = t.inventory.SSHKey.LoginPassword.Login
|
|
|
|
}
|
|
|
|
extraVars["ansible_become_password"] = t.inventory.SSHKey.LoginPassword.Password
|
|
|
|
}
|
|
|
|
|
|
|
|
if t.environment.JSON != "" {
|
|
|
|
err := json.Unmarshal([]byte(t.environment.JSON), &extraVars)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-01 23:22:31 +02:00
|
|
|
delete(extraVars, "ENV")
|
2021-09-01 23:14:32 +02:00
|
|
|
|
|
|
|
ev, err := json.Marshal(extraVars)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return string(ev), nil
|
|
|
|
}
|
|
|
|
|
2018-03-27 22:12:47 +02:00
|
|
|
//nolint: gocyclo
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
func (t *task) getPlaybookArgs() ([]string, error) {
|
2016-04-08 21:41:20 +02:00
|
|
|
playbookName := t.task.Playbook
|
|
|
|
if len(playbookName) == 0 {
|
|
|
|
playbookName = t.template.Playbook
|
|
|
|
}
|
2016-04-07 14:49:34 +02:00
|
|
|
|
2018-03-16 02:26:25 +01:00
|
|
|
var inventory string
|
2017-10-26 09:00:48 +02:00
|
|
|
switch t.inventory.Type {
|
|
|
|
case "file":
|
|
|
|
inventory = t.inventory.Inventory
|
|
|
|
default:
|
|
|
|
inventory = util.Config.TmpPath + "/inventory_" + strconv.Itoa(t.task.ID)
|
|
|
|
}
|
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
args := []string{
|
2017-10-26 09:00:48 +02:00
|
|
|
"-i", inventory,
|
2016-04-08 21:41:20 +02:00
|
|
|
}
|
2016-04-07 14:49:34 +02:00
|
|
|
|
2021-08-30 16:24:20 +02:00
|
|
|
if t.inventory.SSHKeyID != nil && t.inventory.SSHKey.Type == db.AccessKeySSH {
|
2018-03-27 22:12:47 +02:00
|
|
|
args = append(args, "--private-key="+t.inventory.SSHKey.GetPath())
|
2016-04-08 21:41:20 +02:00
|
|
|
}
|
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-06-30 16:57:45 +02:00
|
|
|
if t.task.DryRun {
|
|
|
|
args = append(args, "--check")
|
|
|
|
}
|
|
|
|
|
2021-09-01 23:14:32 +02:00
|
|
|
if t.template.VaultPassID != nil {
|
|
|
|
args = append(args, "--vault-password-file", t.template.VaultPass.GetPath())
|
|
|
|
}
|
2018-02-14 15:54:04 +01:00
|
|
|
|
2021-09-01 23:14:32 +02:00
|
|
|
extraVars, err := t.getExtraVars()
|
|
|
|
if err != nil {
|
|
|
|
t.log(err.Error())
|
|
|
|
t.log("Could not remove command environment, if existant it will be passed to --extra-vars. This is not fatal but be aware of side effects")
|
|
|
|
} else if extraVars != "" {
|
|
|
|
args = append(args, "--extra-vars", extraVars)
|
2016-04-08 21:41:20 +02:00
|
|
|
}
|
|
|
|
|
2018-09-11 13:49:03 +02:00
|
|
|
var templateExtraArgs []string
|
2016-04-08 21:41:20 +02:00
|
|
|
if t.template.Arguments != nil {
|
2018-09-11 13:49:03 +02:00
|
|
|
err := json.Unmarshal([]byte(*t.template.Arguments), &templateExtraArgs)
|
|
|
|
if err != nil {
|
|
|
|
t.log("Could not unmarshal arguments to []string")
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var taskExtraArgs []string
|
|
|
|
if t.task.Arguments != nil {
|
|
|
|
err := json.Unmarshal([]byte(*t.task.Arguments), &taskExtraArgs)
|
2016-04-08 21:41:20 +02:00
|
|
|
if err != nil {
|
|
|
|
t.log("Could not unmarshal arguments to []string")
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
return nil, err
|
2016-04-08 21:41:20 +02:00
|
|
|
}
|
2016-04-07 14:49:34 +02:00
|
|
|
}
|
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
if t.template.OverrideArguments {
|
2018-09-11 13:49:03 +02:00
|
|
|
args = templateExtraArgs
|
2016-04-08 21:41:20 +02:00
|
|
|
} else {
|
2018-09-11 13:49:03 +02:00
|
|
|
args = append(args, templateExtraArgs...)
|
|
|
|
args = append(args, taskExtraArgs...)
|
2016-04-08 21:41:20 +02:00
|
|
|
args = append(args, playbookName)
|
|
|
|
}
|
Allow concurrency for tasks that does not collide
Two different concurrency modes are implemented, and is enabled by
setting "concurrency_mode" in the config file to either "project" or "node".
When "project" concurrency is enabled, tasks will run in parallel if and
only if they do not share the same project id, with no regard to the
nodes/hosts that are affected.
When "node" concurrency is enabled, a task will run in parallel if and
only if the hosts affected by tasks already running does not intersect
with the hosts that would be affected by the task in question.
If "concurrency_mode" is not specified, no task will start before the
previous one has finished.
The collision check is based on the output from the "--list-hosts"
argument to ansible, which uses the hosts specified in the inventory.
Thus, if two different hostnames are used that points to the same node,
such as "127.0.0.1" and "localhost", there will be no collision and two
tasks may connect to the same node concurrently. If this behaviour is
not desired, one should make sure to not include aliases for their hosts
in their inventories when enabling concurrency mode.
To restrict the amount of parallel tasks that runs at the same time, one
can add the "max_parallel_tasks" to the config file. This defaults to a
humble 10 if not specified.
2017-05-29 17:27:56 +02:00
|
|
|
return args, nil
|
2016-04-07 14:49:34 +02:00
|
|
|
}
|
2017-02-08 13:26:15 +01:00
|
|
|
|
2017-04-18 16:21:20 +02:00
|
|
|
func (t *task) envVars(home string, pwd string, gitSSHCommand *string) []string {
|
2017-02-08 13:26:15 +01:00
|
|
|
env := os.Environ()
|
2017-02-08 13:36:44 +01:00
|
|
|
env = append(env, fmt.Sprintf("HOME=%s", home))
|
|
|
|
env = append(env, fmt.Sprintf("PWD=%s", pwd))
|
2017-02-08 13:26:15 +01:00
|
|
|
env = append(env, fmt.Sprintln("PYTHONUNBUFFERED=1"))
|
|
|
|
//env = append(env, fmt.Sprintln("GIT_FLUSH=1"))
|
2018-02-14 15:54:04 +01:00
|
|
|
env = append(env, extractCommandEnvironment(t.environment.JSON)...)
|
|
|
|
|
2017-04-18 16:21:20 +02:00
|
|
|
if gitSSHCommand != nil {
|
|
|
|
env = append(env, fmt.Sprintf("GIT_SSH_COMMAND=%s", *gitSSHCommand))
|
2017-02-08 13:26:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return env
|
|
|
|
}
|
2018-02-14 15:54:04 +01:00
|
|
|
|
2021-04-15 18:39:19 +02:00
|
|
|
func hasRequirementsChanges(requirementsFilePath string, requirementsHashFilePath string) bool {
|
|
|
|
oldFileMD5HashBytes, err := ioutil.ReadFile(requirementsHashFilePath)
|
|
|
|
if err != nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
newFileMD5Hash, err := helpers.GetMD5Hash(requirementsFilePath)
|
|
|
|
if err != nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return string(oldFileMD5HashBytes) != newFileMD5Hash
|
|
|
|
}
|
|
|
|
|
2021-04-15 18:39:36 +02:00
|
|
|
func writeMD5Hash(requirementsFile string, requirementsHashFile string) error {
|
|
|
|
newFileMD5Hash, err := helpers.GetMD5Hash(requirementsFile)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return ioutil.WriteFile(requirementsHashFile, []byte(newFileMD5Hash), 0644)
|
|
|
|
}
|
|
|
|
|
2018-02-14 15:54:04 +01:00
|
|
|
// extractCommandEnvironment unmarshalls a json string, extracts the ENV key from it and returns it as
|
|
|
|
// []string where strings are in key=value format
|
2018-03-15 00:52:37 +01:00
|
|
|
func extractCommandEnvironment(envJSON string) []string {
|
2018-02-14 15:54:04 +01:00
|
|
|
env := make([]string, 0)
|
|
|
|
var js map[string]interface{}
|
2018-03-15 00:52:37 +01:00
|
|
|
err := json.Unmarshal([]byte(envJSON), &js)
|
2018-02-14 15:54:04 +01:00
|
|
|
if err == nil {
|
|
|
|
if cfg, ok := js["ENV"]; ok {
|
|
|
|
switch v := cfg.(type) {
|
|
|
|
case map[string]interface{}:
|
|
|
|
for key, val := range v {
|
|
|
|
env = append(env, fmt.Sprintf("%s=%s", key, val))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return env
|
|
|
|
}
|
|
|
|
|
2018-02-15 21:29:03 +01:00
|
|
|
// checkTmpDir checks to see if the temporary directory exists
|
|
|
|
// and if it does not attempts to create it
|
|
|
|
func checkTmpDir(path string) error {
|
2018-03-27 22:12:47 +02:00
|
|
|
var err error
|
|
|
|
if _, err = os.Stat(path); err != nil {
|
2018-02-15 21:29:03 +01:00
|
|
|
if os.IsNotExist(err) {
|
2018-02-28 10:02:54 +01:00
|
|
|
return os.MkdirAll(path, 0700)
|
2018-02-15 21:29:03 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return err
|
2021-05-06 10:32:13 +02:00
|
|
|
}
|