2016-04-07 14:49:34 +02:00
|
|
|
package tasks
|
|
|
|
|
|
|
|
import (
|
2018-03-16 02:26:25 +01:00
|
|
|
"bytes"
|
2016-04-07 14:49:34 +02:00
|
|
|
"database/sql"
|
2016-04-08 21:41:20 +02:00
|
|
|
"encoding/json"
|
2016-04-07 14:49:34 +02:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2020-12-20 19:00:59 +01:00
|
|
|
"github.com/ansible-semaphore/semaphore/db"
|
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
|
|
|
|
2018-06-08 03:58:08 +02:00
|
|
|
log "github.com/Sirupsen/logrus"
|
2016-04-07 14:49:34 +02:00
|
|
|
"github.com/ansible-semaphore/semaphore/util"
|
|
|
|
)
|
|
|
|
|
2018-03-27 22:12:47 +02:00
|
|
|
const (
|
|
|
|
taskFailStatus = "error"
|
2018-06-07 09:29:55 +02:00
|
|
|
taskTypeID = "task"
|
2018-03-27 22:12:47 +02:00
|
|
|
)
|
|
|
|
|
2016-04-07 14:49:34 +02:00
|
|
|
type task struct {
|
2020-12-20 19:00:59 +01:00
|
|
|
store db.Store
|
|
|
|
task db.Task
|
|
|
|
template db.Template
|
|
|
|
sshKey db.AccessKey
|
|
|
|
inventory db.Inventory
|
|
|
|
repository db.Repository
|
|
|
|
environment db.Environment
|
2016-04-17 12:41:36 +02:00
|
|
|
users []int
|
2016-04-17 20:01:51 +02:00
|
|
|
projectID int
|
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 []string
|
2018-03-27 22:12:47 +02:00
|
|
|
alertChat string
|
|
|
|
alert bool
|
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
|
|
|
prepared bool
|
2016-04-07 14:49:34 +02:00
|
|
|
}
|
|
|
|
|
2016-04-17 02:20:23 +02:00
|
|
|
func (t *task) fail() {
|
2018-03-27 22:12:47 +02:00
|
|
|
t.task.Status = taskFailStatus
|
2016-05-17 21:12:54 +02:00
|
|
|
t.updateStatus()
|
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
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2018-03-27 22:12:47 +02:00
|
|
|
objType := taskTypeID
|
2017-03-21 03:40:00 +01:00
|
|
|
desc := "Task ID " + strconv.Itoa(t.task.ID) + " (" + t.template.Alias + ")" + " finished - " + strings.ToUpper(t.task.Status)
|
2020-12-01 20:06:49 +01:00
|
|
|
|
2020-12-20 19:00:59 +01:00
|
|
|
_, err := t.store.CreateEvent(db.Event{
|
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 {
|
2018-03-27 22:12:47 +02:00
|
|
|
t.panicOnError(err, "Fatal error inserting an event")
|
2016-04-17 20:01:51 +02:00
|
|
|
}
|
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{
|
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
|
|
|
|
}
|
|
|
|
|
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()
|
|
|
|
|
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 + ")" + " finished - " + strings.ToUpper(t.task.Status)
|
2020-12-01 20:06:49 +01:00
|
|
|
|
2020-12-20 19:00:59 +01:00
|
|
|
_, err := t.store.CreateEvent(db.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
|
|
|
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)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
{
|
|
|
|
now := time.Now()
|
|
|
|
t.task.Status = "running"
|
|
|
|
t.task.Start = &now
|
|
|
|
|
|
|
|
t.updateStatus()
|
|
|
|
}
|
|
|
|
|
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{
|
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")
|
|
|
|
|
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
|
|
|
|
|
|
|
t.task.Status = "success"
|
2016-05-17 21:12:54 +02:00
|
|
|
t.updateStatus()
|
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 {
|
|
|
|
if err == sql.ErrNoRows {
|
|
|
|
t.log(errMsg)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
t.fail()
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-04-07 14:49:34 +02:00
|
|
|
func (t *task) fetch(errMsg string, ptr interface{}, query string, args ...interface{}) error {
|
2020-12-04 23:22:05 +01:00
|
|
|
err := t.store.Sql().SelectOne(ptr, query, args...)
|
2016-04-07 14:49:34 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
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 access key
|
2020-12-20 19:00:59 +01:00
|
|
|
t.sshKey, err = t.store.GetAccessKey(t.template.ProjectID, t.template.SSHKeyID)
|
|
|
|
if err != nil {
|
|
|
|
return t.prepareError(err, "Template AccessKey not found!")
|
2016-04-07 14:49:34 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if t.sshKey.Type != "ssh" {
|
|
|
|
t.log("Non ssh-type keys are currently not supported: " + t.sshKey.Type)
|
2020-12-20 19:00:59 +01:00
|
|
|
return errors.New("unsupported SSH Key")
|
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
|
|
|
}
|
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
// get inventory services key
|
2020-12-20 19:00:59 +01:00
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
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
|
2018-03-27 22:12:47 +02:00
|
|
|
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 {
|
2016-04-08 21:41:20 +02:00
|
|
|
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
|
2018-03-27 22:12:47 +02:00
|
|
|
if err := t.fetch("Repository Access Key not found!", &t.repository.SSHKey, "select * from access_key where id=?", t.repository.SSHKeyID); err != nil {
|
2016-04-07 14:49:34 +02:00
|
|
|
return err
|
|
|
|
}
|
2018-03-27 22:12:47 +02:00
|
|
|
if t.repository.SSHKey.Type != "ssh" {
|
|
|
|
t.log("Repository Access Key is not 'SSH': " + t.repository.SSHKey.Type)
|
2020-12-20 19:00:59 +01:00
|
|
|
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 {
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-12-20 19:00:59 +01:00
|
|
|
func (t *task) installKey(key db.AccessKey) error {
|
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()
|
2017-02-23 12:21:18 +01:00
|
|
|
if key.Key != nil {
|
2017-04-18 16:21:20 +02:00
|
|
|
if err := ioutil.WriteFile(path+"-cert.pub", []byte(*key.Key), 0600); err != nil {
|
|
|
|
return err
|
2017-02-23 12:21:18 +01:00
|
|
|
}
|
|
|
|
}
|
2017-04-18 16:21:20 +02:00
|
|
|
|
|
|
|
return ioutil.WriteFile(path, []byte(*key.Secret), 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 {
|
|
|
|
repoName := "repository_" + strconv.Itoa(t.repository.ID)
|
|
|
|
_, err := os.Stat(util.Config.TmpPath + "/" + repoName)
|
|
|
|
|
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
|
|
|
|
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, util.Config.TmpPath, &gitSSHCommand)
|
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-04-15 18:07:49 +02:00
|
|
|
requirementsFilePath := fmt.Sprintf("%s/repository_%d/roles/requirements.yml", util.Config.TmpPath, t.repository.ID)
|
|
|
|
if _, err := os.Stat(requirementsFilePath); err != nil {
|
|
|
|
t.log("No requirements.yml found in roles directory\n")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-04-15 18:06:22 +02:00
|
|
|
if err := t.runGalaxy([]string{
|
|
|
|
"install",
|
|
|
|
"-r",
|
|
|
|
"roles/requirements.yml",
|
|
|
|
"-p",
|
|
|
|
"./roles/",
|
|
|
|
"--force",
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := t.runGalaxy([]string{
|
|
|
|
"collection",
|
|
|
|
"install",
|
|
|
|
"-r",
|
|
|
|
"roles/requirements.yml",
|
|
|
|
"--force",
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
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
|
2016-06-30 17:34:35 +02:00
|
|
|
cmd.Dir = util.Config.TmpPath + "/repository_" + strconv.Itoa(t.repository.ID)
|
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
|
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.Dir = util.Config.TmpPath + "/repository_" + strconv.Itoa(t.repository.ID)
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
func (t *task) runPlaybook() 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 {
|
|
|
|
return err
|
|
|
|
}
|
2018-06-07 09:29:55 +02:00
|
|
|
cmd := exec.Command("ansible-playbook", args...) //nolint: gas
|
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.Dir = util.Config.TmpPath + "/repository_" + strconv.Itoa(t.repository.ID)
|
|
|
|
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("")
|
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 cmd.Run()
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2018-03-27 22:12:47 +02:00
|
|
|
if t.inventory.SSHKeyID != nil {
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
2016-04-08 21:41:20 +02:00
|
|
|
if len(t.environment.JSON) > 0 {
|
2017-03-18 10:57:41 +01:00
|
|
|
var js map[string]interface{}
|
2017-04-18 15:58:48 +02:00
|
|
|
err := json.Unmarshal([]byte(t.environment.JSON), &js)
|
2017-03-18 10:57:41 +01:00
|
|
|
if err != nil {
|
|
|
|
t.log("JSON is not valid")
|
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
|
2017-03-18 10:57:41 +01:00
|
|
|
}
|
|
|
|
|
2018-02-14 15:54:04 +01:00
|
|
|
extraVar, err := removeCommandEnvironment(t.environment.JSON, js)
|
|
|
|
if err != nil {
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
|
|
|
args = append(args, "--extra-vars", extraVar)
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// removeCommandEnvironment removes the ENV key from task environments and returns the resultant json encoded string
|
|
|
|
// which can be passed as the --extra-vars flag values
|
2018-03-15 00:52:37 +01:00
|
|
|
func removeCommandEnvironment(envJSON string, envJs map[string]interface{}) (string, error) {
|
2018-02-14 15:54:04 +01:00
|
|
|
if _, ok := envJs["ENV"]; ok {
|
|
|
|
delete(envJs, "ENV")
|
|
|
|
ev, err := json.Marshal(envJs)
|
|
|
|
if err != nil {
|
2018-03-15 00:52:37 +01:00
|
|
|
return envJSON, err
|
2018-02-14 15:54:04 +01:00
|
|
|
}
|
2018-03-15 00:52:37 +01:00
|
|
|
envJSON = string(ev)
|
2018-02-14 15:54:04 +01:00
|
|
|
}
|
|
|
|
|
2018-03-15 00:52:37 +01:00
|
|
|
return envJSON, nil
|
2018-02-14 15:54:04 +01:00
|
|
|
|
|
|
|
}
|
2018-03-15 00:49:40 +01:00
|
|
|
|
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
|
|
|
|
}
|