Skip to content

Commit 7025b07

Browse files
authored
Merge pull request #14 from swaroopar/feature/integrateGoAnsible
add first integration of ansible client
2 parents 053eb5b + 5acfef3 commit 7025b07

10 files changed

+268
-5
lines changed

.github/workflows/ci.yml

+3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ jobs:
2121
uses: actions/setup-go@v5
2222
with:
2323
go-version: ">=1.22"
24+
- uses: actions/setup-python@v5
25+
with:
26+
python-version: '3.10'
2427
- run: go version
2528
- name: Format code
2629
run: |

go.mod

+6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module xpanse-agent
33
go 1.22.5
44

55
require (
6+
github.com/apenella/go-ansible/v2 v2.0.1
67
github.com/go-co-op/gocron/v2 v2.11.0
78
github.com/go-git/go-git/v5 v5.12.0
89
github.com/spf13/cobra v1.8.1
@@ -14,6 +15,8 @@ require (
1415
dario.cat/mergo v1.0.0 // indirect
1516
github.com/Microsoft/go-winio v0.6.1 // indirect
1617
github.com/ProtonMail/go-crypto v1.0.0 // indirect
18+
github.com/apenella/go-common-utils/data v0.0.0-20220913191136-86daaa87e7df // indirect
19+
github.com/apenella/go-common-utils/error v0.0.0-20220913191136-86daaa87e7df // indirect
1720
github.com/cloudflare/circl v1.3.7 // indirect
1821
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
1922
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
@@ -32,6 +35,7 @@ require (
3235
github.com/mitchellh/mapstructure v1.5.0 // indirect
3336
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
3437
github.com/pjbgf/sha1cd v0.3.0 // indirect
38+
github.com/pkg/errors v0.9.1 // indirect
3539
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
3640
github.com/robfig/cron/v3 v3.0.1 // indirect
3741
github.com/sagikazarmark/locafero v0.4.0 // indirect
@@ -42,6 +46,7 @@ require (
4246
github.com/spf13/afero v1.11.0 // indirect
4347
github.com/spf13/cast v1.6.0 // indirect
4448
github.com/spf13/pflag v1.0.5 // indirect
49+
github.com/stretchr/objx v0.5.2 // indirect
4550
github.com/subosito/gotenv v1.6.0 // indirect
4651
github.com/xanzy/ssh-agent v0.3.3 // indirect
4752
go.uber.org/atomic v1.9.0 // indirect
@@ -56,5 +61,6 @@ require (
5661
golang.org/x/tools v0.22.0 // indirect
5762
gopkg.in/ini.v1 v1.67.0 // indirect
5863
gopkg.in/warnings.v0 v0.1.2 // indirect
64+
gopkg.in/yaml.v2 v2.4.0 // indirect
5965
gopkg.in/yaml.v3 v3.0.1 // indirect
6066
)

go.sum

+14-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0k
77
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
88
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
99
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
10+
github.com/apenella/go-ansible/v2 v2.0.1 h1:9o3805u4NAIMf1px5EKpRS2LEa2aeQbMtHB7vilt7XE=
11+
github.com/apenella/go-ansible/v2 v2.0.1/go.mod h1:ifhiX4d0bpynb8yhdzLTmGl/38HqTYr/26PfjB1enXQ=
12+
github.com/apenella/go-common-utils/data v0.0.0-20220913191136-86daaa87e7df h1:sEikY2P+NZK/7VZUwIsnXIGElhsuFDSxh1bZYwHxdcI=
13+
github.com/apenella/go-common-utils/data v0.0.0-20220913191136-86daaa87e7df/go.mod h1:cLVL6GjUiKG/WyBzX+KD6h/XRV/HnNZIZbMNNiBgQ9o=
14+
github.com/apenella/go-common-utils/error v0.0.0-20220913191136-86daaa87e7df h1:SvlYbjlsSQDS7hbVT1h012/zdgvcwWJ+Yd9XRiiY/8s=
15+
github.com/apenella/go-common-utils/error v0.0.0-20220913191136-86daaa87e7df/go.mod h1:+3dyIlHX350xJIUIffwMLswZXU+N2FwDE05VuKqxYdw=
1016
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
1117
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
1218
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
@@ -28,10 +34,12 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk
2834
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
2935
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
3036
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
31-
github.com/go-co-op/gocron/v2 v2.11.0 h1:IOowNA6SzwdRFnD4/Ol3Kj6G2xKfsoiiGq2Jhhm9bvE=
32-
github.com/go-co-op/gocron/v2 v2.11.0/go.mod h1:xY7bJxGazKam1cz04EebrlP4S9q4iWdiAylMGP3jY9w=
3337
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
3438
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
39+
github.com/go-co-op/gocron/v2 v2.11.0 h1:IOowNA6SzwdRFnD4/Ol3Kj6G2xKfsoiiGq2Jhhm9bvE=
40+
github.com/go-co-op/gocron/v2 v2.11.0/go.mod h1:xY7bJxGazKam1cz04EebrlP4S9q4iWdiAylMGP3jY9w=
41+
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
42+
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
3543
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
3644
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
3745
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
@@ -92,6 +100,8 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG
92100
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
93101
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
94102
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
103+
github.com/sosedoff/ansible-vault-go v0.2.0 h1:XqkBdqbXgTuFQ++NdrZvSdUTNozeb6S3V5x7FVs17vg=
104+
github.com/sosedoff/ansible-vault-go v0.2.0/go.mod h1:wMU54HNJfY0n0KIgbpA9m15NBfaUDlJrAsaZp0FwzkI=
95105
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
96106
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
97107
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
@@ -107,6 +117,7 @@ github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+
107117
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
108118
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
109119
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
120+
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
110121
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
111122
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
112123
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@@ -201,6 +212,7 @@ gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
201212
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
202213
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
203214
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
215+
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
204216
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
205217
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
206218
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

pkg/ansible/manage_virtual_env.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* SPDX-FileCopyrightText: Huawei Inc.
4+
*/
5+
6+
package ansible
7+
8+
import (
9+
"fmt"
10+
"os"
11+
"os/exec"
12+
"path/filepath"
13+
"xpanse-agent/pkg/config"
14+
"xpanse-agent/pkg/logger"
15+
)
16+
17+
func CreateVirtualEnv(virtualEnvDir string, pythonVersion float32, moduleRequirementsFile string) error {
18+
var cmd *exec.Cmd
19+
var err error
20+
// Check if the virtualenv already exists
21+
if _, err = os.Stat(virtualEnvDir); !os.IsNotExist(err) {
22+
logger.Logger.Info(fmt.Sprintf("Virtual environment '%s' already exists. Installing required modules on the same.", virtualEnvDir))
23+
} else {
24+
cmd = exec.Command(fmt.Sprintf("python%.2f", pythonVersion), "-m", "venv", filepath.Dir(virtualEnvDir))
25+
err = cmd.Run()
26+
}
27+
if err != nil {
28+
logger.Logger.Error(fmt.Sprintf("Error creating virtual environment: %s", err))
29+
return err
30+
}
31+
err = installRequirements(virtualEnvDir, moduleRequirementsFile)
32+
logger.Logger.Info(fmt.Sprintf("Virtual environment '%s' prepared successfully.", virtualEnvDir))
33+
return err
34+
}
35+
36+
func installRequirements(virtualEnvDir string, requirementsFile string) error {
37+
logger.Logger.Info(fmt.Sprintf("Installing required modules in %s", virtualEnvDir))
38+
var err error
39+
originalWorkingDir, err := os.Getwd()
40+
if err != nil {
41+
logger.Logger.Error(fmt.Sprintf("Error getting current working directory: %s", err))
42+
return err
43+
}
44+
err = os.Chdir(config.LoadedConfig.RepoCheckoutLocation)
45+
logger.Logger.Info(fmt.Sprintf("running pip install from %s", config.LoadedConfig.RepoCheckoutLocation))
46+
if err != nil {
47+
return err
48+
}
49+
if _, err = os.Stat(requirementsFile); os.IsNotExist(err) {
50+
logger.Logger.Error(fmt.Sprintf("Requirements file '%s' does not exist.", requirementsFile))
51+
return err
52+
}
53+
54+
// Prepare the pip install command
55+
cmd := exec.Command(fmt.Sprintf("%s/bin/pip", virtualEnvDir), "install", "-r", requirementsFile)
56+
57+
// Run the command and capture output
58+
output, err := cmd.CombinedOutput()
59+
if err != nil {
60+
logger.Logger.Error(fmt.Sprintf("Error installing packages: %s", err))
61+
return err
62+
}
63+
64+
logger.Logger.Info("Required modules installed successfully")
65+
logger.Logger.Info(string(output))
66+
67+
// return back to original directory.
68+
err = os.Chdir(originalWorkingDir)
69+
return err
70+
}

pkg/ansible/run_playbook.go

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* SPDX-FileCopyrightText: Huawei Inc.
4+
*/
5+
6+
package ansible
7+
8+
import (
9+
"bytes"
10+
"context"
11+
"fmt"
12+
"github.com/apenella/go-ansible/v2/pkg/execute"
13+
results "github.com/apenella/go-ansible/v2/pkg/execute/result/json"
14+
"github.com/apenella/go-ansible/v2/pkg/execute/stdoutcallback"
15+
"github.com/apenella/go-ansible/v2/pkg/playbook"
16+
"io"
17+
"xpanse-agent/pkg/config"
18+
"xpanse-agent/pkg/logger"
19+
)
20+
21+
func RunPlaybook(playbookName string,
22+
extraVars map[string]interface{},
23+
inventory string,
24+
virtualEnvRootDir string,
25+
pythonVersion float32,
26+
manageVirtualEnv bool,
27+
requirementsFileNameInRepo string) error {
28+
var res *results.AnsiblePlaybookJSONResults
29+
var err error
30+
buff := new(bytes.Buffer)
31+
buffError := new(bytes.Buffer)
32+
33+
if manageVirtualEnv {
34+
logger.Logger.Info("preparing virtual environment in " + virtualEnvRootDir)
35+
err = CreateVirtualEnv(virtualEnvRootDir, pythonVersion, requirementsFileNameInRepo)
36+
if err != nil {
37+
return err
38+
}
39+
}
40+
41+
logger.Logger.Info("Running ansible task using ansible installed in venv " + virtualEnvRootDir)
42+
43+
ansiblePlaybookOptions := &playbook.AnsiblePlaybookOptions{
44+
Become: true,
45+
Inventory: inventory,
46+
ExtraVars: extraVars,
47+
}
48+
49+
// constructs the ansible command to be executed.
50+
playbookCmd := playbook.NewAnsiblePlaybookCmd(
51+
playbook.WithPlaybooks(playbookName),
52+
playbook.WithPlaybookOptions(ansiblePlaybookOptions),
53+
playbook.WithBinary(fmt.Sprintf("%s/bin/ansible-playbook", virtualEnvRootDir)),
54+
)
55+
56+
// execute the ansible command constructed above.
57+
exec := stdoutcallback.NewJSONStdoutCallbackExecute(
58+
execute.NewDefaultExecute(
59+
execute.WithCmd(playbookCmd),
60+
execute.WithCmdRunDir(config.LoadedConfig.RepoCheckoutLocation),
61+
execute.WithErrorEnrich(playbook.NewAnsiblePlaybookErrorEnrich()),
62+
execute.WithWrite(io.Writer(buff)),
63+
execute.WithWriteError(io.Writer(buffError)),
64+
),
65+
)
66+
err = exec.Execute(context.TODO())
67+
if err != nil {
68+
logger.Logger.Error(err.Error())
69+
return err
70+
}
71+
72+
// all warnings from Ansible are written to stderr stream.
73+
if buffError.Len() > 0 {
74+
for _, line := range bytes.Split(buffError.Bytes(), []byte("\n")) {
75+
if len(line) > 0 {
76+
logger.Logger.Warn(string(line))
77+
}
78+
}
79+
}
80+
81+
res, err = results.ParseJSONResultsStream(io.Reader(buff))
82+
if err != nil {
83+
logger.Logger.Error(err.Error())
84+
return err
85+
}
86+
87+
parseAndLogAnsibleOutputForResults(res)
88+
89+
return err
90+
}
91+
92+
func parseAndLogAnsibleOutputForResults(ansibleOutput *results.AnsiblePlaybookJSONResults) {
93+
for _, play := range ansibleOutput.Plays {
94+
for _, task := range play.Tasks {
95+
name := task.Task.Name
96+
for host, result := range task.Hosts {
97+
logger.Logger.Info("|" + host + "|" + play.Play.Name + "|" + name + "|" + fmt.Sprintf("%v", result.StdoutLines) + "|" + fmt.Sprintf("%t", result.Changed) + "|" + fmt.Sprintf("%t", result.Failed))
98+
}
99+
}
100+
}
101+
}

pkg/ansible/run_playbook_test.go

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* SPDX-FileCopyrightText: Huawei Inc.
4+
*/
5+
6+
package ansible
7+
8+
import (
9+
"github.com/stretchr/testify/assert"
10+
"testing"
11+
"xpanse-agent/pkg/config"
12+
)
13+
14+
func TestRunPlaybook(t *testing.T) {
15+
err := config.LoadConfig("test_data/test-xpanse-agent-config.yml")
16+
assert.Nil(t, err)
17+
runError := RunPlaybook(
18+
"kafka-container-manage.yml", nil, "", "/tmp/kafka-test/", 3.10, true, "requirements.txt")
19+
assert.Nil(t, runError)
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
---
2+
- name: Manage containers
3+
hosts: localhost
4+
connection: local
5+
become: true
6+
7+
tasks:
8+
- name: Install packages from requirements.txt
9+
pip:
10+
requirements: requirements.txt
11+
state: present
12+
chdir: "/opt/repos/forked/osc/samples/ansible"
13+
14+
- name: Ensure Docker service is running
15+
systemd:
16+
name: docker
17+
state: started
18+
enabled: yes
19+
20+
- name: Pull latest container image
21+
docker_image:
22+
name: "bitnami/kafka:3.3.2"
23+
source: pull
24+
force_source: yes
25+
26+
- name: Run container
27+
docker_container:
28+
name: "kafka-server"
29+
image: "bitnami/kafka:3.3.2"
30+
env:
31+
KAFKA_BROKER_ID: "1"
32+
KAFKA_ADVERTISED_LISTENERS: "PLAINTEXT://192.168.10.103:9092"
33+
KAFKA_LISTENERS: "PLAINTEXT://0.0.0.0:9092"
34+
ALLOW_PLAINTEXT_LISTENER: "yes"
35+
KAFKA_CFG_ZOOKEEPER_CONNECT: "192.168.10.103:2181"
36+
state: started
37+
restart_policy: always
38+
network_mode: host
39+
ports:
40+
- "9092:9092"
41+
- "9093:9093"
42+
43+
- name: Remove unused images
44+
docker_prune:
45+
images: yes
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
requests==2.25.1
2+
ansible==10.3.0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
---
2+
polling_frequency: 40
3+
repo_checkout_location: test_data

pkg/git/git_clone.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ func CloneProject(projectUrl string, serviceId string, branch string) error {
2121
}
2222
var repository *git.Repository
2323
var err error
24+
var w *git.Worktree
2425

2526
repository, err = git.PlainClone(fmt.Sprintf("/tmp/%s", serviceId), false, gitConfig)
2627

@@ -47,7 +48,7 @@ func CloneProject(projectUrl string, serviceId string, branch string) error {
4748
}
4849

4950
// load default main/master worktree
50-
w, workTreeError := repository.Worktree()
51+
w, err = repository.Worktree()
5152

5253
checkoutOptions := &git.CheckoutOptions{Branch: plumbing.ReferenceName(fmt.Sprintf("refs/heads/%s", branch)), Force: true}
5354
if w != nil {
@@ -56,8 +57,8 @@ func CloneProject(projectUrl string, serviceId string, branch string) error {
5657
return checkoutErr
5758
}
5859
}
59-
if workTreeError != nil {
60-
return workTreeError
60+
if err != nil {
61+
return err
6162
}
6263
}
6364

0 commit comments

Comments
 (0)