2021-02-10 11:34:04 +01:00
|
|
|
package plugin
|
|
|
|
|
|
|
|
import (
|
2023-09-21 13:01:35 +02:00
|
|
|
"errors"
|
2021-02-10 11:34:04 +01:00
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
2024-05-05 13:03:29 +02:00
|
|
|
"github.com/rs/zerolog/log"
|
2021-02-10 11:34:04 +01:00
|
|
|
"github.com/urfave/cli/v2"
|
2023-02-13 15:26:20 +01:00
|
|
|
"golang.org/x/sys/execabs"
|
2021-02-10 11:34:04 +01:00
|
|
|
)
|
|
|
|
|
2023-02-13 15:26:20 +01:00
|
|
|
const (
|
|
|
|
AnsibleForksDefault = 5
|
|
|
|
|
2022-04-25 15:54:18 +02:00
|
|
|
ansibleFolder = "/etc/ansible"
|
|
|
|
ansibleConfig = "/etc/ansible/ansible.cfg"
|
2023-02-13 15:26:20 +01:00
|
|
|
|
2024-05-04 14:35:41 +02:00
|
|
|
pipBin = "/usr/local/bin/pip"
|
|
|
|
ansibleBin = "/usr/local/bin/ansible"
|
|
|
|
ansibleGalaxyBin = "/usr/local/bin/ansible-galaxy"
|
|
|
|
ansiblePlaybookBin = "/usr/local/bin/ansible-playbook"
|
2023-02-13 15:26:20 +01:00
|
|
|
|
|
|
|
strictFilePerm = 0o600
|
2022-04-25 15:54:18 +02:00
|
|
|
)
|
2021-02-10 11:34:04 +01:00
|
|
|
|
2023-02-13 15:26:20 +01:00
|
|
|
const ansibleContent = `
|
2021-02-10 11:34:04 +01:00
|
|
|
[defaults]
|
|
|
|
host_key_checking = False
|
|
|
|
`
|
|
|
|
|
2024-05-05 13:03:29 +02:00
|
|
|
var ErrAnsiblePlaybookNotFound = errors.New("no playbook found")
|
2023-09-21 13:01:35 +02:00
|
|
|
|
2024-05-05 13:03:29 +02:00
|
|
|
// ansibleConfig creates the Ansible configuration directory and file.
|
|
|
|
// It ensures the directory exists and writes the Ansible configuration
|
|
|
|
// content to the config file with strict file permissions.
|
2021-02-10 11:34:04 +01:00
|
|
|
func (p *Plugin) ansibleConfig() error {
|
|
|
|
if err := os.MkdirAll(ansibleFolder, os.ModePerm); err != nil {
|
2023-09-21 13:01:35 +02:00
|
|
|
return fmt.Errorf("failed to create ansible directory: %w", err)
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-02-13 15:26:20 +01:00
|
|
|
if err := os.WriteFile(ansibleConfig, []byte(ansibleContent), strictFilePerm); err != nil {
|
2023-09-21 13:01:35 +02:00
|
|
|
return fmt.Errorf("failed to create ansible config: %w", err)
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-05-05 13:03:29 +02:00
|
|
|
// privateKey creates a temporary file containing the private key specified in the plugin settings,
|
|
|
|
// and sets the PrivateKeyFile field in the plugin settings to the name of the temporary file.
|
|
|
|
// This is used to pass the private key to the Ansible command.
|
2021-02-10 11:34:04 +01:00
|
|
|
func (p *Plugin) privateKey() error {
|
2022-08-22 11:05:35 +02:00
|
|
|
tmpfile, err := os.CreateTemp("", "privateKey")
|
2021-02-10 11:34:04 +01:00
|
|
|
if err != nil {
|
2023-09-21 13:01:35 +02:00
|
|
|
return fmt.Errorf("failed to create private key file: %w", err)
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if _, err := tmpfile.Write([]byte(p.Settings.PrivateKey)); err != nil {
|
2023-09-21 13:01:35 +02:00
|
|
|
return fmt.Errorf("failed to write private key file: %w", err)
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := tmpfile.Close(); err != nil {
|
2023-09-21 13:01:35 +02:00
|
|
|
return fmt.Errorf("failed to close private key file: %w", err)
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
p.Settings.PrivateKeyFile = tmpfile.Name()
|
2023-02-13 15:26:20 +01:00
|
|
|
|
2021-02-10 11:34:04 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-05-05 13:03:29 +02:00
|
|
|
// vaultPass creates a temporary file containing the vault password and sets the VaultPasswordFile
|
|
|
|
// field in the Plugin's Settings. This allows the vault password to be used when running
|
|
|
|
// Ansible commands that require it.
|
2021-02-10 11:34:04 +01:00
|
|
|
func (p *Plugin) vaultPass() error {
|
2022-08-22 11:05:35 +02:00
|
|
|
tmpfile, err := os.CreateTemp("", "vaultPass")
|
2021-02-10 11:34:04 +01:00
|
|
|
if err != nil {
|
2023-09-21 13:01:35 +02:00
|
|
|
return fmt.Errorf("failed to create vault password file: %w", err)
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if _, err := tmpfile.Write([]byte(p.Settings.VaultPassword)); err != nil {
|
2023-09-21 13:01:35 +02:00
|
|
|
return fmt.Errorf("failed to write vault password file: %w", err)
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if err := tmpfile.Close(); err != nil {
|
2023-09-21 13:01:35 +02:00
|
|
|
return fmt.Errorf("failed to close vault password file: %w", err)
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
p.Settings.VaultPasswordFile = tmpfile.Name()
|
2023-02-13 15:26:20 +01:00
|
|
|
|
2021-02-10 11:34:04 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-05-05 13:03:29 +02:00
|
|
|
// getPlaybooks retrieves a list of playbook files based on the configured playbook patterns.
|
|
|
|
// If any of the patterns fail to match any files, the original pattern is included in the list.
|
|
|
|
// If no playbooks are found, ErrAnsiblePlaybookNotFound is returned.
|
|
|
|
func (p *Plugin) getPlaybooks() error {
|
2022-04-25 15:54:18 +02:00
|
|
|
var playbooks []string
|
2021-02-10 11:34:04 +01:00
|
|
|
|
2024-05-05 13:03:29 +02:00
|
|
|
for _, pb := range p.Settings.Playbooks.Value() {
|
|
|
|
files, err := filepath.Glob(pb)
|
2021-02-10 11:34:04 +01:00
|
|
|
if err != nil {
|
2024-05-05 13:03:29 +02:00
|
|
|
playbooks = append(playbooks, pb)
|
2023-02-13 15:26:20 +01:00
|
|
|
|
2021-02-10 11:34:04 +01:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
playbooks = append(playbooks, files...)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(playbooks) == 0 {
|
2024-05-05 13:03:29 +02:00
|
|
|
log.Debug().Strs("patterns", p.Settings.Playbooks.Value()).Msg("no playbooks found")
|
|
|
|
|
2023-09-21 13:01:35 +02:00
|
|
|
return ErrAnsiblePlaybookNotFound
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
p.Settings.Playbooks = *cli.NewStringSlice(playbooks...)
|
2023-02-13 15:26:20 +01:00
|
|
|
|
2021-02-10 11:34:04 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-05-05 13:03:29 +02:00
|
|
|
func (p *Plugin) versionCommand() *Cmd {
|
2021-02-10 11:34:04 +01:00
|
|
|
args := []string{
|
|
|
|
"--version",
|
|
|
|
}
|
|
|
|
|
2024-05-05 13:03:29 +02:00
|
|
|
return &Cmd{
|
|
|
|
Cmd: execabs.Command(ansibleBin, args...),
|
|
|
|
}
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2024-05-05 13:03:29 +02:00
|
|
|
// pythonRequirementsCommand returns an execabs.Cmd that runs the pip install
|
|
|
|
// command with the specified Python requirements file and upgrades any existing
|
|
|
|
// packages.
|
|
|
|
func (p *Plugin) pythonRequirementsCommand() *Cmd {
|
2021-02-10 11:34:04 +01:00
|
|
|
args := []string{
|
|
|
|
"install",
|
|
|
|
"--upgrade",
|
|
|
|
"--requirement",
|
2023-12-22 10:10:39 +01:00
|
|
|
p.Settings.PythonRequirements,
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2024-05-05 13:03:29 +02:00
|
|
|
return &Cmd{
|
|
|
|
Cmd: execabs.Command(pipBin, args...),
|
|
|
|
}
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2024-05-05 13:03:29 +02:00
|
|
|
// galaxyRequirementsCommand returns an execabs.Cmd that runs the Ansible Galaxy
|
|
|
|
// install command with the specified role file and verbosity level.
|
|
|
|
func (p *Plugin) galaxyRequirementsCommand() *Cmd {
|
2021-02-10 11:34:04 +01:00
|
|
|
args := []string{
|
|
|
|
"install",
|
|
|
|
"--force",
|
|
|
|
"--role-file",
|
2023-12-22 10:10:39 +01:00
|
|
|
p.Settings.GalaxyRequirements,
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.Verbose > 0 {
|
|
|
|
args = append(args, fmt.Sprintf("-%s", strings.Repeat("v", p.Settings.Verbose)))
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2024-05-05 13:03:29 +02:00
|
|
|
return &Cmd{
|
|
|
|
Cmd: execabs.Command(ansibleGalaxyBin, args...),
|
|
|
|
}
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2024-05-05 13:03:29 +02:00
|
|
|
// ansibleCommand returns an execabs.Cmd that runs the Ansible playbook with the
|
|
|
|
// specified inventory file and various configuration options set on the Plugin struct.
|
|
|
|
func (p *Plugin) ansibleCommand(inventory string) *Cmd {
|
2021-02-10 11:34:04 +01:00
|
|
|
args := []string{
|
|
|
|
"--inventory",
|
|
|
|
inventory,
|
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if len(p.Settings.ModulePath.Value()) > 0 {
|
|
|
|
args = append(args, "--module-path", strings.Join(p.Settings.ModulePath.Value(), ":"))
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.VaultID != "" {
|
|
|
|
args = append(args, "--vault-id", p.Settings.VaultID)
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.VaultPasswordFile != "" {
|
|
|
|
args = append(args, "--vault-password-file", p.Settings.VaultPasswordFile)
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
for _, v := range p.Settings.ExtraVars.Value() {
|
2021-02-10 11:34:04 +01:00
|
|
|
args = append(args, "--extra-vars", v)
|
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.ListHosts {
|
2021-02-10 11:34:04 +01:00
|
|
|
args = append(args, "--list-hosts")
|
2023-12-19 09:19:23 +01:00
|
|
|
args = append(args, p.Settings.Playbooks.Value()...)
|
2021-02-10 11:34:04 +01:00
|
|
|
|
2024-05-05 13:03:29 +02:00
|
|
|
return &Cmd{
|
|
|
|
Cmd: execabs.Command(ansiblePlaybookBin, args...),
|
|
|
|
}
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.SyntaxCheck {
|
2021-02-10 11:34:04 +01:00
|
|
|
args = append(args, "--syntax-check")
|
2023-12-19 09:19:23 +01:00
|
|
|
args = append(args, p.Settings.Playbooks.Value()...)
|
2021-02-10 11:34:04 +01:00
|
|
|
|
2024-05-05 13:03:29 +02:00
|
|
|
return &Cmd{
|
|
|
|
Cmd: execabs.Command(ansiblePlaybookBin, args...),
|
|
|
|
}
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.Check {
|
2021-02-10 11:34:04 +01:00
|
|
|
args = append(args, "--check")
|
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.Diff {
|
2021-02-10 11:34:04 +01:00
|
|
|
args = append(args, "--diff")
|
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.FlushCache {
|
2021-02-10 11:34:04 +01:00
|
|
|
args = append(args, "--flush-cache")
|
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.ForceHandlers {
|
2021-02-10 11:34:04 +01:00
|
|
|
args = append(args, "--force-handlers")
|
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.Forks != AnsibleForksDefault {
|
|
|
|
args = append(args, "--forks", strconv.Itoa(p.Settings.Forks))
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.Limit != "" {
|
|
|
|
args = append(args, "--limit", p.Settings.Limit)
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.ListTags {
|
2021-02-10 11:34:04 +01:00
|
|
|
args = append(args, "--list-tags")
|
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.ListTasks {
|
2021-02-10 11:34:04 +01:00
|
|
|
args = append(args, "--list-tasks")
|
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.SkipTags != "" {
|
|
|
|
args = append(args, "--skip-tags", p.Settings.SkipTags)
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.StartAtTask != "" {
|
|
|
|
args = append(args, "--start-at-task", p.Settings.StartAtTask)
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.Tags != "" {
|
|
|
|
args = append(args, "--tags", p.Settings.Tags)
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.PrivateKeyFile != "" {
|
|
|
|
args = append(args, "--private-key", p.Settings.PrivateKeyFile)
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.User != "" {
|
|
|
|
args = append(args, "--user", p.Settings.User)
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.Connection != "" {
|
|
|
|
args = append(args, "--connection", p.Settings.Connection)
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.Timeout != 0 {
|
|
|
|
args = append(args, "--timeout", strconv.Itoa(p.Settings.Timeout))
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.SSHCommonArgs != "" {
|
|
|
|
args = append(args, "--ssh-common-args", p.Settings.SSHCommonArgs)
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.SFTPExtraArgs != "" {
|
|
|
|
args = append(args, "--sftp-extra-args", p.Settings.SFTPExtraArgs)
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.SCPExtraArgs != "" {
|
|
|
|
args = append(args, "--scp-extra-args", p.Settings.SCPExtraArgs)
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.SSHExtraArgs != "" {
|
|
|
|
args = append(args, "--ssh-extra-args", p.Settings.SSHExtraArgs)
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.Become {
|
2021-02-10 11:34:04 +01:00
|
|
|
args = append(args, "--become")
|
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.BecomeMethod != "" {
|
|
|
|
args = append(args, "--become-method", p.Settings.BecomeMethod)
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.BecomeUser != "" {
|
|
|
|
args = append(args, "--become-user", p.Settings.BecomeUser)
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
if p.Settings.Verbose > 0 {
|
|
|
|
args = append(args, fmt.Sprintf("-%s", strings.Repeat("v", p.Settings.Verbose)))
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|
|
|
|
|
2023-12-19 09:19:23 +01:00
|
|
|
args = append(args, p.Settings.Playbooks.Value()...)
|
2021-02-10 11:34:04 +01:00
|
|
|
|
2024-05-05 13:03:29 +02:00
|
|
|
return &Cmd{
|
|
|
|
Cmd: execabs.Command(ansiblePlaybookBin, args...),
|
|
|
|
Private: false,
|
|
|
|
}
|
2021-02-10 11:34:04 +01:00
|
|
|
}
|