Semaphore/services/tasks/LocalJob.go
2024-01-30 20:42:56 +05:00

457 lines
10 KiB
Go

package tasks
import (
"encoding/json"
"fmt"
"github.com/ansible-semaphore/semaphore/lib"
"os"
"strconv"
"github.com/ansible-semaphore/semaphore/db"
"github.com/ansible-semaphore/semaphore/db_lib"
"github.com/ansible-semaphore/semaphore/util"
)
type LocalJob struct {
// Received constant fields
Task db.Task
Template db.Template
Inventory db.Inventory
Repository db.Repository
Environment db.Environment
Logger lib.Logger
App db_lib.LocalApp
// Internal field
Process *os.Process
sshKeyInstallation db.AccessKeyInstallation
becomeKeyInstallation db.AccessKeyInstallation
vaultFileInstallation db.AccessKeyInstallation
}
func (t *LocalJob) Kill() {
if t.Process == nil {
return
}
err := t.Process.Kill()
if err != nil {
t.Log(err.Error())
}
}
func (t *LocalJob) Log(msg string) {
t.Logger.Log(msg)
}
func (t *LocalJob) SetStatus(status lib.TaskStatus) {
t.Logger.SetStatus(status)
}
func (t *LocalJob) getEnvironmentExtraVars(username string, incomingVersion *string) (extraVars map[string]interface{}, err error) {
extraVars = make(map[string]interface{})
if t.Environment.JSON != "" {
err = json.Unmarshal([]byte(t.Environment.JSON), &extraVars)
if err != nil {
return
}
}
taskDetails := make(map[string]interface{})
taskDetails["id"] = t.Task.ID
if t.Task.Message != "" {
taskDetails["message"] = t.Task.Message
}
taskDetails["username"] = username
if t.Template.Type != db.TemplateTask {
taskDetails["type"] = t.Template.Type
if incomingVersion != nil {
taskDetails["incoming_version"] = incomingVersion
}
if t.Template.Type == db.TemplateBuild {
taskDetails["target_version"] = t.Task.Version
}
}
vars := make(map[string]interface{})
vars["task_details"] = taskDetails
extraVars["semaphore_vars"] = vars
return
}
func (t *LocalJob) getEnvironmentExtraVarsJSON(username string, incomingVersion *string) (str string, err error) {
extraVars := make(map[string]interface{})
if t.Environment.JSON != "" {
err = json.Unmarshal([]byte(t.Environment.JSON), &extraVars)
if err != nil {
return
}
}
taskDetails := make(map[string]interface{})
taskDetails["id"] = t.Task.ID
if t.Task.Message != "" {
taskDetails["message"] = t.Task.Message
}
taskDetails["username"] = username
if t.Template.Type != db.TemplateTask {
taskDetails["type"] = t.Template.Type
if incomingVersion != nil {
taskDetails["incoming_version"] = incomingVersion
}
if t.Template.Type == db.TemplateBuild {
taskDetails["target_version"] = t.Task.Version
}
}
vars := make(map[string]interface{})
vars["task_details"] = taskDetails
extraVars["semaphore_vars"] = vars
ev, err := json.Marshal(extraVars)
if err != nil {
return
}
str = string(ev)
return
}
func (t *LocalJob) getEnvironmentENV() (arr []string, err error) {
environmentVars := make(map[string]string)
if t.Environment.ENV != nil {
err = json.Unmarshal([]byte(*t.Environment.ENV), &environmentVars)
if err != nil {
return
}
}
for key, val := range environmentVars {
arr = append(arr, fmt.Sprintf("%s=%s", key, val))
}
return
}
// nolint: gocyclo
func (t *LocalJob) getTerraformArgs(username string, incomingVersion *string) (args []string, err error) {
args = []string{}
if t.Task.DryRun {
args = append(args, "plan")
} else {
args = append(args, "apply")
}
extraVars, err := t.getEnvironmentExtraVars(username, incomingVersion)
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")
return
}
for v := range extraVars {
args = append(args, "-var", v)
}
return
}
// nolint: gocyclo
func (t *LocalJob) getPlaybookArgs(username string, incomingVersion *string) (args []string, err error) {
playbookName := t.Task.Playbook
if playbookName == "" {
playbookName = t.Template.Playbook
}
var inventory string
switch t.Inventory.Type {
case db.InventoryFile:
inventory = t.Inventory.Inventory
case db.InventoryStatic, db.InventoryStaticYaml:
inventory = util.Config.TmpPath + "/inventory_" + strconv.Itoa(t.Task.ID)
if t.Inventory.Type == db.InventoryStaticYaml {
inventory += ".yml"
}
default:
err = fmt.Errorf("invalid invetory type")
return
}
args = []string{
"-i", inventory,
}
if t.Inventory.SSHKeyID != nil {
switch t.Inventory.SSHKey.Type {
case db.AccessKeySSH:
//args = append(args, "--extra-vars={\"ansible_ssh_private_key_file\": \""+t.inventory.SSHKey.GetPath()+"\"}")
if t.Inventory.SSHKey.SshKey.Login != "" {
args = append(args, "--extra-vars={\"ansible_user\": \""+t.Inventory.SSHKey.SshKey.Login+"\"}")
}
case db.AccessKeyLoginPassword:
args = append(args, "--extra-vars=@"+t.sshKeyInstallation.GetPath())
case db.AccessKeyNone:
default:
err = fmt.Errorf("access key does not suite for inventory's user credentials")
return
}
}
if t.Inventory.BecomeKeyID != nil {
switch t.Inventory.BecomeKey.Type {
case db.AccessKeyLoginPassword:
args = append(args, "--extra-vars=@"+t.becomeKeyInstallation.GetPath())
case db.AccessKeyNone:
default:
err = fmt.Errorf("access key does not suite for inventory's sudo user credentials")
return
}
}
if t.Task.Debug {
args = append(args, "-vvvv")
}
if t.Task.Diff {
args = append(args, "--diff")
}
if t.Task.DryRun {
args = append(args, "--check")
}
if t.Template.VaultKeyID != nil {
args = append(args, "--vault-password-file", t.vaultFileInstallation.GetPath())
}
extraVars, err := t.getEnvironmentExtraVarsJSON(username, incomingVersion)
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)
}
var templateExtraArgs []string
if t.Template.Arguments != nil {
err = json.Unmarshal([]byte(*t.Template.Arguments), &templateExtraArgs)
if err != nil {
t.Log("Invalid format of the template extra arguments, must be valid JSON")
return
}
}
var taskExtraArgs []string
if t.Template.AllowOverrideArgsInTask && t.Task.Arguments != nil {
err = json.Unmarshal([]byte(*t.Task.Arguments), &taskExtraArgs)
if err != nil {
t.Log("Invalid format of the TaskRunner extra arguments, must be valid JSON")
return
}
}
if t.Task.Limit != "" {
t.Log("--limit=" + t.Task.Limit)
taskExtraArgs = append(taskExtraArgs, "--limit="+t.Task.Limit)
}
args = append(args, templateExtraArgs...)
args = append(args, taskExtraArgs...)
args = append(args, playbookName)
return
}
func (t *LocalJob) Run(username string, incomingVersion *string) (err error) {
t.SetStatus(lib.TaskRunningStatus)
err = t.prepareRun()
if err != nil {
return err
}
defer func() {
t.destroyKeys()
}()
var args []string
switch t.Template.App {
case db.TemplateAnsible:
args, err = t.getPlaybookArgs(username, incomingVersion)
default:
panic("unknown template app")
}
if err != nil {
return
}
environmentVariables, err := t.getEnvironmentENV()
if err != nil {
return
}
if t.Inventory.SSHKey.Type == db.AccessKeySSH && t.Inventory.SSHKeyID != nil {
//var sshAgent lib.SshAgent
//sshAgent, err = t.Inventory.StartSshAgent(t.Logger)
//
//if err != nil {
// return
//}
//
//defer sshAgent.Close()
environmentVariables = append(environmentVariables, fmt.Sprintf("SSH_AUTH_SOCK=%s", t.sshKeyInstallation.SshAgent.SocketFile))
}
return t.App.Run(args, &environmentVariables, func(p *os.Process) {
t.Process = p
})
}
func (t *LocalJob) prepareRun() error {
t.Log("Preparing: " + strconv.Itoa(t.Task.ID))
if err := checkTmpDir(util.Config.TmpPath); err != nil {
t.Log("Creating tmp dir failed: " + err.Error())
return err
}
if t.Repository.GetType() == db.RepositoryLocal {
if _, err := os.Stat(t.Repository.GitURL); err != nil {
t.Log("Failed in finding static repository at " + t.Repository.GitURL + ": " + err.Error())
return err
}
} else {
if err := t.updateRepository(); err != nil {
t.Log("Failed updating repository: " + err.Error())
return err
}
if err := t.checkoutRepository(); err != nil {
t.Log("Failed to checkout repository to required commit: " + err.Error())
return err
}
}
if err := t.installInventory(); err != nil {
t.Log("Failed to install inventory: " + err.Error())
return err
}
if err := t.App.InstallRequirements(); err != nil {
t.Log("Running galaxy failed: " + err.Error())
return err
}
if err := t.installVaultKeyFile(); err != nil {
t.Log("Failed to install vault password file: " + err.Error())
return err
}
return nil
}
func (t *LocalJob) updateRepository() error {
repo := db_lib.GitRepository{
Logger: t.Logger,
TemplateID: t.Template.ID,
Repository: t.Repository,
Client: db_lib.CreateDefaultGitClient(),
}
err := repo.ValidateRepo()
if err != nil {
if !os.IsNotExist(err) {
err = os.RemoveAll(repo.GetFullPath())
if err != nil {
return err
}
}
return repo.Clone()
}
if repo.CanBePulled() {
err = repo.Pull()
if err == nil {
return nil
}
}
err = os.RemoveAll(repo.GetFullPath())
if err != nil {
return err
}
return repo.Clone()
}
func (t *LocalJob) checkoutRepository() error {
repo := db_lib.GitRepository{
Logger: t.Logger,
TemplateID: t.Template.ID,
Repository: t.Repository,
Client: db_lib.CreateDefaultGitClient(),
}
err := repo.ValidateRepo()
if err != nil {
return err
}
if t.Task.CommitHash != nil {
// checkout to commit if it is provided for TaskRunner
return repo.Checkout(*t.Task.CommitHash)
}
// store commit to TaskRunner table
//commitHash, err := repo.GetLastCommitHash()
//
//if err != nil {
// return err
//}
//
//commitMessage, _ := repo.GetLastCommitMessage()
//
//t.task.CommitHash = &commitHash
//t.task.CommitMessage = commitMessage
//
//return t.pool.store.UpdateTask(t.task)
return nil
}
func (t *LocalJob) installVaultKeyFile() (err error) {
if t.Template.VaultKeyID == nil {
return nil
}
t.vaultFileInstallation, err = t.Template.VaultKey.Install(db.AccessKeyRoleAnsiblePasswordVault, t.Logger)
return
}