mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-04-29 01:46:53 +00:00
Compare commits
432 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7468bf30f2 | ||
|
|
b4e7b7cb50 | ||
|
|
666db05eda | ||
|
|
e255e5ed0c | ||
|
|
f79aa6f63f | ||
|
|
da040cb412 | ||
|
|
04b68c296b | ||
|
|
8e2fa624e0 | ||
|
|
c7ac7fbefd | ||
|
|
ed6e87c994 | ||
|
|
cc49336090 | ||
|
|
4b410c5007 | ||
|
|
13be47c7a6 | ||
|
|
94903785d0 | ||
|
|
20e394fd3d | ||
|
|
17157cdfb5 | ||
|
|
769b22cd4c | ||
|
|
dbd19a5583 | ||
|
|
45f7661249 | ||
|
|
ed472d8291 | ||
|
|
c72d8d4b56 | ||
|
|
e9b58cfc09 | ||
|
|
98d25a3e4d | ||
|
|
7a000108af | ||
|
|
a8a1c0af2a | ||
|
|
c394fbe8e9 | ||
|
|
fed5965518 | ||
|
|
1e0a2a72f7 | ||
|
|
add595e121 | ||
|
|
4476a4bb1c | ||
|
|
f104fe3a58 | ||
|
|
1bc052ae6b | ||
|
|
6260d5f873 | ||
|
|
6907ae5a5e | ||
|
|
2314db59c7 | ||
|
|
e32515889b | ||
|
|
7542b5429b | ||
|
|
18afa4e8b0 | ||
|
|
b23d011582 | ||
|
|
8b56b6dfea | ||
|
|
c2a90c215f | ||
|
|
39a66a3196 | ||
|
|
75f649648e | ||
|
|
1f29fa2e39 | ||
|
|
26b8f30afa | ||
|
|
80f43bbbf5 | ||
|
|
ec58aadaa7 | ||
|
|
6f50af8a6e | ||
|
|
e2604e7533 | ||
|
|
8208e52c42 | ||
|
|
39e4f89ff0 | ||
|
|
c7202a1902 | ||
|
|
1f64be7cac | ||
|
|
2447bc90a4 | ||
|
|
98029089d8 | ||
|
|
964aa945a4 | ||
|
|
7d24ba8a28 | ||
|
|
be5b4adf5b | ||
|
|
62b45e235d | ||
|
|
f99508b307 | ||
|
|
9afc096cd2 | ||
|
|
27c0c29cf3 | ||
|
|
c890a161ae | ||
|
|
c911aa8a84 | ||
|
|
a3cb344689 | ||
|
|
bd4112e87b | ||
|
|
ec9b1fc503 | ||
|
|
5467334117 | ||
|
|
e962b9237d | ||
|
|
48c36bc72b | ||
|
|
56bb7d7b9d | ||
|
|
af2438f664 | ||
|
|
a68de9bfb6 | ||
|
|
4614047132 | ||
|
|
6b02eaa795 | ||
|
|
6f774fd4a5 | ||
|
|
f05618a6f2 | ||
|
|
98a956a9d6 | ||
|
|
60d51f7b49 | ||
|
|
245be47a4e | ||
|
|
6d346ddadd | ||
|
|
cb969fc468 | ||
|
|
ec6c2a76ad | ||
|
|
b22777de44 | ||
|
|
151f6c9ce3 | ||
|
|
0acaad60c8 | ||
|
|
a06c1f5c9a | ||
|
|
becda864c4 | ||
|
|
92d077e816 | ||
|
|
700623863c | ||
|
|
31f57b9385 | ||
|
|
14038511a1 | ||
|
|
3db0a11148 | ||
|
|
05ba79c5fe | ||
|
|
0df708b15a | ||
|
|
1829ad4fdc | ||
|
|
0e99b006a2 | ||
|
|
f66bd1035d | ||
|
|
7732d64abb | ||
|
|
03f3b74934 | ||
|
|
29e9afcbf4 | ||
|
|
11ba71c802 | ||
|
|
d3badc6d43 | ||
|
|
92a07f1794 | ||
|
|
7a44dbfe45 | ||
|
|
4f9e7bd793 | ||
|
|
872bc91096 | ||
|
|
641b0693b4 | ||
|
|
45a3396ab0 | ||
|
|
e38f9e5cfc | ||
|
|
60ba39da58 | ||
|
|
f6fa7fb273 | ||
|
|
899fcb8749 | ||
|
|
a5c448d6e8 | ||
|
|
74bd7f1471 | ||
|
|
7e6514b4d4 | ||
|
|
3c7f05c42d | ||
|
|
a13a6d284c | ||
|
|
122086e83b | ||
|
|
92e4bd184d | ||
|
|
12f2d71950 | ||
|
|
e3f72bca4f | ||
|
|
429b4b14a8 | ||
|
|
2d12c6678c | ||
|
|
474c7a7240 | ||
|
|
434032080e | ||
|
|
eec5c82a55 | ||
|
|
97ea891377 | ||
|
|
b3ad22f33f | ||
|
|
977f53f823 | ||
|
|
4c26dc0760 | ||
|
|
3c6131b451 | ||
|
|
2499c1132d | ||
|
|
3fe559b88f | ||
|
|
28fed38757 | ||
|
|
f2196d452f | ||
|
|
07124473cc | ||
|
|
a56cec8582 | ||
|
|
5c2e9e0c5b | ||
|
|
df5818282b | ||
|
|
69aea38683 | ||
|
|
01dce04e33 | ||
|
|
5ab43f0ff1 | ||
|
|
910fb933a7 | ||
|
|
7537674d4b | ||
|
|
6e42e442bc | ||
|
|
161539729d | ||
|
|
8f76c847fe | ||
|
|
3f29683191 | ||
|
|
a89990ab9b | ||
|
|
7bc937b5e8 | ||
|
|
a1c39cc882 | ||
|
|
859039c47a | ||
|
|
b3f669a574 | ||
|
|
770bae70db | ||
|
|
0a6c57bc4d | ||
|
|
459b9f3f9a | ||
|
|
d9436069f1 | ||
|
|
fcf1cb7fbc | ||
|
|
5727f1afd4 | ||
|
|
fbada0026e | ||
|
|
c4373d5ed5 | ||
|
|
54291ab1d1 | ||
|
|
bbd67b5017 | ||
|
|
6d6d3e8039 | ||
|
|
e31999f369 | ||
|
|
09f99e66fe | ||
|
|
5491ff7c6a | ||
|
|
163f3ee305 | ||
|
|
4152770281 | ||
|
|
ecc9afab0b | ||
|
|
6f9b954048 | ||
|
|
74cd18b682 | ||
|
|
0b64fc1ee4 | ||
|
|
5fe39082d5 | ||
|
|
0ff52abfdd | ||
|
|
653ae1b48a | ||
|
|
16d124bbe2 | ||
|
|
4455df380e | ||
|
|
06c7ba640e | ||
|
|
a6f6bcc555 | ||
|
|
cd06325f6b | ||
|
|
a20ee0e816 | ||
|
|
acb6a2f76d | ||
|
|
5a1c68cb62 | ||
|
|
776374ee78 | ||
|
|
c916052124 | ||
|
|
88b1fbbdf0 | ||
|
|
48db44f199 | ||
|
|
eb4c01260f | ||
|
|
10561e6f30 | ||
|
|
5c26387a54 | ||
|
|
8226ea87cf | ||
|
|
411c7d4f32 | ||
|
|
a2f377c621 | ||
|
|
ac0956ed6f | ||
|
|
0d02265a23 | ||
|
|
f38d974d42 | ||
|
|
a975574618 | ||
|
|
b05fa358e6 | ||
|
|
c44118ac3c | ||
|
|
886d4a6596 | ||
|
|
94e3635c0a | ||
|
|
3d03c373ff | ||
|
|
8fc11fe88f | ||
|
|
cecaa1840d | ||
|
|
2429e228a4 | ||
|
|
c8410a924e | ||
|
|
8a2ac4f1eb | ||
|
|
9553dd9ddf | ||
|
|
e63c2f54cf | ||
|
|
c71f662d55 | ||
|
|
9069f673e2 | ||
|
|
67d1b6c413 | ||
|
|
3506f73da1 | ||
|
|
d0b4e91cac | ||
|
|
ce1b9887b1 | ||
|
|
107df41d9c | ||
|
|
39f3b151e8 | ||
|
|
ccf7f62325 | ||
|
|
a8c41ac4c1 | ||
|
|
5b3b7a1fb1 | ||
|
|
9fb686fe35 | ||
|
|
93be499f26 | ||
|
|
cc72fa0786 | ||
|
|
658637dc70 | ||
|
|
b1f4604067 | ||
|
|
064f76c27b | ||
|
|
86166ccade | ||
|
|
1180843e35 | ||
|
|
26fe42776c | ||
|
|
5874711c6e | ||
|
|
12fa2452d8 | ||
|
|
a894f8e7eb | ||
|
|
410288401b | ||
|
|
67ddb567c9 | ||
|
|
3de4682193 | ||
|
|
8df9d0d7de | ||
|
|
ec6496024f | ||
|
|
447d4b0267 | ||
|
|
b523d1b1c9 | ||
|
|
464812a2c2 | ||
|
|
5e6b8e5327 | ||
|
|
24b74cc4b9 | ||
|
|
c814fd0530 | ||
|
|
1bdf8fc025 | ||
|
|
3b109abe18 | ||
|
|
cc8009621f | ||
|
|
c7e2875a4d | ||
|
|
24f2b980b7 | ||
|
|
1d86d49688 | ||
|
|
5b4f41748d | ||
|
|
29a2df8e6b | ||
|
|
8610223d03 | ||
|
|
fea0ffa5aa | ||
|
|
e7ccbc2f18 | ||
|
|
92df5e8fec | ||
|
|
5d9a7ab240 | ||
|
|
d4fb6bf8a6 | ||
|
|
7fc7af306c | ||
|
|
c4e2b73193 | ||
|
|
96dfb89b01 | ||
|
|
daaa008713 | ||
|
|
2d660a1252 | ||
|
|
83080cc005 | ||
|
|
7c913b239a | ||
|
|
a7d1b0fc52 | ||
|
|
ab84f1632f | ||
|
|
8ef77d8664 | ||
|
|
fe18b05f08 | ||
|
|
84e0190eee | ||
|
|
bc6ae849b3 | ||
|
|
002f137134 | ||
|
|
0bc5f24863 | ||
|
|
4700accbff | ||
|
|
d356e255e0 | ||
|
|
89ad40db41 | ||
|
|
199ba0a170 | ||
|
|
293021c3dd | ||
|
|
a32f1d699b | ||
|
|
deaad6e547 | ||
|
|
0bf84ba2b6 | ||
|
|
38479ee9ff | ||
|
|
ac302eb77d | ||
|
|
27cb0c9090 | ||
|
|
6af74d1ba6 | ||
|
|
80f48cceb4 | ||
|
|
f93883aa20 | ||
|
|
4123934b46 | ||
|
|
2f1df973a6 | ||
|
|
e4472b322b | ||
|
|
76ebda7faf | ||
|
|
94472dd7e5 | ||
|
|
37dd6ec8a3 | ||
|
|
41d87f5c9d | ||
|
|
40f1ab31f5 | ||
|
|
2ae41fa83f | ||
|
|
529af4984c | ||
|
|
d73f977b7a | ||
|
|
afd754e384 | ||
|
|
ea389e7045 | ||
|
|
d9b0c42f5f | ||
|
|
982b8d89b7 | ||
|
|
26df6c7657 | ||
|
|
43f8adf1a5 | ||
|
|
7e978c77b4 | ||
|
|
ecc048bc12 | ||
|
|
593d302f0b | ||
|
|
249126f429 | ||
|
|
96d5e6e50e | ||
|
|
e9071e9871 | ||
|
|
9c9c4cbc3e | ||
|
|
573a7b97c6 | ||
|
|
4598758419 | ||
|
|
7dc4429c9c | ||
|
|
5192ffe5b3 | ||
|
|
3607e3d012 | ||
|
|
e3a3c6d58f | ||
|
|
e5bc38d856 | ||
|
|
b79ac4f0ac | ||
|
|
c84fb5577b | ||
|
|
14e86bde07 | ||
|
|
9571ec7c72 | ||
|
|
e1b5ddb050 | ||
|
|
34519a5ecb | ||
|
|
4cb4c608d0 | ||
|
|
2eec853e9e | ||
|
|
2942eda8e0 | ||
|
|
73b54139d6 | ||
|
|
0f59bb7a99 | ||
|
|
76d0222a83 | ||
|
|
158947f5e5 | ||
|
|
8989b6c4d4 | ||
|
|
57e28e5a73 | ||
|
|
e7480ad29e | ||
|
|
b9244130ef | ||
|
|
9f340861ad | ||
|
|
b16263ebd7 | ||
|
|
5322dd942e | ||
|
|
9a16eaf9ba | ||
|
|
5b2711bbd3 | ||
|
|
132faeae34 | ||
|
|
c517f1c483 | ||
|
|
fd811df414 | ||
|
|
b6c6253bfc | ||
|
|
7bbf32dc0e | ||
|
|
2963004991 | ||
|
|
229ed6dad9 | ||
|
|
37c8560542 | ||
|
|
1a8f172186 | ||
|
|
52126b8fae | ||
|
|
58f9860ba7 | ||
|
|
e3fb817a21 | ||
|
|
e1148e6bdc | ||
|
|
c0fd10e793 | ||
|
|
cac55beb4f | ||
|
|
ce65eb8736 | ||
|
|
f9a56b9a9b | ||
|
|
daed4dcc94 | ||
|
|
a24ee93f23 | ||
|
|
3a24aa5b6d | ||
|
|
a78f7b1e6a | ||
|
|
83318c36aa | ||
|
|
9dd2b71d04 | ||
|
|
6cefde622c | ||
|
|
21b16c1c77 | ||
|
|
9f3103e891 | ||
|
|
8990f97b45 | ||
|
|
ca8ecb1df1 | ||
|
|
e794fa74da | ||
|
|
8451fc36ca | ||
|
|
b0797d329c | ||
|
|
feb1ecbfcd | ||
|
|
45972c23d4 | ||
|
|
a3989095af | ||
|
|
b2c773996d | ||
|
|
caecb2297f | ||
|
|
5259caacae | ||
|
|
01d8c7b769 | ||
|
|
6e0142fe3a | ||
|
|
70c8042c99 | ||
|
|
1053545870 | ||
|
|
9e38161400 | ||
|
|
0d50131d5e | ||
|
|
60ba7cab93 | ||
|
|
ecb68aa5d2 | ||
|
|
86f19cb5d3 | ||
|
|
9a18963364 | ||
|
|
2612ceee37 | ||
|
|
69b72e4a8e | ||
|
|
3314d5c8db | ||
|
|
b11da288d2 | ||
|
|
df7fe19bbe | ||
|
|
fd2cd5f28c | ||
|
|
3716187fc3 | ||
|
|
ec4cf55566 | ||
|
|
03966624ba | ||
|
|
d95f4d68a3 | ||
|
|
1d61541951 | ||
|
|
2574cb0dea | ||
|
|
71f9674835 | ||
|
|
49e2a8633e | ||
|
|
f0940d82dc | ||
|
|
8f60f3aef9 | ||
|
|
800bc01112 | ||
|
|
ac3c04357c | ||
|
|
c31499a411 | ||
|
|
1ae6c82558 | ||
|
|
d2d7deb4ec | ||
|
|
06f13e79b1 | ||
|
|
1c4ab7fafc | ||
|
|
a0ad2d5849 | ||
|
|
2a3819a696 | ||
|
|
0129346eda | ||
|
|
6f8f12f762 | ||
|
|
5041ebe5b2 | ||
|
|
961767e2dd | ||
|
|
d46e12e280 | ||
|
|
5a5188a453 | ||
|
|
58ce19d2c2 | ||
|
|
54df0c9b3a | ||
|
|
7d72300c36 | ||
|
|
e690317e3a | ||
|
|
572caeaa39 | ||
|
|
43cb5a0d54 | ||
|
|
d96b2642bc | ||
|
|
e7ee90a937 | ||
|
|
ec886203fc | ||
|
|
da2c87ce0d | ||
|
|
4792e21416 | ||
|
|
95e509753e | ||
|
|
22ed5048a2 |
@@ -3,8 +3,7 @@ Copyright (c) Ansible Project
|
||||
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
# Running keycloak_authentication module integration test
|
||||
|
||||
Run integration tests:
|
||||
## Azure Pipelines Configuration
|
||||
|
||||
ansible-test integration -v keycloak_authentication --allow-unsupported --docker fedora35 --docker-network host
|
||||
Please see the [Documentation](https://github.com/ansible/community/wiki/Testing:-Azure-Pipelines) for more information.
|
||||
436
.azure-pipelines/azure-pipelines.yml
Normal file
436
.azure-pipelines/azure-pipelines.yml
Normal file
@@ -0,0 +1,436 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
trigger:
|
||||
batch: true
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
- stable-*
|
||||
|
||||
pr:
|
||||
autoCancel: true
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
- stable-*
|
||||
|
||||
schedules:
|
||||
- cron: 0 8 * * *
|
||||
displayName: Nightly (main)
|
||||
always: true
|
||||
branches:
|
||||
include:
|
||||
- main
|
||||
- cron: 0 10 * * *
|
||||
displayName: Nightly (active stable branches)
|
||||
always: true
|
||||
branches:
|
||||
include:
|
||||
- stable-10
|
||||
- stable-9
|
||||
- cron: 0 11 * * 0
|
||||
displayName: Weekly (old stable branches)
|
||||
always: true
|
||||
branches:
|
||||
include:
|
||||
- stable-8
|
||||
|
||||
variables:
|
||||
- name: checkoutPath
|
||||
value: ansible_collections/community/general
|
||||
- name: coverageBranches
|
||||
value: main
|
||||
- name: pipelinesCoverage
|
||||
value: coverage
|
||||
- name: entryPoint
|
||||
value: tests/utils/shippable/shippable.sh
|
||||
- name: fetchDepth
|
||||
value: 0
|
||||
|
||||
resources:
|
||||
containers:
|
||||
- container: default
|
||||
image: quay.io/ansible/azure-pipelines-test-container:6.0.0
|
||||
|
||||
pool: Standard
|
||||
|
||||
stages:
|
||||
### Sanity
|
||||
- stage: Sanity_devel
|
||||
displayName: Sanity devel
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Test {0}
|
||||
testFormat: devel/sanity/{0}
|
||||
targets:
|
||||
- test: 1
|
||||
- test: 2
|
||||
- test: 3
|
||||
- test: 4
|
||||
- test: extra
|
||||
- stage: Sanity_2_18
|
||||
displayName: Sanity 2.18
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Test {0}
|
||||
testFormat: 2.18/sanity/{0}
|
||||
targets:
|
||||
- test: 1
|
||||
- test: 2
|
||||
- test: 3
|
||||
- test: 4
|
||||
- stage: Sanity_2_17
|
||||
displayName: Sanity 2.17
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Test {0}
|
||||
testFormat: 2.17/sanity/{0}
|
||||
targets:
|
||||
- test: 1
|
||||
- test: 2
|
||||
- test: 3
|
||||
- test: 4
|
||||
- stage: Sanity_2_16
|
||||
displayName: Sanity 2.16
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Test {0}
|
||||
testFormat: 2.16/sanity/{0}
|
||||
targets:
|
||||
- test: 1
|
||||
- test: 2
|
||||
- test: 3
|
||||
- test: 4
|
||||
### Units
|
||||
- stage: Units_devel
|
||||
displayName: Units devel
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: devel/units/{0}/1
|
||||
targets:
|
||||
- test: 3.8
|
||||
- test: 3.9
|
||||
- test: '3.10'
|
||||
- test: '3.11'
|
||||
- test: '3.12'
|
||||
- test: '3.13'
|
||||
- stage: Units_2_18
|
||||
displayName: Units 2.18
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: 2.18/units/{0}/1
|
||||
targets:
|
||||
- test: 3.8
|
||||
- test: "3.13"
|
||||
- stage: Units_2_17
|
||||
displayName: Units 2.17
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: 2.17/units/{0}/1
|
||||
targets:
|
||||
- test: 3.7
|
||||
- test: "3.12"
|
||||
- stage: Units_2_16
|
||||
displayName: Units 2.16
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
nameFormat: Python {0}
|
||||
testFormat: 2.16/units/{0}/1
|
||||
targets:
|
||||
- test: 2.7
|
||||
- test: 3.6
|
||||
- test: "3.11"
|
||||
|
||||
## Remote
|
||||
- stage: Remote_devel_extra_vms
|
||||
displayName: Remote devel extra VMs
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: devel/{0}
|
||||
targets:
|
||||
- name: Alpine 3.21
|
||||
test: alpine/3.21
|
||||
# - name: Fedora 41
|
||||
# test: fedora/41
|
||||
- name: Ubuntu 22.04
|
||||
test: ubuntu/22.04
|
||||
- name: Ubuntu 24.04
|
||||
test: ubuntu/24.04
|
||||
groups:
|
||||
- vm
|
||||
- stage: Remote_devel
|
||||
displayName: Remote devel
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: devel/{0}
|
||||
targets:
|
||||
- name: macOS 14.3
|
||||
test: macos/14.3
|
||||
- name: RHEL 9.5
|
||||
test: rhel/9.5
|
||||
- name: FreeBSD 14.2
|
||||
test: freebsd/14.2
|
||||
- name: FreeBSD 13.4
|
||||
test: freebsd/13.4
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- stage: Remote_2_18
|
||||
displayName: Remote 2.18
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.18/{0}
|
||||
targets:
|
||||
- name: RHEL 9.4
|
||||
test: rhel/9.4
|
||||
- name: FreeBSD 14.1
|
||||
test: freebsd/14.1
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- stage: Remote_2_17
|
||||
displayName: Remote 2.17
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.17/{0}
|
||||
targets:
|
||||
- name: FreeBSD 13.3
|
||||
test: freebsd/13.3
|
||||
- name: RHEL 9.3
|
||||
test: rhel/9.3
|
||||
- name: FreeBSD 14.0
|
||||
test: freebsd/14.0
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- stage: Remote_2_16
|
||||
displayName: Remote 2.16
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.16/{0}
|
||||
targets:
|
||||
- name: macOS 13.2
|
||||
test: macos/13.2
|
||||
- name: RHEL 9.2
|
||||
test: rhel/9.2
|
||||
- name: RHEL 8.8
|
||||
test: rhel/8.8
|
||||
- name: RHEL 7.9
|
||||
test: rhel/7.9
|
||||
# - name: FreeBSD 13.2
|
||||
# test: freebsd/13.2
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
|
||||
### Docker
|
||||
- stage: Docker_devel
|
||||
displayName: Docker devel
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: devel/linux/{0}
|
||||
targets:
|
||||
- name: Fedora 41
|
||||
test: fedora41
|
||||
- name: Alpine 3.21
|
||||
test: alpine321
|
||||
- name: Ubuntu 22.04
|
||||
test: ubuntu2204
|
||||
- name: Ubuntu 24.04
|
||||
test: ubuntu2404
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- stage: Docker_2_18
|
||||
displayName: Docker 2.18
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.18/linux/{0}
|
||||
targets:
|
||||
- name: Fedora 40
|
||||
test: fedora40
|
||||
- name: Alpine 3.20
|
||||
test: alpine320
|
||||
- name: Ubuntu 24.04
|
||||
test: ubuntu2404
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- stage: Docker_2_17
|
||||
displayName: Docker 2.17
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.17/linux/{0}
|
||||
targets:
|
||||
- name: Fedora 39
|
||||
test: fedora39
|
||||
- name: Alpine 3.19
|
||||
test: alpine319
|
||||
- name: Ubuntu 20.04
|
||||
test: ubuntu2004
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- stage: Docker_2_16
|
||||
displayName: Docker 2.16
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: 2.16/linux/{0}
|
||||
targets:
|
||||
- name: Fedora 38
|
||||
test: fedora38
|
||||
- name: openSUSE 15
|
||||
test: opensuse15
|
||||
- name: Alpine 3
|
||||
test: alpine3
|
||||
- name: CentOS 7
|
||||
test: centos7
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
|
||||
### Community Docker
|
||||
- stage: Docker_community_devel
|
||||
displayName: Docker (community images) devel
|
||||
dependsOn: []
|
||||
jobs:
|
||||
- template: templates/matrix.yml
|
||||
parameters:
|
||||
testFormat: devel/linux-community/{0}
|
||||
targets:
|
||||
- name: Debian Bullseye
|
||||
test: debian-bullseye/3.9
|
||||
- name: Debian Bookworm
|
||||
test: debian-bookworm/3.11
|
||||
- name: ArchLinux
|
||||
test: archlinux/3.13
|
||||
groups:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
|
||||
### Generic
|
||||
# Right now all generic tests are disabled. Uncomment when at least one of them is re-enabled.
|
||||
# - stage: Generic_devel
|
||||
# displayName: Generic devel
|
||||
# dependsOn: []
|
||||
# jobs:
|
||||
# - template: templates/matrix.yml
|
||||
# parameters:
|
||||
# nameFormat: Python {0}
|
||||
# testFormat: devel/generic/{0}/1
|
||||
# targets:
|
||||
# - test: '3.8'
|
||||
# - test: '3.11'
|
||||
# - test: '3.13'
|
||||
# - stage: Generic_2_18
|
||||
# displayName: Generic 2.18
|
||||
# dependsOn: []
|
||||
# jobs:
|
||||
# - template: templates/matrix.yml
|
||||
# parameters:
|
||||
# nameFormat: Python {0}
|
||||
# testFormat: 2.18/generic/{0}/1
|
||||
# targets:
|
||||
# - test: '3.8'
|
||||
# - test: '3.13'
|
||||
# - stage: Generic_2_17
|
||||
# displayName: Generic 2.17
|
||||
# dependsOn: []
|
||||
# jobs:
|
||||
# - template: templates/matrix.yml
|
||||
# parameters:
|
||||
# nameFormat: Python {0}
|
||||
# testFormat: 2.17/generic/{0}/1
|
||||
# targets:
|
||||
# - test: '3.7'
|
||||
# - test: '3.12'
|
||||
# - stage: Generic_2_16
|
||||
# displayName: Generic 2.16
|
||||
# dependsOn: []
|
||||
# jobs:
|
||||
# - template: templates/matrix.yml
|
||||
# parameters:
|
||||
# nameFormat: Python {0}
|
||||
# testFormat: 2.16/generic/{0}/1
|
||||
# targets:
|
||||
# - test: '2.7'
|
||||
# - test: '3.6'
|
||||
# - test: '3.11'
|
||||
|
||||
- stage: Summary
|
||||
condition: succeededOrFailed()
|
||||
dependsOn:
|
||||
- Sanity_devel
|
||||
- Sanity_2_18
|
||||
- Sanity_2_17
|
||||
- Sanity_2_16
|
||||
- Units_devel
|
||||
- Units_2_18
|
||||
- Units_2_17
|
||||
- Units_2_16
|
||||
- Remote_devel_extra_vms
|
||||
- Remote_devel
|
||||
- Remote_2_18
|
||||
- Remote_2_17
|
||||
- Remote_2_16
|
||||
- Docker_devel
|
||||
- Docker_2_18
|
||||
- Docker_2_17
|
||||
- Docker_2_16
|
||||
- Docker_community_devel
|
||||
# Right now all generic tests are disabled. Uncomment when at least one of them is re-enabled.
|
||||
# - Generic_devel
|
||||
# - Generic_2_18
|
||||
# - Generic_2_17
|
||||
# - Generic_2_16
|
||||
jobs:
|
||||
- template: templates/coverage.yml
|
||||
24
.azure-pipelines/scripts/aggregate-coverage.sh
Executable file
24
.azure-pipelines/scripts/aggregate-coverage.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Aggregate code coverage results for later processing.
|
||||
|
||||
set -o pipefail -eu
|
||||
|
||||
agent_temp_directory="$1"
|
||||
|
||||
PATH="${PWD}/bin:${PATH}"
|
||||
|
||||
mkdir "${agent_temp_directory}/coverage/"
|
||||
|
||||
options=(--venv --venv-system-site-packages --color -v)
|
||||
|
||||
ansible-test coverage combine --group-by command --export "${agent_temp_directory}/coverage/" "${options[@]}"
|
||||
|
||||
if ansible-test coverage analyze targets generate --help >/dev/null 2>&1; then
|
||||
# Only analyze coverage if the installed version of ansible-test supports it.
|
||||
# Doing so allows this script to work unmodified for multiple Ansible versions.
|
||||
ansible-test coverage analyze targets generate "${agent_temp_directory}/coverage/coverage-analyze-targets.json" "${options[@]}"
|
||||
fi
|
||||
64
.azure-pipelines/scripts/combine-coverage.py
Executable file
64
.azure-pipelines/scripts/combine-coverage.py
Executable file
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
"""
|
||||
Combine coverage data from multiple jobs, keeping the data only from the most recent attempt from each job.
|
||||
Coverage artifacts must be named using the format: "Coverage $(System.JobAttempt) {StableUniqueNameForEachJob}"
|
||||
The recommended coverage artifact name format is: Coverage $(System.JobAttempt) $(System.StageDisplayName) $(System.JobDisplayName)
|
||||
Keep in mind that Azure Pipelines does not enforce unique job display names (only names).
|
||||
It is up to pipeline authors to avoid name collisions when deviating from the recommended format.
|
||||
"""
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Main program entry point."""
|
||||
source_directory = sys.argv[1]
|
||||
|
||||
if '/ansible_collections/' in os.getcwd():
|
||||
output_path = "tests/output"
|
||||
else:
|
||||
output_path = "test/results"
|
||||
|
||||
destination_directory = os.path.join(output_path, 'coverage')
|
||||
|
||||
if not os.path.exists(destination_directory):
|
||||
os.makedirs(destination_directory)
|
||||
|
||||
jobs = {}
|
||||
count = 0
|
||||
|
||||
for name in os.listdir(source_directory):
|
||||
match = re.search('^Coverage (?P<attempt>[0-9]+) (?P<label>.+)$', name)
|
||||
label = match.group('label')
|
||||
attempt = int(match.group('attempt'))
|
||||
jobs[label] = max(attempt, jobs.get(label, 0))
|
||||
|
||||
for label, attempt in jobs.items():
|
||||
name = 'Coverage {attempt} {label}'.format(label=label, attempt=attempt)
|
||||
source = os.path.join(source_directory, name)
|
||||
source_files = os.listdir(source)
|
||||
|
||||
for source_file in source_files:
|
||||
source_path = os.path.join(source, source_file)
|
||||
destination_path = os.path.join(destination_directory, source_file + '.' + label)
|
||||
print('"%s" -> "%s"' % (source_path, destination_path))
|
||||
shutil.copyfile(source_path, destination_path)
|
||||
count += 1
|
||||
|
||||
print('Coverage file count: %d' % count)
|
||||
print('##vso[task.setVariable variable=coverageFileCount]%d' % count)
|
||||
print('##vso[task.setVariable variable=outputPath]%s' % output_path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
28
.azure-pipelines/scripts/process-results.sh
Executable file
28
.azure-pipelines/scripts/process-results.sh
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Check the test results and set variables for use in later steps.
|
||||
|
||||
set -o pipefail -eu
|
||||
|
||||
if [[ "$PWD" =~ /ansible_collections/ ]]; then
|
||||
output_path="tests/output"
|
||||
else
|
||||
output_path="test/results"
|
||||
fi
|
||||
|
||||
echo "##vso[task.setVariable variable=outputPath]${output_path}"
|
||||
|
||||
if compgen -G "${output_path}"'/junit/*.xml' > /dev/null; then
|
||||
echo "##vso[task.setVariable variable=haveTestResults]true"
|
||||
fi
|
||||
|
||||
if compgen -G "${output_path}"'/bot/ansible-test-*' > /dev/null; then
|
||||
echo "##vso[task.setVariable variable=haveBotResults]true"
|
||||
fi
|
||||
|
||||
if compgen -G "${output_path}"'/coverage/*' > /dev/null; then
|
||||
echo "##vso[task.setVariable variable=haveCoverageData]true"
|
||||
fi
|
||||
105
.azure-pipelines/scripts/publish-codecov.py
Executable file
105
.azure-pipelines/scripts/publish-codecov.py
Executable file
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
"""
|
||||
Upload code coverage reports to codecov.io.
|
||||
Multiple coverage files from multiple languages are accepted and aggregated after upload.
|
||||
Python coverage, as well as PowerShell and Python stubs can all be uploaded.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import dataclasses
|
||||
import pathlib
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import typing as t
|
||||
import urllib.request
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class CoverageFile:
|
||||
name: str
|
||||
path: pathlib.Path
|
||||
flags: t.List[str]
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class Args:
|
||||
dry_run: bool
|
||||
path: pathlib.Path
|
||||
|
||||
|
||||
def parse_args() -> Args:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('-n', '--dry-run', action='store_true')
|
||||
parser.add_argument('path', type=pathlib.Path)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Store arguments in a typed dataclass
|
||||
fields = dataclasses.fields(Args)
|
||||
kwargs = {field.name: getattr(args, field.name) for field in fields}
|
||||
|
||||
return Args(**kwargs)
|
||||
|
||||
|
||||
def process_files(directory: pathlib.Path) -> t.Tuple[CoverageFile, ...]:
|
||||
processed = []
|
||||
for file in directory.joinpath('reports').glob('coverage*.xml'):
|
||||
name = file.stem.replace('coverage=', '')
|
||||
|
||||
# Get flags from name
|
||||
flags = name.replace('-powershell', '').split('=') # Drop '-powershell' suffix
|
||||
flags = [flag if not flag.startswith('stub') else flag.split('-')[0] for flag in flags] # Remove "-01" from stub files
|
||||
|
||||
processed.append(CoverageFile(name, file, flags))
|
||||
|
||||
return tuple(processed)
|
||||
|
||||
|
||||
def upload_files(codecov_bin: pathlib.Path, files: t.Tuple[CoverageFile, ...], dry_run: bool = False) -> None:
|
||||
for file in files:
|
||||
cmd = [
|
||||
str(codecov_bin),
|
||||
'--name', file.name,
|
||||
'--file', str(file.path),
|
||||
]
|
||||
for flag in file.flags:
|
||||
cmd.extend(['--flags', flag])
|
||||
|
||||
if dry_run:
|
||||
print(f'DRY-RUN: Would run command: {cmd}')
|
||||
continue
|
||||
|
||||
subprocess.run(cmd, check=True)
|
||||
|
||||
|
||||
def download_file(url: str, dest: pathlib.Path, flags: int, dry_run: bool = False) -> None:
|
||||
if dry_run:
|
||||
print(f'DRY-RUN: Would download {url} to {dest} and set mode to {flags:o}')
|
||||
return
|
||||
|
||||
with urllib.request.urlopen(url) as resp:
|
||||
with dest.open('w+b') as f:
|
||||
# Read data in chunks rather than all at once
|
||||
shutil.copyfileobj(resp, f, 64 * 1024)
|
||||
|
||||
dest.chmod(flags)
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
url = 'https://ansible-ci-files.s3.amazonaws.com/codecov/linux/codecov'
|
||||
with tempfile.TemporaryDirectory(prefix='codecov-') as tmpdir:
|
||||
codecov_bin = pathlib.Path(tmpdir) / 'codecov'
|
||||
download_file(url, codecov_bin, 0o755, args.dry_run)
|
||||
|
||||
files = process_files(args.path)
|
||||
upload_files(codecov_bin, files, args.dry_run)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
19
.azure-pipelines/scripts/report-coverage.sh
Executable file
19
.azure-pipelines/scripts/report-coverage.sh
Executable file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Generate code coverage reports for uploading to Azure Pipelines and codecov.io.
|
||||
|
||||
set -o pipefail -eu
|
||||
|
||||
PATH="${PWD}/bin:${PATH}"
|
||||
|
||||
if ! ansible-test --help >/dev/null 2>&1; then
|
||||
# Install the devel version of ansible-test for generating code coverage reports.
|
||||
# This is only used by Ansible Collections, which are typically tested against multiple Ansible versions (in separate jobs).
|
||||
# Since a version of ansible-test is required that can work the output from multiple older releases, the devel version is used.
|
||||
pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check
|
||||
fi
|
||||
|
||||
ansible-test coverage xml --group-by command --stub --venv --venv-system-site-packages --color -v
|
||||
38
.azure-pipelines/scripts/run-tests.sh
Executable file
38
.azure-pipelines/scripts/run-tests.sh
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Configure the test environment and run the tests.
|
||||
|
||||
set -o pipefail -eu
|
||||
|
||||
entry_point="$1"
|
||||
test="$2"
|
||||
read -r -a coverage_branches <<< "$3" # space separated list of branches to run code coverage on for scheduled builds
|
||||
|
||||
export COMMIT_MESSAGE
|
||||
export COMPLETE
|
||||
export COVERAGE
|
||||
export IS_PULL_REQUEST
|
||||
|
||||
if [ "${SYSTEM_PULLREQUEST_TARGETBRANCH:-}" ]; then
|
||||
IS_PULL_REQUEST=true
|
||||
COMMIT_MESSAGE=$(git log --format=%B -n 1 HEAD^2)
|
||||
else
|
||||
IS_PULL_REQUEST=
|
||||
COMMIT_MESSAGE=$(git log --format=%B -n 1 HEAD)
|
||||
fi
|
||||
|
||||
COMPLETE=
|
||||
COVERAGE=
|
||||
|
||||
if [ "${BUILD_REASON}" = "Schedule" ]; then
|
||||
COMPLETE=yes
|
||||
|
||||
if printf '%s\n' "${coverage_branches[@]}" | grep -q "^${BUILD_SOURCEBRANCHNAME}$"; then
|
||||
COVERAGE=yes
|
||||
fi
|
||||
fi
|
||||
|
||||
"${entry_point}" "${test}" 2>&1 | "$(dirname "$0")/time-command.py"
|
||||
29
.azure-pipelines/scripts/time-command.py
Executable file
29
.azure-pipelines/scripts/time-command.py
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
"""Prepends a relative timestamp to each input line from stdin and writes it to stdout."""
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
def main():
|
||||
"""Main program entry point."""
|
||||
start = time.time()
|
||||
|
||||
sys.stdin.reconfigure(errors='surrogateescape')
|
||||
sys.stdout.reconfigure(errors='surrogateescape')
|
||||
|
||||
for line in sys.stdin:
|
||||
seconds = time.time() - start
|
||||
sys.stdout.write('%02d:%02d %s' % (seconds // 60, seconds % 60, line))
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
44
.azure-pipelines/templates/coverage.yml
Normal file
44
.azure-pipelines/templates/coverage.yml
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# This template adds a job for processing code coverage data.
|
||||
# It will upload results to Azure Pipelines and codecov.io.
|
||||
# Use it from a job stage that completes after all other jobs have completed.
|
||||
# This can be done by placing it in a separate summary stage that runs after the test stage(s) have completed.
|
||||
|
||||
jobs:
|
||||
- job: Coverage
|
||||
displayName: Code Coverage
|
||||
container: default
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- checkout: self
|
||||
fetchDepth: $(fetchDepth)
|
||||
path: $(checkoutPath)
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download Coverage Data
|
||||
inputs:
|
||||
path: coverage/
|
||||
patterns: "Coverage */*=coverage.combined"
|
||||
- bash: .azure-pipelines/scripts/combine-coverage.py coverage/
|
||||
displayName: Combine Coverage Data
|
||||
- bash: .azure-pipelines/scripts/report-coverage.sh
|
||||
displayName: Generate Coverage Report
|
||||
condition: gt(variables.coverageFileCount, 0)
|
||||
- task: PublishCodeCoverageResults@1
|
||||
inputs:
|
||||
codeCoverageTool: Cobertura
|
||||
# Azure Pipelines only accepts a single coverage data file.
|
||||
# That means only Python or PowerShell coverage can be uploaded, but not both.
|
||||
# Set the "pipelinesCoverage" variable to determine which type is uploaded.
|
||||
# Use "coverage" for Python and "coverage-powershell" for PowerShell.
|
||||
summaryFileLocation: "$(outputPath)/reports/$(pipelinesCoverage).xml"
|
||||
displayName: Publish to Azure Pipelines
|
||||
condition: gt(variables.coverageFileCount, 0)
|
||||
- bash: .azure-pipelines/scripts/publish-codecov.py "$(outputPath)"
|
||||
displayName: Publish to codecov.io
|
||||
condition: gt(variables.coverageFileCount, 0)
|
||||
continueOnError: true
|
||||
60
.azure-pipelines/templates/matrix.yml
Normal file
60
.azure-pipelines/templates/matrix.yml
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# This template uses the provided targets and optional groups to generate a matrix which is then passed to the test template.
|
||||
# If this matrix template does not provide the required functionality, consider using the test template directly instead.
|
||||
|
||||
parameters:
|
||||
# A required list of dictionaries, one per test target.
|
||||
# Each item in the list must contain a "test" or "name" key.
|
||||
# Both may be provided. If one is omitted, the other will be used.
|
||||
- name: targets
|
||||
type: object
|
||||
|
||||
# An optional list of values which will be used to multiply the targets list into a matrix.
|
||||
# Values can be strings or numbers.
|
||||
- name: groups
|
||||
type: object
|
||||
default: []
|
||||
|
||||
# An optional format string used to generate the job name.
|
||||
# - {0} is the name of an item in the targets list.
|
||||
- name: nameFormat
|
||||
type: string
|
||||
default: "{0}"
|
||||
|
||||
# An optional format string used to generate the test name.
|
||||
# - {0} is the name of an item in the targets list.
|
||||
- name: testFormat
|
||||
type: string
|
||||
default: "{0}"
|
||||
|
||||
# An optional format string used to add the group to the job name.
|
||||
# {0} is the formatted name of an item in the targets list.
|
||||
# {{1}} is the group -- be sure to include the double "{{" and "}}".
|
||||
- name: nameGroupFormat
|
||||
type: string
|
||||
default: "{0} - {{1}}"
|
||||
|
||||
# An optional format string used to add the group to the test name.
|
||||
# {0} is the formatted test of an item in the targets list.
|
||||
# {{1}} is the group -- be sure to include the double "{{" and "}}".
|
||||
- name: testGroupFormat
|
||||
type: string
|
||||
default: "{0}/{{1}}"
|
||||
|
||||
jobs:
|
||||
- template: test.yml
|
||||
parameters:
|
||||
jobs:
|
||||
- ${{ if eq(length(parameters.groups), 0) }}:
|
||||
- ${{ each target in parameters.targets }}:
|
||||
- name: ${{ format(parameters.nameFormat, coalesce(target.name, target.test)) }}
|
||||
test: ${{ format(parameters.testFormat, coalesce(target.test, target.name)) }}
|
||||
- ${{ if not(eq(length(parameters.groups), 0)) }}:
|
||||
- ${{ each group in parameters.groups }}:
|
||||
- ${{ each target in parameters.targets }}:
|
||||
- name: ${{ format(format(parameters.nameGroupFormat, parameters.nameFormat), coalesce(target.name, target.test), group) }}
|
||||
test: ${{ format(format(parameters.testGroupFormat, parameters.testFormat), coalesce(target.test, target.name), group) }}
|
||||
50
.azure-pipelines/templates/test.yml
Normal file
50
.azure-pipelines/templates/test.yml
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# This template uses the provided list of jobs to create test one or more test jobs.
|
||||
# It can be used directly if needed, or through the matrix template.
|
||||
|
||||
parameters:
|
||||
# A required list of dictionaries, one per test job.
|
||||
# Each item in the list must contain a "job" and "name" key.
|
||||
- name: jobs
|
||||
type: object
|
||||
|
||||
jobs:
|
||||
- ${{ each job in parameters.jobs }}:
|
||||
- job: test_${{ replace(replace(replace(job.test, '/', '_'), '.', '_'), '-', '_') }}
|
||||
displayName: ${{ job.name }}
|
||||
container: default
|
||||
workspace:
|
||||
clean: all
|
||||
steps:
|
||||
- checkout: self
|
||||
fetchDepth: $(fetchDepth)
|
||||
path: $(checkoutPath)
|
||||
- bash: .azure-pipelines/scripts/run-tests.sh "$(entryPoint)" "${{ job.test }}" "$(coverageBranches)"
|
||||
displayName: Run Tests
|
||||
- bash: .azure-pipelines/scripts/process-results.sh
|
||||
condition: succeededOrFailed()
|
||||
displayName: Process Results
|
||||
- bash: .azure-pipelines/scripts/aggregate-coverage.sh "$(Agent.TempDirectory)"
|
||||
condition: eq(variables.haveCoverageData, 'true')
|
||||
displayName: Aggregate Coverage Data
|
||||
- task: PublishTestResults@2
|
||||
condition: eq(variables.haveTestResults, 'true')
|
||||
inputs:
|
||||
testResultsFiles: "$(outputPath)/junit/*.xml"
|
||||
displayName: Publish Test Results
|
||||
- task: PublishPipelineArtifact@1
|
||||
condition: eq(variables.haveBotResults, 'true')
|
||||
displayName: Publish Bot Results
|
||||
inputs:
|
||||
targetPath: "$(outputPath)/bot/"
|
||||
artifactName: "Bot $(System.JobAttempt) $(System.StageDisplayName) $(System.JobDisplayName)"
|
||||
- task: PublishPipelineArtifact@1
|
||||
condition: eq(variables.haveCoverageData, 'true')
|
||||
displayName: Publish Coverage Data
|
||||
inputs:
|
||||
targetPath: "$(Agent.TempDirectory)/coverage/"
|
||||
artifactName: "Coverage $(System.JobAttempt) $(System.StageDisplayName) $(System.JobDisplayName)"
|
||||
54
.github/BOTMETA.yml
vendored
54
.github/BOTMETA.yml
vendored
@@ -61,7 +61,6 @@ files:
|
||||
$callbacks/elastic.py:
|
||||
keywords: apm observability
|
||||
maintainers: v1v
|
||||
$callbacks/hipchat.py: {}
|
||||
$callbacks/jabber.py: {}
|
||||
$callbacks/log_plays.py: {}
|
||||
$callbacks/loganalytics.py:
|
||||
@@ -112,6 +111,9 @@ files:
|
||||
$connections/lxd.py:
|
||||
labels: lxd
|
||||
maintainers: mattclay
|
||||
$connections/proxmox_pct_remote.py:
|
||||
labels: proxmox
|
||||
maintainers: mietzen
|
||||
$connections/qubes.py:
|
||||
maintainers: kushaldas
|
||||
$connections/saltstack.py:
|
||||
@@ -121,6 +123,8 @@ files:
|
||||
maintainers: $team_ansible_core
|
||||
$doc_fragments/:
|
||||
labels: docs_fragments
|
||||
$doc_fragments/clc.py:
|
||||
maintainers: clc-runner russoz
|
||||
$doc_fragments/django.py:
|
||||
maintainers: russoz
|
||||
$doc_fragments/hpe3par.py:
|
||||
@@ -136,6 +140,8 @@ files:
|
||||
$doc_fragments/xenserver.py:
|
||||
labels: xenserver
|
||||
maintainers: bvitnik
|
||||
$filters/accumulate.py:
|
||||
maintainers: VannTen
|
||||
$filters/counter.py:
|
||||
maintainers: keilr
|
||||
$filters/crc32.py:
|
||||
@@ -158,6 +164,14 @@ files:
|
||||
maintainers: Ajpantuso
|
||||
$filters/jc.py:
|
||||
maintainers: kellyjonbrazil
|
||||
$filters/json_diff.yml:
|
||||
maintainers: numo68
|
||||
$filters/json_patch.py:
|
||||
maintainers: numo68
|
||||
$filters/json_patch.yml:
|
||||
maintainers: numo68
|
||||
$filters/json_patch_recipe.yml:
|
||||
maintainers: numo68
|
||||
$filters/json_query.py: {}
|
||||
$filters/keep_keys.py:
|
||||
maintainers: vbotka
|
||||
@@ -212,6 +226,8 @@ files:
|
||||
maintainers: opoplawski
|
||||
$inventories/gitlab_runners.py:
|
||||
maintainers: morph027
|
||||
$inventories/iocage.py:
|
||||
maintainers: vbotka
|
||||
$inventories/icinga2.py:
|
||||
maintainers: BongoEADGC6
|
||||
$inventories/linode.py:
|
||||
@@ -291,6 +307,8 @@ files:
|
||||
$lookups/onepassword_raw.py:
|
||||
ignore: scottsb
|
||||
maintainers: azenk
|
||||
$lookups/onepassword_ssh_key.py:
|
||||
maintainers: mohammedbabelly20
|
||||
$lookups/passwordstore.py: {}
|
||||
$lookups/random_pet.py:
|
||||
maintainers: Akasurde
|
||||
@@ -308,8 +326,12 @@ files:
|
||||
maintainers: delineaKrehl tylerezimmerman
|
||||
$module_utils/:
|
||||
labels: module_utils
|
||||
$module_utils/android_sdkmanager.py:
|
||||
maintainers: shamilovstas
|
||||
$module_utils/btrfs.py:
|
||||
maintainers: gnfzdz
|
||||
$module_utils/cmd_runner_fmt.py:
|
||||
maintainers: russoz
|
||||
$module_utils/cmd_runner.py:
|
||||
maintainers: russoz
|
||||
$module_utils/deps.py:
|
||||
@@ -417,6 +439,8 @@ files:
|
||||
ignore: DavidWittman jiuka
|
||||
labels: alternatives
|
||||
maintainers: mulby
|
||||
$modules/android_sdk.py:
|
||||
maintainers: shamilovstas
|
||||
$modules/ansible_galaxy_install.py:
|
||||
maintainers: russoz
|
||||
$modules/apache2_mod_proxy.py:
|
||||
@@ -505,6 +529,8 @@ files:
|
||||
ignore: skornehl
|
||||
$modules/dconf.py:
|
||||
maintainers: azaghal
|
||||
$modules/decompress.py:
|
||||
maintainers: shamilovstas
|
||||
$modules/deploy_helper.py:
|
||||
maintainers: ramondelafuente
|
||||
$modules/dimensiondata_network.py:
|
||||
@@ -761,6 +787,8 @@ files:
|
||||
maintainers: sermilrod
|
||||
$modules/jenkins_job_info.py:
|
||||
maintainers: stpierre
|
||||
$modules/jenkins_node.py:
|
||||
maintainers: phyrwork
|
||||
$modules/jenkins_plugin.py:
|
||||
maintainers: jtyr
|
||||
$modules/jenkins_script.py:
|
||||
@@ -797,6 +825,8 @@ files:
|
||||
maintainers: fynncfchen johncant
|
||||
$modules/keycloak_clientsecret_regenerate.py:
|
||||
maintainers: fynncfchen johncant
|
||||
$modules/keycloak_component.py:
|
||||
maintainers: fivetide
|
||||
$modules/keycloak_group.py:
|
||||
maintainers: adamgoossens
|
||||
$modules/keycloak_identity_provider.py:
|
||||
@@ -829,6 +859,8 @@ files:
|
||||
maintainers: ahussey-redhat
|
||||
$modules/kibana_plugin.py:
|
||||
maintainers: barryib
|
||||
$modules/krb_ticket.py:
|
||||
maintainers: abakanovskii
|
||||
$modules/launchd.py:
|
||||
maintainers: martinm82
|
||||
$modules/layman.py:
|
||||
@@ -839,6 +871,8 @@ files:
|
||||
maintainers: drybjed jtyr noles
|
||||
$modules/ldap_entry.py:
|
||||
maintainers: jtyr
|
||||
$modules/ldap_inc.py:
|
||||
maintainers: pduveau
|
||||
$modules/ldap_passwd.py:
|
||||
maintainers: KellerFuchs jtyr
|
||||
$modules/ldap_search.py:
|
||||
@@ -1110,6 +1144,10 @@ files:
|
||||
$modules/proxmox_kvm.py:
|
||||
ignore: skvidal
|
||||
maintainers: helldorado krauthosting
|
||||
$modules/proxmox_backup.py:
|
||||
maintainers: IamLunchbox
|
||||
$modules/proxmox_backup_info.py:
|
||||
maintainers: raoufnezhad mmayabi
|
||||
$modules/proxmox_nic.py:
|
||||
maintainers: Kogelvis krauthosting
|
||||
$modules/proxmox_node_info.py:
|
||||
@@ -1159,12 +1197,6 @@ files:
|
||||
keywords: kvm libvirt proxmox qemu
|
||||
labels: rhevm virt
|
||||
maintainers: $team_virt TimothyVandenbrande
|
||||
$modules/rhn_channel.py:
|
||||
labels: rhn_channel
|
||||
maintainers: vincentvdk alikins $team_rhn
|
||||
$modules/rhn_register.py:
|
||||
labels: rhn_register
|
||||
maintainers: jlaska $team_rhn
|
||||
$modules/rhsm_release.py:
|
||||
maintainers: seandst $team_rhsm
|
||||
$modules/rhsm_repository.py:
|
||||
@@ -1328,6 +1360,10 @@ files:
|
||||
maintainers: precurse
|
||||
$modules/sysrc.py:
|
||||
maintainers: dlundgren
|
||||
$modules/systemd_creds_decrypt.py:
|
||||
maintainers: konstruktoid
|
||||
$modules/systemd_creds_encrypt.py:
|
||||
maintainers: konstruktoid
|
||||
$modules/sysupgrade.py:
|
||||
maintainers: precurse
|
||||
$modules/taiga_issue.py:
|
||||
@@ -1447,6 +1483,9 @@ files:
|
||||
maintainers: $team_suse
|
||||
$plugin_utils/ansible_type.py:
|
||||
maintainers: vbotka
|
||||
$modules/zypper_repository_info.py:
|
||||
labels: zypper
|
||||
maintainers: $team_suse TobiasZeuch181
|
||||
$plugin_utils/keys_filter.py:
|
||||
maintainers: vbotka
|
||||
$plugin_utils/unsafe.py:
|
||||
@@ -1555,7 +1594,6 @@ macros:
|
||||
team_oracle: manojmeda mross22 nalsaber
|
||||
team_purestorage: bannaych dnix101 genegr lionmax opslounge raekins sdodsley sile16
|
||||
team_redfish: mraineri tomasg2012 xmadsen renxulei rajeevkallur bhavya06 jyundt
|
||||
team_rhn: FlossWare alikins barnabycourt vritant
|
||||
team_rhsm: cnsnyder ptoscano
|
||||
team_scaleway: remyleone abarbare
|
||||
team_solaris: bcoca fishman jasperla jpdasma mator scathatheworm troy2914 xen0l
|
||||
|
||||
178
.github/workflows/ansible-test.yml
vendored
Normal file
178
.github/workflows/ansible-test.yml
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# For the comprehensive list of the inputs supported by the ansible-community/ansible-test-gh-action GitHub Action, see
|
||||
# https://github.com/marketplace/actions/ansible-test
|
||||
|
||||
name: EOL CI
|
||||
on:
|
||||
# Run EOL CI against all pushes (direct commits, also merged PRs), Pull Requests
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- stable-*
|
||||
pull_request:
|
||||
# Run EOL CI once per day (at 08:00 UTC)
|
||||
schedule:
|
||||
- cron: '0 8 * * *'
|
||||
|
||||
concurrency:
|
||||
# Make sure there is at most one active run per PR, but do not cancel any non-PR runs
|
||||
group: ${{ github.workflow }}-${{ (github.head_ref && github.event.number) || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
sanity:
|
||||
name: EOL Sanity (Ⓐ${{ matrix.ansible }})
|
||||
strategy:
|
||||
matrix:
|
||||
ansible:
|
||||
- '2.15'
|
||||
# Ansible-test on various stable branches does not yet work well with cgroups v2.
|
||||
# Since ubuntu-latest now uses Ubuntu 22.04, we need to fall back to the ubuntu-20.04
|
||||
# image for these stable branches. The list of branches where this is necessary will
|
||||
# shrink over time, check out https://github.com/ansible-collections/news-for-maintainers/issues/28
|
||||
# for the latest list.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Perform sanity testing
|
||||
uses: felixfontein/ansible-test-gh-action@main
|
||||
with:
|
||||
ansible-core-version: stable-${{ matrix.ansible }}
|
||||
codecov-token: ${{ secrets.CODECOV_TOKEN }}
|
||||
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
|
||||
pull-request-change-detection: 'true'
|
||||
testing-type: sanity
|
||||
|
||||
units:
|
||||
# Ansible-test on various stable branches does not yet work well with cgroups v2.
|
||||
# Since ubuntu-latest now uses Ubuntu 22.04, we need to fall back to the ubuntu-20.04
|
||||
# image for these stable branches. The list of branches where this is necessary will
|
||||
# shrink over time, check out https://github.com/ansible-collections/news-for-maintainers/issues/28
|
||||
# for the latest list.
|
||||
runs-on: ubuntu-latest
|
||||
name: EOL Units (Ⓐ${{ matrix.ansible }}+py${{ matrix.python }})
|
||||
strategy:
|
||||
# As soon as the first unit test fails, cancel the others to free up the CI queue
|
||||
fail-fast: true
|
||||
matrix:
|
||||
ansible:
|
||||
- ''
|
||||
python:
|
||||
- ''
|
||||
exclude:
|
||||
- ansible: ''
|
||||
include:
|
||||
- ansible: '2.15'
|
||||
python: '2.7'
|
||||
- ansible: '2.15'
|
||||
python: '3.5'
|
||||
- ansible: '2.15'
|
||||
python: '3.10'
|
||||
|
||||
steps:
|
||||
- name: >-
|
||||
Perform unit testing against
|
||||
Ansible version ${{ matrix.ansible }}
|
||||
uses: felixfontein/ansible-test-gh-action@main
|
||||
with:
|
||||
ansible-core-version: stable-${{ matrix.ansible }}
|
||||
codecov-token: ${{ secrets.CODECOV_TOKEN }}
|
||||
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
|
||||
pre-test-cmd: >-
|
||||
mkdir -p ../../ansible
|
||||
;
|
||||
git clone --depth=1 --single-branch https://github.com/ansible-collections/community.internal_test_tools.git ../../community/internal_test_tools
|
||||
pull-request-change-detection: 'true'
|
||||
target-python-version: ${{ matrix.python }}
|
||||
testing-type: units
|
||||
|
||||
integration:
|
||||
# Ansible-test on various stable branches does not yet work well with cgroups v2.
|
||||
# Since ubuntu-latest now uses Ubuntu 22.04, we need to fall back to the ubuntu-20.04
|
||||
# image for these stable branches. The list of branches where this is necessary will
|
||||
# shrink over time, check out https://github.com/ansible-collections/news-for-maintainers/issues/28
|
||||
# for the latest list.
|
||||
runs-on: ubuntu-latest
|
||||
name: EOL I (Ⓐ${{ matrix.ansible }}+${{ matrix.docker }}+py${{ matrix.python }}:${{ matrix.target }})
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
ansible:
|
||||
- ''
|
||||
docker:
|
||||
- ''
|
||||
python:
|
||||
- ''
|
||||
target:
|
||||
- ''
|
||||
exclude:
|
||||
- ansible: ''
|
||||
include:
|
||||
# 2.15
|
||||
- ansible: '2.15'
|
||||
docker: alpine3
|
||||
python: ''
|
||||
target: azp/posix/1/
|
||||
- ansible: '2.15'
|
||||
docker: alpine3
|
||||
python: ''
|
||||
target: azp/posix/2/
|
||||
- ansible: '2.15'
|
||||
docker: alpine3
|
||||
python: ''
|
||||
target: azp/posix/3/
|
||||
- ansible: '2.15'
|
||||
docker: fedora37
|
||||
python: ''
|
||||
target: azp/posix/1/
|
||||
- ansible: '2.15'
|
||||
docker: fedora37
|
||||
python: ''
|
||||
target: azp/posix/2/
|
||||
- ansible: '2.15'
|
||||
docker: fedora37
|
||||
python: ''
|
||||
target: azp/posix/3/
|
||||
# Right now all generic tests are disabled. Uncomment when at least one of them is re-enabled.
|
||||
# - ansible: '2.13'
|
||||
# docker: default
|
||||
# python: '3.9'
|
||||
# target: azp/generic/1/
|
||||
# - ansible: '2.14'
|
||||
# docker: default
|
||||
# python: '3.10'
|
||||
# target: azp/generic/1/
|
||||
# - ansible: '2.15'
|
||||
# docker: default
|
||||
# python: '3.9'
|
||||
# target: azp/generic/1/
|
||||
|
||||
steps:
|
||||
- name: >-
|
||||
Perform integration testing against
|
||||
Ansible version ${{ matrix.ansible }}
|
||||
under Python ${{ matrix.python }}
|
||||
uses: felixfontein/ansible-test-gh-action@main
|
||||
with:
|
||||
ansible-core-version: stable-${{ matrix.ansible }}
|
||||
codecov-token: ${{ secrets.CODECOV_TOKEN }}
|
||||
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
|
||||
docker-image: ${{ matrix.docker }}
|
||||
integration-continue-on-error: 'false'
|
||||
integration-diff: 'false'
|
||||
integration-retry-on-error: 'true'
|
||||
pre-test-cmd: >-
|
||||
mkdir -p ../../ansible
|
||||
;
|
||||
git clone --depth=1 --single-branch https://github.com/ansible-collections/ansible.posix.git ../../ansible/posix
|
||||
;
|
||||
git clone --depth=1 --single-branch https://github.com/ansible-collections/community.crypto.git ../../community/crypto
|
||||
;
|
||||
git clone --depth=1 --single-branch https://github.com/ansible-collections/community.internal_test_tools.git ../../community/internal_test_tools
|
||||
pull-request-change-detection: 'true'
|
||||
target: ${{ matrix.target }}
|
||||
target-python-version: ${{ matrix.python }}
|
||||
testing-type: integration
|
||||
38
.github/workflows/codeql-analysis.yml
vendored
Normal file
38
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
name: "Code scanning - action"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '26 19 * * 1'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
CodeQL-Build:
|
||||
|
||||
permissions:
|
||||
actions: read # for github/codeql-action/init to get workflow details
|
||||
contents: read # for actions/checkout to fetch code
|
||||
security-events: write # for github/codeql-action/autobuild to send a status report
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: python
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
20
.github/workflows/import-galaxy.yml
vendored
Normal file
20
.github/workflows/import-galaxy.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
name: import-galaxy
|
||||
'on':
|
||||
# Run CI against all pushes (direct commits, also merged PRs) to main, and all Pull Requests
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- stable-*
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
import-galaxy:
|
||||
permissions:
|
||||
contents: read
|
||||
name: Test to import built collection artifact with Galaxy importer
|
||||
uses: ansible-community/github-action-test-galaxy-import/.github/workflows/test-galaxy-import.yml@main
|
||||
35
.github/workflows/reuse.yml
vendored
Normal file
35
.github/workflows/reuse.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
name: Verify REUSE
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- stable-*
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches:
|
||||
- main
|
||||
- stable-*
|
||||
# Run CI once per day (at 07:30 UTC)
|
||||
schedule:
|
||||
- cron: '30 7 * * *'
|
||||
|
||||
jobs:
|
||||
check:
|
||||
permissions:
|
||||
contents: read
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event.pull_request.head.sha || '' }}
|
||||
|
||||
- name: REUSE Compliance Check
|
||||
uses: fsfe/reuse-action@v5
|
||||
14
.gitignore
vendored
14
.gitignore
vendored
@@ -383,16 +383,6 @@ cython_debug/
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
### Python Patch ###
|
||||
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
|
||||
poetry.toml
|
||||
|
||||
# ruff
|
||||
.ruff_cache/
|
||||
|
||||
# LSP config files
|
||||
pyrightconfig.json
|
||||
|
||||
### Vim ###
|
||||
# Swap
|
||||
[._]*.s[a-v][a-z]
|
||||
@@ -492,10 +482,6 @@ tags
|
||||
# https://plugins.jetbrains.com/plugin/12206-codestream
|
||||
.idea/codestream.xml
|
||||
|
||||
# Azure Toolkit for IntelliJ plugin
|
||||
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
|
||||
.idea/**/azureSettings.xml
|
||||
|
||||
### Windows ###
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
|
||||
5
.reuse/dep5
Normal file
5
.reuse/dep5
Normal file
@@ -0,0 +1,5 @@
|
||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||
|
||||
Files: changelogs/fragments/*
|
||||
Copyright: Ansible Project
|
||||
License: GPL-3.0-or-later
|
||||
1592
CHANGELOG.md
1592
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
1427
CHANGELOG.rst
1427
CHANGELOG.rst
File diff suppressed because it is too large
Load Diff
@@ -44,49 +44,7 @@ If you want to test a PR locally, refer to [our testing guide](https://github.co
|
||||
|
||||
If you find any inconsistencies or places in this document which can be improved, feel free to raise an issue or pull request to fix it.
|
||||
|
||||
## Run sanity or unit locally (with antsibull-nox)
|
||||
|
||||
The easiest way to run sanity and unit tests locally is to use [antsibull-nox](https://ansible.readthedocs.io/projects/antsibull-nox/).
|
||||
(If you have [nox](https://nox.thea.codes/en/stable/) installed, it will automatically install antsibull-nox in a virtual environment for you.)
|
||||
|
||||
### Sanity tests
|
||||
|
||||
The following commands show how to run ansible-test sanity tests:
|
||||
|
||||
```.bash
|
||||
# Run basic sanity tests for all files in the collection:
|
||||
nox -Re ansible-test-sanity-devel
|
||||
|
||||
# Run basic sanity tests for the given files and directories:
|
||||
nox -Re ansible-test-sanity-devel -- plugins/modules/system/pids.py tests/integration/targets/pids/
|
||||
|
||||
# Run all other sanity tests for all files in the collection:
|
||||
nox -R
|
||||
```
|
||||
|
||||
If you replace `-Re` with `-e`, respectively. If you leave `-R` away, then the virtual environments will be re-created. The `-R` re-uses them (if they already exist).
|
||||
|
||||
### Unit tests
|
||||
|
||||
The following commands show how to run unit tests:
|
||||
|
||||
```.bash
|
||||
# Run all unit tests:
|
||||
nox -Re ansible-test-units-devel
|
||||
|
||||
# Run all unit tests for one Python version (a lot faster):
|
||||
nox -Re ansible-test-units-devel -- --python 3.13
|
||||
|
||||
# Run a specific unit test (for the nmcli module) for one Python version:
|
||||
nox -Re ansible-test-units-devel -- --python 3.13 tests/unit/plugins/modules/net_tools/test_nmcli.py
|
||||
```
|
||||
|
||||
If you replace `-Re` with `-e`, then the virtual environments will be re-created. The `-R` re-uses them (if they already exist).
|
||||
|
||||
## Run basic sanity, unit or integration tests locally (with ansible-test)
|
||||
|
||||
Instead of using antsibull-nox, you can also run sanity and unit tests with ansible-test directly.
|
||||
This also allows you to run integration tests.
|
||||
## Run sanity, unit or integration tests locally
|
||||
|
||||
You have to check out the repository into a specific path structure to be able to run `ansible-test`. The path to the git checkout must end with `.../ansible_collections/community/general`. Please see [our testing guide](https://github.com/ansible/community-docs/blob/main/test_pr_locally_guide.rst) for instructions on how to check out the repository into a correct path structure. The short version of these instructions is:
|
||||
|
||||
@@ -98,27 +56,20 @@ cd ~/dev/ansible_collections/community/general
|
||||
|
||||
Then you can run `ansible-test` (which is a part of [ansible-core](https://pypi.org/project/ansible-core/)) inside the checkout. The following example commands expect that you have installed Docker or Podman. Note that Podman has only been supported by more recent ansible-core releases. If you are using Docker, the following will work with Ansible 2.9+.
|
||||
|
||||
### Basic sanity tests
|
||||
### Sanity tests
|
||||
|
||||
The following commands show how to run basic sanity tests:
|
||||
The following commands show how to run sanity tests:
|
||||
|
||||
```.bash
|
||||
# Run basic sanity tests for all files in the collection:
|
||||
# Run sanity tests for all files in the collection:
|
||||
ansible-test sanity --docker -v
|
||||
|
||||
# Run basic sanity tests for the given files and directories:
|
||||
# Run sanity tests for the given files and directories:
|
||||
ansible-test sanity --docker -v plugins/modules/system/pids.py tests/integration/targets/pids/
|
||||
```
|
||||
|
||||
### Unit tests
|
||||
|
||||
Note that for running unit tests, you need to install required collections in the same folder structure that `community.general` is checked out in.
|
||||
Right now, you need to install [`community.internal_test_tools`](https://github.com/ansible-collections/community.internal_test_tools).
|
||||
If you want to use the latest version from GitHub, you can run:
|
||||
```
|
||||
git clone https://github.com/ansible-collections/community.internal_test_tools.git ~/dev/ansible_collections/community/internal_test_tools
|
||||
```
|
||||
|
||||
The following commands show how to run unit tests:
|
||||
|
||||
```.bash
|
||||
@@ -134,16 +85,6 @@ ansible-test units --docker -v --python 3.8 tests/unit/plugins/modules/net_tools
|
||||
|
||||
### Integration tests
|
||||
|
||||
Note that for running integration tests, you need to install required collections in the same folder structure that `community.general` is checked out in.
|
||||
Right now, depending on the test, you need to install [`ansible.posix`](https://github.com/ansible-collections/ansible.posix), [`community.crypto`](https://github.com/ansible-collections/community.crypto), and [`community.docker`](https://github.com/ansible-collections/community.docker):
|
||||
If you want to use the latest versions from GitHub, you can run:
|
||||
```
|
||||
mkdir -p ~/dev/ansible_collections/ansible
|
||||
git clone https://github.com/ansible-collections/ansible.posix.git ~/dev/ansible_collections/ansible/posix
|
||||
git clone https://github.com/ansible-collections/community.crypto.git ~/dev/ansible_collections/community/crypto
|
||||
git clone https://github.com/ansible-collections/community.docker.git ~/dev/ansible_collections/community/docker
|
||||
```
|
||||
|
||||
The following commands show how to run integration tests:
|
||||
|
||||
#### In Docker
|
||||
@@ -151,8 +92,8 @@ The following commands show how to run integration tests:
|
||||
Integration tests on Docker have the following parameters:
|
||||
- `image_name` (required): The name of the Docker image. To get the list of supported Docker images, run
|
||||
`ansible-test integration --help` and look for _target docker images_.
|
||||
- `test_name` (optional): The name of the integration test.
|
||||
For modules, this equals the short name of the module; for example, `pacman` in case of `community.general.pacman`.
|
||||
- `test_name` (optional): The name of the integration test.
|
||||
For modules, this equals the short name of the module; for example, `pacman` in case of `community.general.pacman`.
|
||||
For plugins, the plugin type is added before the plugin's short name, for example `callback_yaml` for the `community.general.yaml` callback.
|
||||
```.bash
|
||||
# Test all plugins/modules on fedora40
|
||||
|
||||
15
README.md
15
README.md
@@ -7,9 +7,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# Community General Collection
|
||||
|
||||
[](https://docs.ansible.com/ansible/latest/collections/community/general/)
|
||||
[](https://dev.azure.com/ansible/community.general/_build?definitionId=31)
|
||||
[](https://github.com/ansible-collections/community.general/actions)
|
||||
[](https://github.com/ansible-collections/community.general/actions)
|
||||
[](https://dev.azure.com/ansible/community.general/_build?definitionId=31)
|
||||
[](https://github.com/ansible-collections/community.general/actions)
|
||||
[](https://codecov.io/gh/ansible-collections/community.general)
|
||||
[](https://api.reuse.software/info/github.com/ansible-collections/community.general)
|
||||
|
||||
@@ -39,7 +38,7 @@ For more information about communication, see the [Ansible communication guide](
|
||||
|
||||
## Tested with Ansible
|
||||
|
||||
Tested with the current ansible-core 2.13, ansible-core 2.14, ansible-core 2.15, ansible-core 2.16, ansible-core 2.17, ansible-core 2.18, and ansible-core 2.19 releases. Ansible-core versions before 2.13.0 are not supported. This includes all ansible-base 2.10 and Ansible 2.9 releases.
|
||||
Tested with the current ansible-core 2.15, ansible-core 2.16, ansible-core 2.17, ansible-core 2.18 releases and the current development version of ansible-core. Ansible-core versions before 2.15.0 are not supported. This includes all ansible-base 2.10 and Ansible 2.9 releases.
|
||||
|
||||
## External requirements
|
||||
|
||||
@@ -118,7 +117,7 @@ See the [Releasing guidelines](https://github.com/ansible/community-docs/blob/ma
|
||||
|
||||
## Release notes
|
||||
|
||||
See the [changelog](https://github.com/ansible-collections/community.general/blob/stable-9/CHANGELOG.md).
|
||||
See the [changelog](https://github.com/ansible-collections/community.general/blob/stable-10/CHANGELOG.md).
|
||||
|
||||
## Roadmap
|
||||
|
||||
@@ -137,8 +136,8 @@ See [this issue](https://github.com/ansible-collections/community.general/issues
|
||||
|
||||
This collection is primarily licensed and distributed as a whole under the GNU General Public License v3.0 or later.
|
||||
|
||||
See [LICENSES/GPL-3.0-or-later.txt](https://github.com/ansible-collections/community.general/blob/stable-9/COPYING) for the full text.
|
||||
See [LICENSES/GPL-3.0-or-later.txt](https://github.com/ansible-collections/community.general/blob/stable-10/COPYING) for the full text.
|
||||
|
||||
Parts of the collection are licensed under the [BSD 2-Clause license](https://github.com/ansible-collections/community.general/blob/stable-9/LICENSES/BSD-2-Clause.txt), the [MIT license](https://github.com/ansible-collections/community.general/blob/stable-9/LICENSES/MIT.txt), and the [PSF 2.0 license](https://github.com/ansible-collections/community.general/blob/stable-9/LICENSES/PSF-2.0.txt).
|
||||
Parts of the collection are licensed under the [BSD 2-Clause license](https://github.com/ansible-collections/community.general/blob/stable-10/LICENSES/BSD-2-Clause.txt), the [MIT license](https://github.com/ansible-collections/community.general/blob/stable-10/LICENSES/MIT.txt), and the [PSF 2.0 license](https://github.com/ansible-collections/community.general/blob/stable-10/LICENSES/PSF-2.0.txt).
|
||||
|
||||
All files have a machine readable `SDPX-License-Identifier:` comment denoting its respective license(s) or an equivalent entry in an accompanying `.license` file. Only changelog fragments (which will not be part of a release) are covered by a blanket statement in `REUSE.toml`. This conforms to the [REUSE specification](https://reuse.software/spec/).
|
||||
All files have a machine readable `SDPX-License-Identifier:` comment denoting its respective license(s) or an equivalent entry in an accompanying `.license` file. Only changelog fragments (which will not be part of a release) are covered by a blanket statement in `.reuse/dep5`. This conforms to the [REUSE specification](https://reuse.software/spec/).
|
||||
|
||||
11
REUSE.toml
11
REUSE.toml
@@ -1,11 +0,0 @@
|
||||
# Copyright (c) Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
version = 1
|
||||
|
||||
[[annotations]]
|
||||
path = "changelogs/fragments/**"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "Ansible Project"
|
||||
SPDX-License-Identifier = "GPL-3.0-or-later"
|
||||
@@ -1,68 +0,0 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# SPDX-FileCopyrightText: 2025 Felix Fontein <felix@fontein.de>
|
||||
|
||||
[collection_sources]
|
||||
"ansible.posix" = "git+https://github.com/ansible-collections/ansible.posix.git,main"
|
||||
"community.crypto" = "git+https://github.com/ansible-collections/community.crypto.git,main"
|
||||
"community.docker" = "git+https://github.com/ansible-collections/community.docker.git,main"
|
||||
"community.internal_test_tools" = "git+https://github.com/ansible-collections/community.internal_test_tools.git,main"
|
||||
|
||||
[collection_sources_per_ansible.'2.13']
|
||||
"community.crypto" = "git+https://github.com/ansible-collections/community.crypto.git,stable-2"
|
||||
|
||||
[collection_sources_per_ansible.'2.14']
|
||||
"community.crypto" = "git+https://github.com/ansible-collections/community.crypto.git,stable-2"
|
||||
|
||||
[collection_sources_per_ansible.'2.15']
|
||||
# community.crypto's main branch needs ansible-core >= 2.17
|
||||
"community.crypto" = "git+https://github.com/ansible-collections/community.crypto.git,stable-2"
|
||||
|
||||
[collection_sources_per_ansible.'2.16']
|
||||
# community.crypto's main branch needs ansible-core >= 2.17
|
||||
"community.crypto" = "git+https://github.com/ansible-collections/community.crypto.git,stable-2"
|
||||
|
||||
[vcs]
|
||||
vcs = "git"
|
||||
development_branch = "main"
|
||||
stable_branches = [ "stable-*" ]
|
||||
|
||||
[sessions]
|
||||
|
||||
[sessions.docs_check]
|
||||
validate_collection_refs="all"
|
||||
|
||||
[sessions.license_check]
|
||||
|
||||
[sessions.extra_checks]
|
||||
run_no_unwanted_files = true
|
||||
no_unwanted_files_module_extensions = [".py"]
|
||||
no_unwanted_files_yaml_extensions = [".yml"]
|
||||
run_action_groups = true
|
||||
|
||||
[[sessions.extra_checks.action_groups_config]]
|
||||
name = "consul"
|
||||
pattern = "^consul_.*$"
|
||||
exclusions = [
|
||||
"consul_acl",
|
||||
"consul_acl_bootstrap",
|
||||
"consul_kv",
|
||||
]
|
||||
doc_fragment = "community.general.consul.actiongroup_consul"
|
||||
|
||||
[[sessions.extra_checks.action_groups_config]]
|
||||
name = "proxmox"
|
||||
pattern = "^proxmox(_.*)?$"
|
||||
exclusions = []
|
||||
doc_fragment = "community.general.proxmox.actiongroup_proxmox"
|
||||
|
||||
[sessions.build_import_check]
|
||||
run_galaxy_importer = true
|
||||
|
||||
[sessions.ansible_test_sanity]
|
||||
include_devel = false
|
||||
max_version = "2.19"
|
||||
|
||||
[sessions.ansible_test_units]
|
||||
include_devel = false
|
||||
max_version = "2.19"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,9 +7,9 @@ changelog_filename_template: ../CHANGELOG.rst
|
||||
changelog_filename_version_depth: 0
|
||||
changes_file: changelog.yaml
|
||||
changes_format: combined
|
||||
ignore_other_fragment_extensions: true
|
||||
keep_fragments: false
|
||||
mention_ancestor: true
|
||||
flatmap: true
|
||||
new_plugins_after_name: removed_features
|
||||
notesdir: fragments
|
||||
output_formats:
|
||||
@@ -40,4 +40,3 @@ use_fqcn: true
|
||||
add_plugin_period: true
|
||||
changelog_nice_yaml: true
|
||||
changelog_sort: version
|
||||
vcs: auto
|
||||
|
||||
@@ -65,7 +65,7 @@ All three statements are equivalent and give:
|
||||
|
||||
.. note:: Be aware that in most cases, filter calls without any argument require ``flatten=true``, otherwise the input is returned as result. The reason for this is, that the input is considered as a variable argument and is wrapped by an additional outer list. ``flatten=true`` ensures that this list is removed before the input is processed by the filter logic.
|
||||
|
||||
The filters :ansplugin:`community.general.lists_difference#filter` or :ansplugin:`community.general.lists_symmetric_difference#filter` can be used in the same way as the filters in the examples above. They calculate the difference or the symmetric difference between two or more lists and preserve the item order.
|
||||
The filters ansplugin:`community.general.lists_difference#filter` or :ansplugin:`community.general.lists_symmetric_difference#filter` can be used in the same way as the filters in the examples above. They calculate the difference or the symmetric difference between two or more lists and preserve the item order.
|
||||
|
||||
For example, the symmetric difference of ``A``, ``B`` and ``C`` may be written as:
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ To get a hash map with all ports and names of a cluster:
|
||||
var: item
|
||||
loop: "{{ domain_definition | community.general.json_query(server_name_cluster1_query) }}"
|
||||
vars:
|
||||
server_name_cluster1_query: "domain.server[?cluster=='cluster1'].{name: name, port: port}"
|
||||
server_name_cluster1_query: "domain.server[?cluster=='cluster2'].{name: name, port: port}"
|
||||
|
||||
To extract ports from all clusters with name starting with 'server1':
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ The same example from the Developer Guide would become:
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils import deps
|
||||
|
||||
|
||||
with deps.declare("foo"):
|
||||
import foo
|
||||
|
||||
|
||||
@@ -75,7 +75,6 @@ section above, but there are more elements that will take part in it.
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.module_helper import ModuleHelper
|
||||
|
||||
|
||||
class MyTest(ModuleHelper):
|
||||
output_params = ()
|
||||
change_params = ()
|
||||
@@ -257,9 +256,9 @@ With that, MH will automatically generate the diff output for variables that hav
|
||||
class MyTest(ModuleHelper):
|
||||
diff_params = ('value', )
|
||||
|
||||
def __run__(self):
|
||||
# example from community.general.gio_mime
|
||||
self.vars.set_meta("handler", initial_value=gio_mime_get(self.runner, self.vars.mime_type), diff=True, change=True)
|
||||
def __run__(self):
|
||||
# example from community.general.gio_mime
|
||||
self.vars.set_meta("handler", initial_value=gio_mime_get(self.runner, self.vars.mime_type), diff=True, change=True)
|
||||
|
||||
Moreover, if a module is set to return *facts* instead of return values, then again use the metadata ``fact=True`` and ``fact_params`` for module parameters.
|
||||
Additionally, you must specify ``facts_name``, as in:
|
||||
@@ -347,6 +346,8 @@ However, you can set output variables specifically for that exception, if you so
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from ansible_collections.community.general.plugins.module_utils.module_helper import ModuleHelperException
|
||||
|
||||
def __init_module__(self):
|
||||
if not complex_validation():
|
||||
self.do_raise("Validation failed!")
|
||||
@@ -355,11 +356,16 @@ However, you can set output variables specifically for that exception, if you so
|
||||
awesomeness = calculate_awesomeness()
|
||||
if awesomeness > 1000:
|
||||
self.do_raise("Over awesome, I cannot handle it!", update_output={"awesomeness": awesomeness})
|
||||
# which is just a convenience shortcut for
|
||||
raise ModuleHelperException("...", update_output={...})
|
||||
|
||||
All exceptions derived from ``Exception`` are captured and translated into a ``fail_json()`` call.
|
||||
However, if you do want to call ``self.module.fail_json()`` yourself it will work,
|
||||
just keep in mind that there will be no automatic handling of output variables in that case.
|
||||
|
||||
Behind the curtains, all ``do_raise()`` does is to raise a ``ModuleHelperException``.
|
||||
If you want to create specialized error handling for your code, the best way is to extend that clas and raise it when needed.
|
||||
|
||||
.. _ansible_collections.community.general.docsite.guide_modulehelper.statemh:
|
||||
|
||||
StateModuleHelper
|
||||
@@ -462,6 +468,11 @@ Additionally, MH will also delegate:
|
||||
- ``diff_mode`` to ``self.module._diff``
|
||||
- ``verbosity`` to ``self.module._verbosity``
|
||||
|
||||
Starting in community.general 10.3.0, MH will also delegate the method ``debug`` to ``self.module``.
|
||||
If any existing module already has a ``debug`` attribute defined, a warning message will be generated,
|
||||
requesting it to be renamed. Upon the release of community.general 12.0.0, the delegation will be
|
||||
preemptive and will override any existing method or property in the subclasses.
|
||||
|
||||
Decorators
|
||||
""""""""""
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace: community
|
||||
name: general
|
||||
version: 9.5.13
|
||||
version: 10.3.0
|
||||
readme: README.md
|
||||
authors:
|
||||
- Ansible (https://github.com/ansible)
|
||||
|
||||
352
meta/runtime.yml
352
meta/runtime.yml
@@ -3,7 +3,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
requires_ansible: '>=2.13.0'
|
||||
requires_ansible: '>=2.15.0'
|
||||
action_groups:
|
||||
consul:
|
||||
- consul_agent_check
|
||||
@@ -16,6 +16,8 @@ action_groups:
|
||||
- consul_token
|
||||
proxmox:
|
||||
- proxmox
|
||||
- proxmox_backup
|
||||
- proxmox_backup_info
|
||||
- proxmox_disk
|
||||
- proxmox_domain_info
|
||||
- proxmox_group_info
|
||||
@@ -31,6 +33,34 @@ action_groups:
|
||||
- proxmox_template
|
||||
- proxmox_user_info
|
||||
- proxmox_vm_info
|
||||
keycloak:
|
||||
- keycloak_authentication
|
||||
- keycloak_authentication_required_actions
|
||||
- keycloak_authz_authorization_scope
|
||||
- keycloak_authz_custom_policy
|
||||
- keycloak_authz_permission
|
||||
- keycloak_authz_permission_info
|
||||
- keycloak_client
|
||||
- keycloak_client_rolemapping
|
||||
- keycloak_client_rolescope
|
||||
- keycloak_clientscope
|
||||
- keycloak_clientscope_type
|
||||
- keycloak_clientsecret_info
|
||||
- keycloak_clientsecret_regenerate
|
||||
- keycloak_clienttemplate
|
||||
- keycloak_component
|
||||
- keycloak_component_info
|
||||
- keycloak_group
|
||||
- keycloak_identity_provider
|
||||
- keycloak_realm
|
||||
- keycloak_realm_key
|
||||
- keycloak_realm_keys_metadata_info
|
||||
- keycloak_realm_rolemapping
|
||||
- keycloak_role
|
||||
- keycloak_user
|
||||
- keycloak_user_federation
|
||||
- keycloak_user_rolemapping
|
||||
- keycloak_userprofile
|
||||
plugin_routing:
|
||||
callback:
|
||||
actionable:
|
||||
@@ -44,7 +74,7 @@ plugin_routing:
|
||||
warning_text: Use the 'default' callback plugin with 'display_skipped_hosts
|
||||
= no' option.
|
||||
hipchat:
|
||||
deprecation:
|
||||
tombstone:
|
||||
removal_version: 10.0.0
|
||||
warning_text: The hipchat service has been discontinued and the self-hosted variant has been End of Life since 2020.
|
||||
osx_say:
|
||||
@@ -54,6 +84,10 @@ plugin_routing:
|
||||
removal_version: 2.0.0
|
||||
warning_text: Use the 'default' callback plugin with 'display_failed_stderr
|
||||
= yes' option.
|
||||
yaml:
|
||||
deprecation:
|
||||
removal_version: 13.0.0
|
||||
warning_text: The plugin has been superseded by the the option `result_format=yaml` in callback plugin ansible.builtin.default from ansible-core 2.13 onwards.
|
||||
connection:
|
||||
docker:
|
||||
redirect: community.docker.docker
|
||||
@@ -71,140 +105,28 @@ plugin_routing:
|
||||
nios_next_network:
|
||||
redirect: infoblox.nios_modules.nios_next_network
|
||||
modules:
|
||||
consul_acl:
|
||||
deprecation:
|
||||
removal_version: 10.0.0
|
||||
warning_text: Use community.general.consul_token and/or community.general.consul_policy instead.
|
||||
hipchat:
|
||||
deprecation:
|
||||
removal_version: 11.0.0
|
||||
warning_text: The hipchat service has been discontinued and the self-hosted variant has been End of Life since 2020.
|
||||
rax_cbs_attachments:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_cbs:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_cdb_database:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_cdb_user:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_cdb:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_clb_nodes:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_clb_ssl:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_clb:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_dns_record:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_dns:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_facts:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_files_objects:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_files:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_identity:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_keypair:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_meta:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_mon_alarm:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_mon_check:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_mon_entity:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_mon_notification_plan:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_mon_notification:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_network:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_queue:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_scaling_group:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_scaling_policy:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rhn_channel:
|
||||
deprecation:
|
||||
removal_version: 10.0.0
|
||||
warning_text: RHN is EOL, please contact the community.general maintainers
|
||||
if still using this; see the module documentation for more details.
|
||||
rhn_register:
|
||||
deprecation:
|
||||
removal_version: 10.0.0
|
||||
warning_text: RHN is EOL, please contact the community.general maintainers
|
||||
if still using this; see the module documentation for more details.
|
||||
stackdriver:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on HTTPS APIs that do not exist anymore,
|
||||
and any new development in the direction of providing an alternative should
|
||||
happen in the context of the google.cloud collection.
|
||||
ali_instance_facts:
|
||||
tombstone:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.ali_instance_info instead.
|
||||
atomic_container:
|
||||
deprecation:
|
||||
removal_version: 13.0.0
|
||||
warning_text: Poject Atomic was sunset by the end of 2019.
|
||||
atomic_host:
|
||||
deprecation:
|
||||
removal_version: 13.0.0
|
||||
warning_text: Poject Atomic was sunset by the end of 2019.
|
||||
atomic_image:
|
||||
deprecation:
|
||||
removal_version: 13.0.0
|
||||
warning_text: Poject Atomic was sunset by the end of 2019.
|
||||
cisco_spark:
|
||||
redirect: community.general.cisco_webex
|
||||
consul_acl:
|
||||
tombstone:
|
||||
removal_version: 10.0.0
|
||||
warning_text: Use community.general.consul_token and/or community.general.consul_policy instead.
|
||||
docker_compose:
|
||||
redirect: community.docker.docker_compose
|
||||
docker_config:
|
||||
@@ -259,6 +181,10 @@ plugin_routing:
|
||||
redirect: community.docker.docker_volume
|
||||
docker_volume_info:
|
||||
redirect: community.docker.docker_volume_info
|
||||
facter:
|
||||
deprecation:
|
||||
removal_version: 12.0.0
|
||||
warning_text: Use community.general.facter_facts instead.
|
||||
flowdock:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
@@ -352,6 +278,10 @@ plugin_routing:
|
||||
redirect: community.hrobot.firewall
|
||||
hetzner_firewall_info:
|
||||
redirect: community.hrobot.firewall_info
|
||||
hipchat:
|
||||
deprecation:
|
||||
removal_version: 11.0.0
|
||||
warning_text: The hipchat service has been discontinued and the self-hosted variant has been End of Life since 2020.
|
||||
hpilo_facts:
|
||||
tombstone:
|
||||
removal_version: 3.0.0
|
||||
@@ -685,10 +615,122 @@ plugin_routing:
|
||||
tombstone:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.python_requirements_info instead.
|
||||
rax_cbs_attachments:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_cbs:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_cdb_database:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_cdb_user:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_cdb:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_clb_nodes:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_clb_ssl:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_clb:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_dns_record:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_dns:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_facts:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_files_objects:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_files:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_identity:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_keypair:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_meta:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_mon_alarm:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_mon_check:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_mon_entity:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_mon_notification_plan:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_mon_notification:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_network:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_queue:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_scaling_group:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
rax_scaling_policy:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on the deprecated package pyrax.
|
||||
redfish_facts:
|
||||
tombstone:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.redfish_info instead.
|
||||
rhn_channel:
|
||||
tombstone:
|
||||
removal_version: 10.0.0
|
||||
warning_text: RHN is EOL.
|
||||
rhn_register:
|
||||
tombstone:
|
||||
removal_version: 10.0.0
|
||||
warning_text: RHN is EOL.
|
||||
sapcar_extract:
|
||||
redirect: community.sap_libs.sapcar_extract
|
||||
sap_task_list_execute:
|
||||
@@ -721,6 +763,26 @@ plugin_routing:
|
||||
tombstone:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.scaleway_volume_info instead.
|
||||
sensu_check:
|
||||
deprecation:
|
||||
removal_version: 13.0.0
|
||||
warning_text: Sensu Core and Sensu Enterprise products have been End of Life since 2019/20.
|
||||
sensu_client:
|
||||
deprecation:
|
||||
removal_version: 13.0.0
|
||||
warning_text: Sensu Core and Sensu Enterprise products have been End of Life since 2019/20.
|
||||
sensu_handler:
|
||||
deprecation:
|
||||
removal_version: 13.0.0
|
||||
warning_text: Sensu Core and Sensu Enterprise products have been End of Life since 2019/20.
|
||||
sensu_silence:
|
||||
deprecation:
|
||||
removal_version: 13.0.0
|
||||
warning_text: Sensu Core and Sensu Enterprise products have been End of Life since 2019/20.
|
||||
sensu_subscription:
|
||||
deprecation:
|
||||
removal_version: 13.0.0
|
||||
warning_text: Sensu Core and Sensu Enterprise products have been End of Life since 2019/20.
|
||||
sf_account_manager:
|
||||
tombstone:
|
||||
removal_version: 2.0.0
|
||||
@@ -745,6 +807,12 @@ plugin_routing:
|
||||
tombstone:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.smartos_image_info instead.
|
||||
stackdriver:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module relied on HTTPS APIs that do not exist anymore,
|
||||
and any new development in the direction of providing an alternative should
|
||||
happen in the context of the google.cloud collection.
|
||||
vertica_facts:
|
||||
tombstone:
|
||||
removal_version: 3.0.0
|
||||
@@ -779,11 +847,6 @@ plugin_routing:
|
||||
removal_version: 3.0.0
|
||||
warning_text: Use community.general.xenserver_guest_info instead.
|
||||
doc_fragments:
|
||||
rackspace:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This doc fragment was used by rax modules, that relied on the deprecated
|
||||
package pyrax.
|
||||
_gcp:
|
||||
redirect: community.google._gcp
|
||||
docker:
|
||||
@@ -798,11 +861,16 @@ plugin_routing:
|
||||
redirect: infoblox.nios_modules.nios
|
||||
postgresql:
|
||||
redirect: community.postgresql.postgresql
|
||||
module_utils:
|
||||
rax:
|
||||
purestorage:
|
||||
deprecation:
|
||||
removal_version: 12.0.0
|
||||
warning_text: The modules for purestorage were removed in community.general 3.0.0, this document fragment was left behind.
|
||||
rackspace:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module util relied on the deprecated package pyrax.
|
||||
warning_text: This doc fragment was used by rax modules, that relied on the deprecated
|
||||
package pyrax.
|
||||
module_utils:
|
||||
docker.common:
|
||||
redirect: community.docker.common
|
||||
docker.swarm:
|
||||
@@ -821,6 +889,14 @@ plugin_routing:
|
||||
redirect: infoblox.nios_modules.api
|
||||
postgresql:
|
||||
redirect: community.postgresql.postgresql
|
||||
pure:
|
||||
deprecation:
|
||||
removal_version: 12.0.0
|
||||
warning_text: The modules for purestorage were removed in community.general 3.0.0, this module util was left behind.
|
||||
rax:
|
||||
tombstone:
|
||||
removal_version: 9.0.0
|
||||
warning_text: This module util relied on the deprecated package pyrax.
|
||||
remote_management.dellemc.dellemc_idrac:
|
||||
redirect: dellemc.openmanage.dellemc_idrac
|
||||
remote_management.dellemc.ome:
|
||||
|
||||
38
noxfile.py
38
noxfile.py
@@ -1,38 +0,0 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
# SPDX-FileCopyrightText: 2025 Felix Fontein <felix@fontein.de>
|
||||
|
||||
# /// script
|
||||
# dependencies = ["nox>=2025.02.09", "antsibull-nox"]
|
||||
# ///
|
||||
|
||||
import sys
|
||||
|
||||
import nox
|
||||
|
||||
|
||||
try:
|
||||
import antsibull_nox
|
||||
except ImportError:
|
||||
print("You need to install antsibull-nox in the same Python environment as nox.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
antsibull_nox.load_antsibull_nox_toml()
|
||||
|
||||
|
||||
@nox.session(name="aliases", python=False, default=True)
|
||||
def aliases(session: nox.Session) -> None:
|
||||
session.run("python", "tests/sanity/extra/aliases.py")
|
||||
|
||||
|
||||
@nox.session(name="botmeta", default=True)
|
||||
def botmeta(session: nox.Session) -> None:
|
||||
session.install("PyYAML", "voluptuous")
|
||||
session.run("python", "tests/sanity/extra/botmeta.py")
|
||||
|
||||
|
||||
# Allow to run the noxfile with `python noxfile.py`, `pipx run noxfile.py`, or similar.
|
||||
# Requires nox >= 2025.02.09
|
||||
if __name__ == "__main__":
|
||||
nox.main()
|
||||
@@ -3,8 +3,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
|
||||
@@ -22,25 +21,33 @@ class ActionModule(ActionBase):
|
||||
_VALID_ARGS = frozenset(('path', 'state', 'table', 'noflush', 'counters', 'modprobe', 'ip_version', 'wait'))
|
||||
DEFAULT_SUDOABLE = True
|
||||
|
||||
MSG_ERROR__ASYNC_AND_POLL_NOT_ZERO = (
|
||||
"This module doesn't support async>0 and poll>0 when its 'state' param "
|
||||
"is set to 'restored'. To enable its rollback feature (that needs the "
|
||||
"module to run asynchronously on the remote), please set task attribute "
|
||||
"'poll' (=%s) to 0, and 'async' (=%s) to a value >2 and not greater than "
|
||||
"'ansible_timeout' (=%s) (recommended).")
|
||||
MSG_WARNING__NO_ASYNC_IS_NO_ROLLBACK = (
|
||||
"Attempts to restore iptables state without rollback in case of mistake "
|
||||
"may lead the ansible controller to loose access to the hosts and never "
|
||||
"regain it before fixing firewall rules through a serial console, or any "
|
||||
"other way except SSH. Please set task attribute 'poll' (=%s) to 0, and "
|
||||
"'async' (=%s) to a value >2 and not greater than 'ansible_timeout' (=%s) "
|
||||
"(recommended).")
|
||||
MSG_WARNING__ASYNC_GREATER_THAN_TIMEOUT = (
|
||||
"You attempt to restore iptables state with rollback in case of mistake, "
|
||||
"but with settings that will lead this rollback to happen AFTER that the "
|
||||
"controller will reach its own timeout. Please set task attribute 'poll' "
|
||||
"(=%s) to 0, and 'async' (=%s) to a value >2 and not greater than "
|
||||
"'ansible_timeout' (=%s) (recommended).")
|
||||
@staticmethod
|
||||
def msg_error__async_and_poll_not_zero(task_poll, task_async, max_timeout):
|
||||
return (
|
||||
"This module doesn't support async>0 and poll>0 when its 'state' param "
|
||||
"is set to 'restored'. To enable its rollback feature (that needs the "
|
||||
"module to run asynchronously on the remote), please set task attribute "
|
||||
f"'poll' (={task_poll}) to 0, and 'async' (={task_async}) to a value >2 and not greater than "
|
||||
f"'ansible_timeout' (={max_timeout}) (recommended).")
|
||||
|
||||
@staticmethod
|
||||
def msg_warning__no_async_is_no_rollback(task_poll, task_async, max_timeout):
|
||||
return (
|
||||
"Attempts to restore iptables state without rollback in case of mistake "
|
||||
"may lead the ansible controller to loose access to the hosts and never "
|
||||
"regain it before fixing firewall rules through a serial console, or any "
|
||||
f"other way except SSH. Please set task attribute 'poll' (={task_poll}) to 0, and "
|
||||
f"'async' (={task_async}) to a value >2 and not greater than 'ansible_timeout' (={max_timeout}) "
|
||||
"(recommended).")
|
||||
|
||||
@staticmethod
|
||||
def msg_warning__async_greater_than_timeout(task_poll, task_async, max_timeout):
|
||||
return (
|
||||
"You attempt to restore iptables state with rollback in case of mistake, "
|
||||
"but with settings that will lead this rollback to happen AFTER that the "
|
||||
"controller will reach its own timeout. Please set task attribute 'poll' "
|
||||
f"(={task_poll}) to 0, and 'async' (={task_async}) to a value >2 and not greater than "
|
||||
f"'ansible_timeout' (={max_timeout}) (recommended).")
|
||||
|
||||
def _async_result(self, async_status_args, task_vars, timeout):
|
||||
'''
|
||||
@@ -95,18 +102,18 @@ class ActionModule(ActionBase):
|
||||
if module_args.get('state', None) == 'restored':
|
||||
if not wrap_async:
|
||||
if not check_mode:
|
||||
display.warning(self.MSG_WARNING__NO_ASYNC_IS_NO_ROLLBACK % (
|
||||
display.warning(self.msg_error__async_and_poll_not_zero(
|
||||
task_poll,
|
||||
task_async,
|
||||
max_timeout))
|
||||
elif task_poll:
|
||||
raise AnsibleActionFail(self.MSG_ERROR__ASYNC_AND_POLL_NOT_ZERO % (
|
||||
raise AnsibleActionFail(self.msg_warning__no_async_is_no_rollback(
|
||||
task_poll,
|
||||
task_async,
|
||||
max_timeout))
|
||||
else:
|
||||
if task_async > max_timeout and not check_mode:
|
||||
display.warning(self.MSG_WARNING__ASYNC_GREATER_THAN_TIMEOUT % (
|
||||
display.warning(self.msg_warning__async_greater_than_timeout(
|
||||
task_poll,
|
||||
task_async,
|
||||
max_timeout))
|
||||
@@ -119,10 +126,10 @@ class ActionModule(ActionBase):
|
||||
# remote and local sides (if not the same, make the loop
|
||||
# longer on the controller); and set a backup file path.
|
||||
module_args['_timeout'] = task_async
|
||||
module_args['_back'] = '%s/iptables.state' % async_dir
|
||||
module_args['_back'] = f'{async_dir}/iptables.state'
|
||||
async_status_args = dict(mode='status')
|
||||
confirm_cmd = 'rm -f %s' % module_args['_back']
|
||||
starter_cmd = 'touch %s.starter' % module_args['_back']
|
||||
confirm_cmd = f"rm -f {module_args['_back']}"
|
||||
starter_cmd = f"touch {module_args['_back']}.starter"
|
||||
remaining_time = max(task_async, max_timeout)
|
||||
|
||||
# do work!
|
||||
|
||||
@@ -5,9 +5,8 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
from __future__ import annotations
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.errors import AnsibleError, AnsibleConnectionFailure
|
||||
from ansible.module_utils.common.text.converters import to_native, to_text
|
||||
@@ -18,6 +17,10 @@ from ansible.utils.display import Display
|
||||
display = Display()
|
||||
|
||||
|
||||
def fmt(mapping, key):
|
||||
return to_native(mapping[key]).strip()
|
||||
|
||||
|
||||
class TimedOutException(Exception):
|
||||
pass
|
||||
|
||||
@@ -84,31 +87,26 @@ class ActionModule(ActionBase):
|
||||
def get_distribution(self, task_vars):
|
||||
# FIXME: only execute the module if we don't already have the facts we need
|
||||
distribution = {}
|
||||
display.debug('{action}: running setup module to get distribution'.format(action=self._task.action))
|
||||
display.debug(f'{self._task.action}: running setup module to get distribution')
|
||||
module_output = self._execute_module(
|
||||
task_vars=task_vars,
|
||||
module_name='ansible.legacy.setup',
|
||||
module_args={'gather_subset': 'min'})
|
||||
try:
|
||||
if module_output.get('failed', False):
|
||||
raise AnsibleError('Failed to determine system distribution. {0}, {1}'.format(
|
||||
to_native(module_output['module_stdout']).strip(),
|
||||
to_native(module_output['module_stderr']).strip()))
|
||||
raise AnsibleError(f"Failed to determine system distribution. {fmt(module_output, 'module_stdout')}, {fmt(module_output, 'module_stderr')}")
|
||||
distribution['name'] = module_output['ansible_facts']['ansible_distribution'].lower()
|
||||
distribution['version'] = to_text(
|
||||
module_output['ansible_facts']['ansible_distribution_version'].split('.')[0])
|
||||
distribution['family'] = to_text(module_output['ansible_facts']['ansible_os_family'].lower())
|
||||
display.debug("{action}: distribution: {dist}".format(action=self._task.action, dist=distribution))
|
||||
display.debug(f"{self._task.action}: distribution: {distribution}")
|
||||
return distribution
|
||||
except KeyError as ke:
|
||||
raise AnsibleError('Failed to get distribution information. Missing "{0}" in output.'.format(ke.args[0]))
|
||||
raise AnsibleError(f'Failed to get distribution information. Missing "{ke.args[0]}" in output.')
|
||||
|
||||
def get_shutdown_command(self, task_vars, distribution):
|
||||
def find_command(command, find_search_paths):
|
||||
display.debug('{action}: running find module looking in {paths} to get path for "{command}"'.format(
|
||||
action=self._task.action,
|
||||
command=command,
|
||||
paths=find_search_paths))
|
||||
display.debug(f'{self._task.action}: running find module looking in {find_search_paths} to get path for "{command}"')
|
||||
find_result = self._execute_module(
|
||||
task_vars=task_vars,
|
||||
# prevent collection search by calling with ansible.legacy (still allows library/ override of find)
|
||||
@@ -130,42 +128,37 @@ class ActionModule(ActionBase):
|
||||
if is_string(search_paths):
|
||||
search_paths = [search_paths]
|
||||
|
||||
# Error if we didn't get a list
|
||||
err_msg = "'search_paths' must be a string or flat list of strings, got {0}"
|
||||
try:
|
||||
incorrect_type = any(not is_string(x) for x in search_paths)
|
||||
if not isinstance(search_paths, list) or incorrect_type:
|
||||
raise TypeError
|
||||
except TypeError:
|
||||
raise AnsibleError(err_msg.format(search_paths))
|
||||
# Error if we didn't get a list
|
||||
err_msg = f"'search_paths' must be a string or flat list of strings, got {search_paths}"
|
||||
raise AnsibleError(err_msg)
|
||||
|
||||
full_path = find_command(shutdown_bin, search_paths) # find the path to the shutdown command
|
||||
if not full_path: # if we could not find the shutdown command
|
||||
display.vvv('Unable to find command "{0}" in search paths: {1}, will attempt a shutdown using systemd '
|
||||
'directly.'.format(shutdown_bin, search_paths)) # tell the user we will try with systemd
|
||||
|
||||
# tell the user we will try with systemd
|
||||
display.vvv(f'Unable to find command "{shutdown_bin}" in search paths: {search_paths}, will attempt a shutdown using systemd directly.')
|
||||
systemctl_search_paths = ['/bin', '/usr/bin']
|
||||
full_path = find_command('systemctl', systemctl_search_paths) # find the path to the systemctl command
|
||||
if not full_path: # if we couldn't find systemctl
|
||||
raise AnsibleError(
|
||||
'Could not find command "{0}" in search paths: {1} or systemctl command in search paths: {2}, unable to shutdown.'.
|
||||
format(shutdown_bin, search_paths, systemctl_search_paths)) # we give up here
|
||||
f'Could not find command "{shutdown_bin}" in search paths: {search_paths} or systemctl'
|
||||
f' command in search paths: {systemctl_search_paths}, unable to shutdown.') # we give up here
|
||||
else:
|
||||
return "{0} poweroff".format(full_path[0]) # done, since we cannot use args with systemd shutdown
|
||||
return f"{full_path[0]} poweroff" # done, since we cannot use args with systemd shutdown
|
||||
|
||||
# systemd case taken care of, here we add args to the command
|
||||
args = self._get_value_from_facts('SHUTDOWN_COMMAND_ARGS', distribution, 'DEFAULT_SHUTDOWN_COMMAND_ARGS')
|
||||
# Convert seconds to minutes. If less that 60, set it to 0.
|
||||
delay_sec = self.delay
|
||||
shutdown_message = self._task.args.get('msg', self.DEFAULT_SHUTDOWN_MESSAGE)
|
||||
return '{0} {1}'. \
|
||||
format(
|
||||
full_path[0],
|
||||
args.format(
|
||||
delay_sec=delay_sec,
|
||||
delay_min=delay_sec // 60,
|
||||
message=shutdown_message
|
||||
)
|
||||
)
|
||||
|
||||
af = args.format(delay_sec=delay_sec, delay_min=delay_sec // 60, message=shutdown_message)
|
||||
return f'{full_path[0]} {af}'
|
||||
|
||||
def perform_shutdown(self, task_vars, distribution):
|
||||
result = {}
|
||||
@@ -174,9 +167,8 @@ class ActionModule(ActionBase):
|
||||
|
||||
self.cleanup(force=True)
|
||||
try:
|
||||
display.vvv("{action}: shutting down server...".format(action=self._task.action))
|
||||
display.debug("{action}: shutting down server with command '{command}'".
|
||||
format(action=self._task.action, command=shutdown_command_exec))
|
||||
display.vvv(f"{self._task.action}: shutting down server...")
|
||||
display.debug(f"{self._task.action}: shutting down server with command '{shutdown_command_exec}'")
|
||||
if self._play_context.check_mode:
|
||||
shutdown_result['rc'] = 0
|
||||
else:
|
||||
@@ -184,16 +176,13 @@ class ActionModule(ActionBase):
|
||||
except AnsibleConnectionFailure as e:
|
||||
# If the connection is closed too quickly due to the system being shutdown, carry on
|
||||
display.debug(
|
||||
'{action}: AnsibleConnectionFailure caught and handled: {error}'.format(action=self._task.action,
|
||||
error=to_text(e)))
|
||||
f'{self._task.action}: AnsibleConnectionFailure caught and handled: {e}')
|
||||
shutdown_result['rc'] = 0
|
||||
|
||||
if shutdown_result['rc'] != 0:
|
||||
result['failed'] = True
|
||||
result['shutdown'] = False
|
||||
result['msg'] = "Shutdown command failed. Error was {stdout}, {stderr}".format(
|
||||
stdout=to_native(shutdown_result['stdout'].strip()),
|
||||
stderr=to_native(shutdown_result['stderr'].strip()))
|
||||
result['msg'] = f"Shutdown command failed. Error was {fmt(shutdown_result, 'stdout')}, {fmt(shutdown_result, 'stderr')}"
|
||||
return result
|
||||
|
||||
result['failed'] = False
|
||||
@@ -206,7 +195,7 @@ class ActionModule(ActionBase):
|
||||
|
||||
# If running with local connection, fail so we don't shutdown ourself
|
||||
if self._connection.transport == 'local' and (not self._play_context.check_mode):
|
||||
msg = 'Running {0} with local connection would shutdown the control node.'.format(self._task.action)
|
||||
msg = f'Running {self._task.action} with local connection would shutdown the control node.'
|
||||
return {'changed': False, 'elapsed': 0, 'shutdown': False, 'failed': True, 'msg': msg}
|
||||
|
||||
if task_vars is None:
|
||||
|
||||
@@ -2,92 +2,88 @@
|
||||
# Copyright (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: doas
|
||||
short_description: Do As user
|
||||
DOCUMENTATION = r"""
|
||||
name: doas
|
||||
short_description: Do As user
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user using the C(doas) utility.
|
||||
author: Ansible Core Team
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task.
|
||||
type: string
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: doas_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_doas_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_DOAS_USER
|
||||
become_exe:
|
||||
description: C(doas) executable.
|
||||
type: string
|
||||
default: doas
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: doas_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_doas_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_DOAS_EXE
|
||||
become_flags:
|
||||
description: Options to pass to C(doas).
|
||||
type: string
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: doas_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_doas_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_DOAS_FLAGS
|
||||
become_pass:
|
||||
description: Password for C(doas) prompt.
|
||||
type: string
|
||||
required: false
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_doas_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_DOAS_PASS
|
||||
ini:
|
||||
- section: doas_become_plugin
|
||||
key: password
|
||||
prompt_l10n:
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the doas utility.
|
||||
author: Ansible Core Team
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task.
|
||||
type: string
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: doas_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_doas_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_DOAS_USER
|
||||
become_exe:
|
||||
description: Doas executable.
|
||||
type: string
|
||||
default: doas
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: doas_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_doas_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_DOAS_EXE
|
||||
become_flags:
|
||||
description: Options to pass to doas.
|
||||
type: string
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: doas_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_doas_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_DOAS_FLAGS
|
||||
become_pass:
|
||||
description: Password for doas prompt.
|
||||
type: string
|
||||
required: false
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_doas_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_DOAS_PASS
|
||||
ini:
|
||||
- section: doas_become_plugin
|
||||
key: password
|
||||
prompt_l10n:
|
||||
description:
|
||||
- List of localized strings to match for prompt detection.
|
||||
- If empty we will use the built in one.
|
||||
type: list
|
||||
elements: string
|
||||
default: []
|
||||
ini:
|
||||
- section: doas_become_plugin
|
||||
key: localized_prompts
|
||||
vars:
|
||||
- name: ansible_doas_prompt_l10n
|
||||
env:
|
||||
- name: ANSIBLE_DOAS_PROMPT_L10N
|
||||
notes:
|
||||
- This become plugin does not work when connection pipelining is enabled. With ansible-core 2.19+, using it automatically
|
||||
disables pipelining. On ansible-core 2.18 and before, pipelining must explicitly be disabled by the user.
|
||||
'''
|
||||
- List of localized strings to match for prompt detection.
|
||||
- If empty we will use the built in one.
|
||||
type: list
|
||||
elements: string
|
||||
default: []
|
||||
ini:
|
||||
- section: doas_become_plugin
|
||||
key: localized_prompts
|
||||
vars:
|
||||
- name: ansible_doas_prompt_l10n
|
||||
env:
|
||||
- name: ANSIBLE_DOAS_PROMPT_L10N
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
@@ -103,10 +99,6 @@ class BecomeModule(BecomeBase):
|
||||
fail = ('Permission denied',)
|
||||
missing = ('Authorization required',)
|
||||
|
||||
# See https://github.com/ansible-collections/community.general/issues/9977,
|
||||
# https://github.com/ansible/ansible/pull/78111
|
||||
pipelining = False
|
||||
|
||||
def check_password_prompt(self, b_output):
|
||||
''' checks if the expected password prompt exists in b_output '''
|
||||
|
||||
@@ -132,9 +124,9 @@ class BecomeModule(BecomeBase):
|
||||
flags += ' -n'
|
||||
|
||||
become_user = self.get_option('become_user')
|
||||
user = '-u %s' % (become_user) if become_user else ''
|
||||
user = f'-u {become_user}' if become_user else ''
|
||||
|
||||
success_cmd = self._build_success_command(cmd, shell, noexe=True)
|
||||
executable = getattr(shell, 'executable', shell.SHELL_FAMILY)
|
||||
|
||||
return '%s %s %s %s -c %s' % (become_exe, flags, user, executable, success_cmd)
|
||||
return f'{become_exe} {flags} {user} {executable} -c {success_cmd}'
|
||||
|
||||
@@ -2,75 +2,74 @@
|
||||
# Copyright (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: dzdo
|
||||
short_description: Centrify's Direct Authorize
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the dzdo utility.
|
||||
author: Ansible Core Team
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task.
|
||||
type: string
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: dzdo_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_dzdo_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_DZDO_USER
|
||||
become_exe:
|
||||
description: Dzdo executable.
|
||||
type: string
|
||||
default: dzdo
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: dzdo_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_dzdo_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_DZDO_EXE
|
||||
become_flags:
|
||||
description: Options to pass to dzdo.
|
||||
type: string
|
||||
default: -H -S -n
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: dzdo_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_dzdo_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_DZDO_FLAGS
|
||||
become_pass:
|
||||
description: Options to pass to dzdo.
|
||||
type: string
|
||||
required: false
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_dzdo_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_DZDO_PASS
|
||||
ini:
|
||||
- section: dzdo_become_plugin
|
||||
key: password
|
||||
'''
|
||||
DOCUMENTATION = r"""
|
||||
name: dzdo
|
||||
short_description: Centrify's Direct Authorize
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user using the C(dzdo) utility.
|
||||
author: Ansible Core Team
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task.
|
||||
type: string
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: dzdo_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_dzdo_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_DZDO_USER
|
||||
become_exe:
|
||||
description: C(dzdo) executable.
|
||||
type: string
|
||||
default: dzdo
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: dzdo_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_dzdo_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_DZDO_EXE
|
||||
become_flags:
|
||||
description: Options to pass to C(dzdo).
|
||||
type: string
|
||||
default: -H -S -n
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: dzdo_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_dzdo_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_DZDO_FLAGS
|
||||
become_pass:
|
||||
description: Options to pass to C(dzdo).
|
||||
type: string
|
||||
required: false
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_dzdo_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_DZDO_PASS
|
||||
ini:
|
||||
- section: dzdo_become_plugin
|
||||
key: password
|
||||
"""
|
||||
|
||||
from ansible.plugins.become import BecomeBase
|
||||
|
||||
@@ -92,10 +91,10 @@ class BecomeModule(BecomeBase):
|
||||
|
||||
flags = self.get_option('become_flags')
|
||||
if self.get_option('become_pass'):
|
||||
self.prompt = '[dzdo via ansible, key=%s] password:' % self._id
|
||||
flags = '%s -p "%s"' % (flags.replace('-n', ''), self.prompt)
|
||||
self.prompt = f'[dzdo via ansible, key={self._id}] password:'
|
||||
flags = f"{flags.replace('-n', '')} -p \"{self.prompt}\""
|
||||
|
||||
become_user = self.get_option('become_user')
|
||||
user = '-u %s' % (become_user) if become_user else ''
|
||||
user = f'-u {become_user}' if become_user else ''
|
||||
|
||||
return ' '.join([becomecmd, flags, user, self._build_success_command(cmd, shell)])
|
||||
return f"{becomecmd} {flags} {user} {self._build_success_command(cmd, shell)}"
|
||||
|
||||
@@ -2,90 +2,89 @@
|
||||
# Copyright (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: ksu
|
||||
short_description: Kerberos substitute user
|
||||
DOCUMENTATION = r"""
|
||||
name: ksu
|
||||
short_description: Kerberos substitute user
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user using the C(ksu) utility.
|
||||
author: Ansible Core Team
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task.
|
||||
type: string
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: ksu_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_ksu_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_KSU_USER
|
||||
required: true
|
||||
become_exe:
|
||||
description: C(ksu) executable.
|
||||
type: string
|
||||
default: ksu
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: ksu_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_ksu_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_KSU_EXE
|
||||
become_flags:
|
||||
description: Options to pass to C(ksu).
|
||||
type: string
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: ksu_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_ksu_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_KSU_FLAGS
|
||||
become_pass:
|
||||
description: C(ksu) password.
|
||||
type: string
|
||||
required: false
|
||||
vars:
|
||||
- name: ansible_ksu_pass
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_become_password
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_KSU_PASS
|
||||
ini:
|
||||
- section: ksu_become_plugin
|
||||
key: password
|
||||
prompt_l10n:
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the ksu utility.
|
||||
author: Ansible Core Team
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task.
|
||||
type: string
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: ksu_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_ksu_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_KSU_USER
|
||||
required: true
|
||||
become_exe:
|
||||
description: Su executable.
|
||||
type: string
|
||||
default: ksu
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: ksu_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_ksu_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_KSU_EXE
|
||||
become_flags:
|
||||
description: Options to pass to ksu.
|
||||
type: string
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: ksu_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_ksu_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_KSU_FLAGS
|
||||
become_pass:
|
||||
description: Ksu password.
|
||||
type: string
|
||||
required: false
|
||||
vars:
|
||||
- name: ansible_ksu_pass
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_become_password
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_KSU_PASS
|
||||
ini:
|
||||
- section: ksu_become_plugin
|
||||
key: password
|
||||
prompt_l10n:
|
||||
description:
|
||||
- List of localized strings to match for prompt detection.
|
||||
- If empty we will use the built in one.
|
||||
type: list
|
||||
elements: string
|
||||
default: []
|
||||
ini:
|
||||
- section: ksu_become_plugin
|
||||
key: localized_prompts
|
||||
vars:
|
||||
- name: ansible_ksu_prompt_l10n
|
||||
env:
|
||||
- name: ANSIBLE_KSU_PROMPT_L10N
|
||||
'''
|
||||
- List of localized strings to match for prompt detection.
|
||||
- If empty we will use the built in one.
|
||||
type: list
|
||||
elements: string
|
||||
default: []
|
||||
ini:
|
||||
- section: ksu_become_plugin
|
||||
key: localized_prompts
|
||||
vars:
|
||||
- name: ansible_ksu_prompt_l10n
|
||||
env:
|
||||
- name: ANSIBLE_KSU_PROMPT_L10N
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
@@ -124,4 +123,4 @@ class BecomeModule(BecomeBase):
|
||||
|
||||
flags = self.get_option('become_flags')
|
||||
user = self.get_option('become_user')
|
||||
return '%s %s %s -e %s ' % (exe, user, flags, self._build_success_command(cmd, shell))
|
||||
return f'{exe} {user} {flags} -e {self._build_success_command(cmd, shell)} '
|
||||
|
||||
@@ -2,96 +2,92 @@
|
||||
# Copyright (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: machinectl
|
||||
short_description: Systemd's machinectl privilege escalation
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the machinectl utility.
|
||||
author: Ansible Core Team
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task.
|
||||
type: string
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: machinectl_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_machinectl_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_MACHINECTL_USER
|
||||
become_exe:
|
||||
description: Machinectl executable.
|
||||
type: string
|
||||
default: machinectl
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: machinectl_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_machinectl_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_MACHINECTL_EXE
|
||||
become_flags:
|
||||
description: Options to pass to machinectl.
|
||||
type: string
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: machinectl_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_machinectl_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_MACHINECTL_FLAGS
|
||||
become_pass:
|
||||
description: Password for machinectl.
|
||||
type: string
|
||||
required: false
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_machinectl_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_MACHINECTL_PASS
|
||||
ini:
|
||||
- section: machinectl_become_plugin
|
||||
key: password
|
||||
notes:
|
||||
- When not using this plugin with user V(root), it only works correctly with a polkit rule which will alter
|
||||
the behaviour of machinectl. This rule must alter the prompt behaviour to ask directly for the user credentials,
|
||||
if the user is allowed to perform the action (take a look at the examples section).
|
||||
If such a rule is not present the plugin only work if it is used in context with the root user,
|
||||
because then no further prompt will be shown by machinectl.
|
||||
- This become plugin does not work when connection pipelining is enabled. With ansible-core 2.19+, using it automatically
|
||||
disables pipelining. On ansible-core 2.18 and before, pipelining must explicitly be disabled by the user.
|
||||
'''
|
||||
DOCUMENTATION = r"""
|
||||
name: machinectl
|
||||
short_description: Systemd's machinectl privilege escalation
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user using the C(machinectl) utility.
|
||||
author: Ansible Core Team
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task.
|
||||
type: string
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: machinectl_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_machinectl_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_MACHINECTL_USER
|
||||
become_exe:
|
||||
description: C(machinectl) executable.
|
||||
type: string
|
||||
default: machinectl
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: machinectl_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_machinectl_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_MACHINECTL_EXE
|
||||
become_flags:
|
||||
description: Options to pass to C(machinectl).
|
||||
type: string
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: machinectl_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_machinectl_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_MACHINECTL_FLAGS
|
||||
become_pass:
|
||||
description: Password for C(machinectl).
|
||||
type: string
|
||||
required: false
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_machinectl_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_MACHINECTL_PASS
|
||||
ini:
|
||||
- section: machinectl_become_plugin
|
||||
key: password
|
||||
notes:
|
||||
- When not using this plugin with user V(root), it only works correctly with a polkit rule which will alter the behaviour
|
||||
of machinectl. This rule must alter the prompt behaviour to ask directly for the user credentials, if the user is allowed
|
||||
to perform the action (take a look at the examples section). If such a rule is not present the plugin only work if it
|
||||
is used in context with the root user, because then no further prompt will be shown by machinectl.
|
||||
"""
|
||||
|
||||
EXAMPLES = r'''
|
||||
EXAMPLES = r"""
|
||||
# A polkit rule needed to use the module with a non-root user.
|
||||
# See the Notes section for details.
|
||||
/etc/polkit-1/rules.d/60-machinectl-fast-user-auth.rules: |
|
||||
/etc/polkit-1/rules.d/60-machinectl-fast-user-auth.rules: |-
|
||||
polkit.addRule(function(action, subject) {
|
||||
if(action.id == "org.freedesktop.machine1.host-shell" &&
|
||||
subject.isInGroup("wheel")) {
|
||||
return polkit.Result.AUTH_SELF_KEEP;
|
||||
}
|
||||
});
|
||||
'''
|
||||
"""
|
||||
|
||||
from re import compile as re_compile
|
||||
|
||||
@@ -111,10 +107,6 @@ class BecomeModule(BecomeBase):
|
||||
success = ('==== AUTHENTICATION COMPLETE ====',)
|
||||
require_tty = True # see https://github.com/ansible-collections/community.general/issues/6932
|
||||
|
||||
# See https://github.com/ansible/ansible/issues/81254,
|
||||
# https://github.com/ansible/ansible/pull/78111
|
||||
pipelining = False
|
||||
|
||||
@staticmethod
|
||||
def remove_ansi_codes(line):
|
||||
return ansi_color_codes.sub(b"", line)
|
||||
@@ -129,7 +121,7 @@ class BecomeModule(BecomeBase):
|
||||
|
||||
flags = self.get_option('become_flags')
|
||||
user = self.get_option('become_user')
|
||||
return '%s -q shell %s %s@ %s' % (become, flags, user, self._build_success_command(cmd, shell))
|
||||
return f'{become} -q shell {flags} {user}@ {self._build_success_command(cmd, shell)}'
|
||||
|
||||
def check_success(self, b_output):
|
||||
b_output = self.remove_ansi_codes(b_output)
|
||||
|
||||
@@ -2,87 +2,86 @@
|
||||
# Copyright (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: pbrun
|
||||
short_description: PowerBroker run
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the pbrun utility.
|
||||
author: Ansible Core Team
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task.
|
||||
type: string
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: pbrun_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_pbrun_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_PBRUN_USER
|
||||
become_exe:
|
||||
description: Sudo executable.
|
||||
type: string
|
||||
default: pbrun
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: pbrun_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_pbrun_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_PBRUN_EXE
|
||||
become_flags:
|
||||
description: Options to pass to pbrun.
|
||||
type: string
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: pbrun_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_pbrun_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_PBRUN_FLAGS
|
||||
become_pass:
|
||||
description: Password for pbrun.
|
||||
type: string
|
||||
required: false
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_pbrun_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_PBRUN_PASS
|
||||
ini:
|
||||
- section: pbrun_become_plugin
|
||||
key: password
|
||||
wrap_exe:
|
||||
description: Toggle to wrap the command pbrun calls in C(shell -c) or not.
|
||||
default: false
|
||||
type: bool
|
||||
ini:
|
||||
- section: pbrun_become_plugin
|
||||
key: wrap_execution
|
||||
vars:
|
||||
- name: ansible_pbrun_wrap_execution
|
||||
env:
|
||||
- name: ANSIBLE_PBRUN_WRAP_EXECUTION
|
||||
'''
|
||||
DOCUMENTATION = r"""
|
||||
name: pbrun
|
||||
short_description: PowerBroker run
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user using the C(pbrun) utility.
|
||||
author: Ansible Core Team
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task.
|
||||
type: string
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: pbrun_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_pbrun_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_PBRUN_USER
|
||||
become_exe:
|
||||
description: C(pbrun) executable.
|
||||
type: string
|
||||
default: pbrun
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: pbrun_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_pbrun_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_PBRUN_EXE
|
||||
become_flags:
|
||||
description: Options to pass to C(pbrun).
|
||||
type: string
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: pbrun_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_pbrun_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_PBRUN_FLAGS
|
||||
become_pass:
|
||||
description: Password for C(pbrun).
|
||||
type: string
|
||||
required: false
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_pbrun_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_PBRUN_PASS
|
||||
ini:
|
||||
- section: pbrun_become_plugin
|
||||
key: password
|
||||
wrap_exe:
|
||||
description: Toggle to wrap the command C(pbrun) calls in C(shell -c) or not.
|
||||
default: false
|
||||
type: bool
|
||||
ini:
|
||||
- section: pbrun_become_plugin
|
||||
key: wrap_execution
|
||||
vars:
|
||||
- name: ansible_pbrun_wrap_execution
|
||||
env:
|
||||
- name: ANSIBLE_PBRUN_WRAP_EXECUTION
|
||||
"""
|
||||
|
||||
from ansible.plugins.become import BecomeBase
|
||||
|
||||
@@ -103,7 +102,7 @@ class BecomeModule(BecomeBase):
|
||||
|
||||
flags = self.get_option('become_flags')
|
||||
become_user = self.get_option('become_user')
|
||||
user = '-u %s' % (become_user) if become_user else ''
|
||||
user = f'-u {become_user}' if become_user else ''
|
||||
noexe = not self.get_option('wrap_exe')
|
||||
|
||||
return ' '.join([become_exe, flags, user, self._build_success_command(cmd, shell, noexe=noexe)])
|
||||
return f"{become_exe} {flags} {user} {self._build_success_command(cmd, shell, noexe=noexe)}"
|
||||
|
||||
@@ -2,92 +2,91 @@
|
||||
# Copyright (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: pfexec
|
||||
short_description: profile based execution
|
||||
DOCUMENTATION = r"""
|
||||
name: pfexec
|
||||
short_description: profile based execution
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user using the C(pfexec) utility.
|
||||
author: Ansible Core Team
|
||||
options:
|
||||
become_user:
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the pfexec utility.
|
||||
author: Ansible Core Team
|
||||
options:
|
||||
become_user:
|
||||
description:
|
||||
- User you 'become' to execute the task.
|
||||
- This plugin ignores this setting as pfexec uses it's own C(exec_attr) to figure this out,
|
||||
but it is supplied here for Ansible to make decisions needed for the task execution, like file permissions.
|
||||
type: string
|
||||
default: root
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: pfexec_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_pfexec_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_PFEXEC_USER
|
||||
become_exe:
|
||||
description: Sudo executable.
|
||||
type: string
|
||||
default: pfexec
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: pfexec_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_pfexec_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_PFEXEC_EXE
|
||||
become_flags:
|
||||
description: Options to pass to pfexec.
|
||||
type: string
|
||||
default: -H -S -n
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: pfexec_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_pfexec_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_PFEXEC_FLAGS
|
||||
become_pass:
|
||||
description: pfexec password.
|
||||
type: string
|
||||
required: false
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_pfexec_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_PFEXEC_PASS
|
||||
ini:
|
||||
- section: pfexec_become_plugin
|
||||
key: password
|
||||
wrap_exe:
|
||||
description: Toggle to wrap the command pfexec calls in C(shell -c) or not.
|
||||
default: false
|
||||
type: bool
|
||||
ini:
|
||||
- section: pfexec_become_plugin
|
||||
key: wrap_execution
|
||||
vars:
|
||||
- name: ansible_pfexec_wrap_execution
|
||||
env:
|
||||
- name: ANSIBLE_PFEXEC_WRAP_EXECUTION
|
||||
notes:
|
||||
- This plugin ignores O(become_user) as pfexec uses its own C(exec_attr) to figure this out.
|
||||
'''
|
||||
- User you 'become' to execute the task.
|
||||
- This plugin ignores this setting as pfexec uses its own C(exec_attr) to figure this out, but it is supplied here for
|
||||
Ansible to make decisions needed for the task execution, like file permissions.
|
||||
type: string
|
||||
default: root
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: pfexec_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_pfexec_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_PFEXEC_USER
|
||||
become_exe:
|
||||
description: C(pfexec) executable.
|
||||
type: string
|
||||
default: pfexec
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: pfexec_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_pfexec_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_PFEXEC_EXE
|
||||
become_flags:
|
||||
description: Options to pass to C(pfexec).
|
||||
type: string
|
||||
default: -H -S -n
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: pfexec_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_pfexec_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_PFEXEC_FLAGS
|
||||
become_pass:
|
||||
description: C(pfexec) password.
|
||||
type: string
|
||||
required: false
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_pfexec_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_PFEXEC_PASS
|
||||
ini:
|
||||
- section: pfexec_become_plugin
|
||||
key: password
|
||||
wrap_exe:
|
||||
description: Toggle to wrap the command C(pfexec) calls in C(shell -c) or not.
|
||||
default: false
|
||||
type: bool
|
||||
ini:
|
||||
- section: pfexec_become_plugin
|
||||
key: wrap_execution
|
||||
vars:
|
||||
- name: ansible_pfexec_wrap_execution
|
||||
env:
|
||||
- name: ANSIBLE_PFEXEC_WRAP_EXECUTION
|
||||
notes:
|
||||
- This plugin ignores O(become_user) as pfexec uses its own C(exec_attr) to figure this out.
|
||||
"""
|
||||
|
||||
from ansible.plugins.become import BecomeBase
|
||||
|
||||
@@ -106,4 +105,4 @@ class BecomeModule(BecomeBase):
|
||||
|
||||
flags = self.get_option('become_flags')
|
||||
noexe = not self.get_option('wrap_exe')
|
||||
return '%s %s %s' % (exe, flags, self._build_success_command(cmd, shell, noexe=noexe))
|
||||
return f'{exe} {flags} {self._build_success_command(cmd, shell, noexe=noexe)}'
|
||||
|
||||
@@ -2,63 +2,62 @@
|
||||
# Copyright (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: pmrun
|
||||
short_description: Privilege Manager run
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the pmrun utility.
|
||||
author: Ansible Core Team
|
||||
options:
|
||||
become_exe:
|
||||
description: Sudo executable
|
||||
type: string
|
||||
default: pmrun
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: pmrun_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_pmrun_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_PMRUN_EXE
|
||||
become_flags:
|
||||
description: Options to pass to pmrun.
|
||||
type: string
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: pmrun_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_pmrun_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_PMRUN_FLAGS
|
||||
become_pass:
|
||||
description: pmrun password.
|
||||
type: string
|
||||
required: false
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_pmrun_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_PMRUN_PASS
|
||||
ini:
|
||||
- section: pmrun_become_plugin
|
||||
key: password
|
||||
notes:
|
||||
- This plugin ignores the become_user supplied and uses pmrun's own configuration to select the user.
|
||||
'''
|
||||
DOCUMENTATION = r"""
|
||||
name: pmrun
|
||||
short_description: Privilege Manager run
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user using the C(pmrun) utility.
|
||||
author: Ansible Core Team
|
||||
options:
|
||||
become_exe:
|
||||
description: C(pmrun) executable.
|
||||
type: string
|
||||
default: pmrun
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: pmrun_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_pmrun_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_PMRUN_EXE
|
||||
become_flags:
|
||||
description: Options to pass to C(pmrun).
|
||||
type: string
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: pmrun_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_pmrun_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_PMRUN_FLAGS
|
||||
become_pass:
|
||||
description: C(pmrun) password.
|
||||
type: string
|
||||
required: false
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_pmrun_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_PMRUN_PASS
|
||||
ini:
|
||||
- section: pmrun_become_plugin
|
||||
key: password
|
||||
notes:
|
||||
- This plugin ignores the C(become_user) supplied and uses C(pmrun)'s own configuration to select the user.
|
||||
"""
|
||||
|
||||
from ansible.plugins.become import BecomeBase
|
||||
from ansible.module_utils.six.moves import shlex_quote
|
||||
@@ -78,4 +77,4 @@ class BecomeModule(BecomeBase):
|
||||
become = self.get_option('become_exe')
|
||||
|
||||
flags = self.get_option('become_flags')
|
||||
return '%s %s %s' % (become, flags, shlex_quote(self._build_success_command(cmd, shell)))
|
||||
return f'{become} {flags} {shlex_quote(self._build_success_command(cmd, shell))}'
|
||||
|
||||
@@ -3,72 +3,71 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import annotations
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = """
|
||||
name: run0
|
||||
short_description: Systemd's run0
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the C(run0) utility.
|
||||
author:
|
||||
- Thomas Sjögren (@konstruktoid)
|
||||
version_added: '9.0.0'
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task.
|
||||
default: root
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: run0_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_run0_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_RUN0_USER
|
||||
type: string
|
||||
become_exe:
|
||||
description: The C(run0) executable.
|
||||
default: run0
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: run0_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_run0_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_RUN0_EXE
|
||||
type: string
|
||||
become_flags:
|
||||
description: Options to pass to run0.
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: run0_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_run0_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_RUN0_FLAGS
|
||||
type: string
|
||||
notes:
|
||||
- This plugin will only work when a polkit rule is in place.
|
||||
DOCUMENTATION = r"""
|
||||
name: run0
|
||||
short_description: Systemd's run0
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user using the C(run0) utility.
|
||||
author:
|
||||
- Thomas Sjögren (@konstruktoid)
|
||||
version_added: '9.0.0'
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task.
|
||||
default: root
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: run0_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_run0_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_RUN0_USER
|
||||
type: string
|
||||
become_exe:
|
||||
description: C(run0) executable.
|
||||
default: run0
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: run0_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_run0_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_RUN0_EXE
|
||||
type: string
|
||||
become_flags:
|
||||
description: Options to pass to C(run0).
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: run0_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_run0_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_RUN0_FLAGS
|
||||
type: string
|
||||
notes:
|
||||
- This plugin will only work when a C(polkit) rule is in place.
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
# An example polkit rule that allows the user 'ansible' in the 'wheel' group
|
||||
# to execute commands using run0 without authentication.
|
||||
/etc/polkit-1/rules.d/60-run0-fast-user-auth.rules: |
|
||||
/etc/polkit-1/rules.d/60-run0-fast-user-auth.rules: |-
|
||||
polkit.addRule(function(action, subject) {
|
||||
if(action.id == "org.freedesktop.systemd1.manage-units" &&
|
||||
subject.isInGroup("wheel") &&
|
||||
|
||||
@@ -2,76 +2,75 @@
|
||||
# Copyright (c) 2018, Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = '''
|
||||
name: sesu
|
||||
short_description: CA Privileged Access Manager
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user via the sesu utility.
|
||||
author: ansible (@nekonyuu)
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task.
|
||||
type: string
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: sesu_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_sesu_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_SESU_USER
|
||||
become_exe:
|
||||
description: sesu executable.
|
||||
type: string
|
||||
default: sesu
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: sesu_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_sesu_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_SESU_EXE
|
||||
become_flags:
|
||||
description: Options to pass to sesu.
|
||||
type: string
|
||||
default: -H -S -n
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: sesu_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_sesu_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_SESU_FLAGS
|
||||
become_pass:
|
||||
description: Password to pass to sesu.
|
||||
type: string
|
||||
required: false
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_sesu_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_SESU_PASS
|
||||
ini:
|
||||
- section: sesu_become_plugin
|
||||
key: password
|
||||
'''
|
||||
DOCUMENTATION = r"""
|
||||
name: sesu
|
||||
short_description: CA Privileged Access Manager
|
||||
description:
|
||||
- This become plugins allows your remote/login user to execute commands as another user using the C(sesu) utility.
|
||||
author: ansible (@nekonyuu)
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task.
|
||||
type: string
|
||||
default: ''
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: sesu_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_sesu_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_SESU_USER
|
||||
become_exe:
|
||||
description: C(sesu) executable.
|
||||
type: string
|
||||
default: sesu
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_exe
|
||||
- section: sesu_become_plugin
|
||||
key: executable
|
||||
vars:
|
||||
- name: ansible_become_exe
|
||||
- name: ansible_sesu_exe
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_EXE
|
||||
- name: ANSIBLE_SESU_EXE
|
||||
become_flags:
|
||||
description: Options to pass to C(sesu).
|
||||
type: string
|
||||
default: -H -S -n
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: sesu_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_sesu_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_SESU_FLAGS
|
||||
become_pass:
|
||||
description: Password to pass to C(sesu).
|
||||
type: string
|
||||
required: false
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_sesu_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_SESU_PASS
|
||||
ini:
|
||||
- section: sesu_become_plugin
|
||||
key: password
|
||||
"""
|
||||
|
||||
from ansible.plugins.become import BecomeBase
|
||||
|
||||
@@ -93,4 +92,4 @@ class BecomeModule(BecomeBase):
|
||||
|
||||
flags = self.get_option('become_flags')
|
||||
user = self.get_option('become_user')
|
||||
return '%s %s %s -c %s' % (become, flags, user, self._build_success_command(cmd, shell))
|
||||
return f'{become} {flags} {user} -c {self._build_success_command(cmd, shell)}'
|
||||
|
||||
@@ -2,77 +2,77 @@
|
||||
# Copyright (c) 2021, Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = """
|
||||
name: sudosu
|
||||
short_description: Run tasks using sudo su -
|
||||
DOCUMENTATION = r"""
|
||||
name: sudosu
|
||||
short_description: Run tasks using sudo su -
|
||||
description:
|
||||
- This become plugin allows your remote/login user to execute commands as another user using the C(sudo) and C(su) utilities
|
||||
combined.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: 2.4.0
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task.
|
||||
type: string
|
||||
default: root
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: sudo_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_sudo_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_SUDO_USER
|
||||
become_flags:
|
||||
description: Options to pass to C(sudo).
|
||||
type: string
|
||||
default: -H -S -n
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: sudo_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_sudo_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_SUDO_FLAGS
|
||||
become_pass:
|
||||
description: Password to pass to C(sudo).
|
||||
type: string
|
||||
required: false
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_sudo_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_SUDO_PASS
|
||||
ini:
|
||||
- section: sudo_become_plugin
|
||||
key: password
|
||||
alt_method:
|
||||
description:
|
||||
- This become plugin allows your remote/login user to execute commands as another user via the C(sudo) and C(su) utilities combined.
|
||||
author:
|
||||
- Dag Wieers (@dagwieers)
|
||||
version_added: 2.4.0
|
||||
options:
|
||||
become_user:
|
||||
description: User you 'become' to execute the task.
|
||||
type: string
|
||||
default: root
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_user
|
||||
- section: sudo_become_plugin
|
||||
key: user
|
||||
vars:
|
||||
- name: ansible_become_user
|
||||
- name: ansible_sudo_user
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_USER
|
||||
- name: ANSIBLE_SUDO_USER
|
||||
become_flags:
|
||||
description: Options to pass to C(sudo).
|
||||
type: string
|
||||
default: -H -S -n
|
||||
ini:
|
||||
- section: privilege_escalation
|
||||
key: become_flags
|
||||
- section: sudo_become_plugin
|
||||
key: flags
|
||||
vars:
|
||||
- name: ansible_become_flags
|
||||
- name: ansible_sudo_flags
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_FLAGS
|
||||
- name: ANSIBLE_SUDO_FLAGS
|
||||
become_pass:
|
||||
description: Password to pass to C(sudo).
|
||||
type: string
|
||||
required: false
|
||||
vars:
|
||||
- name: ansible_become_password
|
||||
- name: ansible_become_pass
|
||||
- name: ansible_sudo_pass
|
||||
env:
|
||||
- name: ANSIBLE_BECOME_PASS
|
||||
- name: ANSIBLE_SUDO_PASS
|
||||
ini:
|
||||
- section: sudo_become_plugin
|
||||
key: password
|
||||
alt_method:
|
||||
description:
|
||||
- Whether to use an alternative method to call C(su). Instead of running C(su -l user /path/to/shell -c command),
|
||||
it runs C(su -l user -c command).
|
||||
- Use this when the default one is not working on your system.
|
||||
required: false
|
||||
type: boolean
|
||||
ini:
|
||||
- section: community.general.sudosu
|
||||
key: alternative_method
|
||||
vars:
|
||||
- name: ansible_sudosu_alt_method
|
||||
env:
|
||||
- name: ANSIBLE_SUDOSU_ALT_METHOD
|
||||
version_added: 9.2.0
|
||||
- Whether to use an alternative method to call C(su). Instead of running C(su -l user /path/to/shell -c command), it
|
||||
runs C(su -l user -c command).
|
||||
- Use this when the default one is not working on your system.
|
||||
required: false
|
||||
type: boolean
|
||||
ini:
|
||||
- section: community.general.sudosu
|
||||
key: alternative_method
|
||||
vars:
|
||||
- name: ansible_sudosu_alt_method
|
||||
env:
|
||||
- name: ANSIBLE_SUDOSU_ALT_METHOD
|
||||
version_added: 9.2.0
|
||||
"""
|
||||
|
||||
|
||||
@@ -98,16 +98,16 @@ class BecomeModule(BecomeBase):
|
||||
flags = self.get_option('become_flags') or ''
|
||||
prompt = ''
|
||||
if self.get_option('become_pass'):
|
||||
self.prompt = '[sudo via ansible, key=%s] password:' % self._id
|
||||
self.prompt = f'[sudo via ansible, key={self._id}] password:'
|
||||
if flags: # this could be simplified, but kept as is for now for backwards string matching
|
||||
flags = flags.replace('-n', '')
|
||||
prompt = '-p "%s"' % (self.prompt)
|
||||
prompt = f'-p "{self.prompt}"'
|
||||
|
||||
user = self.get_option('become_user') or ''
|
||||
if user:
|
||||
user = '%s' % (user)
|
||||
user = f'{user}'
|
||||
|
||||
if self.get_option('alt_method'):
|
||||
return ' '.join([becomecmd, flags, prompt, "su -l", user, "-c", self._build_success_command(cmd, shell, True)])
|
||||
return f"{becomecmd} {flags} {prompt} su -l {user} -c {self._build_success_command(cmd, shell, True)}"
|
||||
else:
|
||||
return ' '.join([becomecmd, flags, prompt, 'su -l', user, self._build_success_command(cmd, shell)])
|
||||
return f"{becomecmd} {flags} {prompt} su -l {user} {self._build_success_command(cmd, shell)}"
|
||||
|
||||
5
plugins/cache/memcached.py
vendored
5
plugins/cache/memcached.py
vendored
@@ -4,8 +4,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Unknown (!UNKNOWN)
|
||||
@@ -191,7 +190,7 @@ class CacheModule(BaseCacheModule):
|
||||
self._keys = CacheModuleKeys(self._db, self._db.get(CacheModuleKeys.PREFIX) or [])
|
||||
|
||||
def _make_key(self, key):
|
||||
return "{0}{1}".format(self._prefix, key)
|
||||
return f"{self._prefix}{key}"
|
||||
|
||||
def _expire_keys(self):
|
||||
if self._timeout > 0:
|
||||
|
||||
4
plugins/cache/pickle.py
vendored
4
plugins/cache/pickle.py
vendored
@@ -5,8 +5,7 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
name: pickle
|
||||
@@ -57,7 +56,6 @@ class CacheModule(BaseFileCacheModule):
|
||||
"""
|
||||
A caching module backed by pickle files.
|
||||
"""
|
||||
_persistent = False # prevent unnecessary JSON serialization and key munging
|
||||
|
||||
def _load(self, filepath):
|
||||
# Pickle is a binary format
|
||||
|
||||
10
plugins/cache/redis.py
vendored
10
plugins/cache/redis.py
vendored
@@ -3,8 +3,7 @@
|
||||
# Copyright (c) 2017 Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Unknown (!UNKNOWN)
|
||||
@@ -73,7 +72,6 @@ import time
|
||||
import json
|
||||
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.module_utils.common.text.converters import to_native
|
||||
from ansible.parsing.ajson import AnsibleJSONEncoder, AnsibleJSONDecoder
|
||||
from ansible.plugins.cache import BaseCacheModule
|
||||
from ansible.utils.display import Display
|
||||
@@ -131,7 +129,7 @@ class CacheModule(BaseCacheModule):
|
||||
connection = self._parse_connection(self.re_url_conn, uri)
|
||||
self._db = StrictRedis(*connection, **kw)
|
||||
|
||||
display.vv('Redis connection: %s' % self._db)
|
||||
display.vv(f'Redis connection: {self._db}')
|
||||
|
||||
@staticmethod
|
||||
def _parse_connection(re_patt, uri):
|
||||
@@ -164,12 +162,12 @@ class CacheModule(BaseCacheModule):
|
||||
pass # password is optional
|
||||
|
||||
sentinels = [self._parse_connection(self.re_sent_conn, shost) for shost in connections]
|
||||
display.vv('\nUsing redis sentinels: %s' % sentinels)
|
||||
display.vv(f'\nUsing redis sentinels: {sentinels}')
|
||||
scon = Sentinel(sentinels, **kw)
|
||||
try:
|
||||
return scon.master_for(self._sentinel_service_name, socket_timeout=0.2)
|
||||
except Exception as exc:
|
||||
raise AnsibleError('Could not connect to redis sentinel: %s' % to_native(exc))
|
||||
raise AnsibleError(f'Could not connect to redis sentinel: {exc}')
|
||||
|
||||
def _make_key(self, key):
|
||||
return self._prefix + key
|
||||
|
||||
10
plugins/cache/yaml.py
vendored
10
plugins/cache/yaml.py
vendored
@@ -5,8 +5,7 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
name: yaml
|
||||
@@ -45,7 +44,8 @@ options:
|
||||
# TODO: determine whether it is OK to change to: type: float
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
import codecs
|
||||
|
||||
import yaml
|
||||
|
||||
@@ -60,9 +60,9 @@ class CacheModule(BaseFileCacheModule):
|
||||
"""
|
||||
|
||||
def _load(self, filepath):
|
||||
with open(os.path.abspath(filepath), 'r', encoding='utf-8') as f:
|
||||
with codecs.open(filepath, 'r', encoding='utf-8') as f:
|
||||
return AnsibleLoader(f).get_single_data()
|
||||
|
||||
def _dump(self, value, filepath):
|
||||
with open(os.path.abspath(filepath), 'w', encoding='utf-8') as f:
|
||||
with codecs.open(filepath, 'w', encoding='utf-8') as f:
|
||||
yaml.dump(value, f, Dumper=AnsibleDumper, default_flow_style=False)
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Unknown (!UNKNOWN)
|
||||
@@ -115,7 +114,7 @@ class CallbackModule(CallbackBase):
|
||||
max_results = int(f.read().strip()) / 1024 / 1024
|
||||
|
||||
self._display.banner('CGROUP MEMORY RECAP')
|
||||
self._display.display('Execution Maximum: %0.2fMB\n\n' % max_results)
|
||||
self._display.display(f'Execution Maximum: {max_results:0.2f}MB\n\n')
|
||||
|
||||
for task, memory in self.task_results:
|
||||
self._display.display('%s (%s): %0.2fMB' % (task.get_name(), task._uuid, memory))
|
||||
self._display.display(f'{task.get_name()} ({task._uuid}): {memory:0.2f}MB')
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Unknown (!UNKNOWN)
|
||||
@@ -38,15 +37,15 @@ class CallbackModule(CallbackBase):
|
||||
self.play = None
|
||||
|
||||
def v2_on_any(self, *args, **kwargs):
|
||||
self._display.display("--- play: {0} task: {1} ---".format(getattr(self.play, 'name', None), self.task))
|
||||
self._display.display(f"--- play: {getattr(self.play, 'name', None)} task: {self.task} ---")
|
||||
|
||||
self._display.display(" --- ARGS ")
|
||||
for i, a in enumerate(args):
|
||||
self._display.display(' %s: %s' % (i, a))
|
||||
self._display.display(f' {i}: {a}')
|
||||
|
||||
self._display.display(" --- KWARGS ")
|
||||
for k in kwargs:
|
||||
self._display.display(' %s: %s' % (k, kwargs[k]))
|
||||
self._display.display(f' {k}: {kwargs[k]}')
|
||||
|
||||
def v2_playbook_on_play_start(self, play):
|
||||
self.play = play
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
Counter enabled Ansible callback plugin (See DOCUMENTATION for more information)
|
||||
'''
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Unknown (!UNKNOWN)
|
||||
@@ -71,7 +70,7 @@ class CallbackModule(CallbackBase):
|
||||
if not name:
|
||||
msg = u"play"
|
||||
else:
|
||||
msg = u"PLAY [%s]" % name
|
||||
msg = f"PLAY [{name}]"
|
||||
|
||||
self._play = play
|
||||
|
||||
@@ -91,25 +90,17 @@ class CallbackModule(CallbackBase):
|
||||
for host in hosts:
|
||||
stat = stats.summarize(host)
|
||||
|
||||
self._display.display(u"%s : %s %s %s %s %s %s" % (
|
||||
hostcolor(host, stat),
|
||||
colorize(u'ok', stat['ok'], C.COLOR_OK),
|
||||
colorize(u'changed', stat['changed'], C.COLOR_CHANGED),
|
||||
colorize(u'unreachable', stat['unreachable'], C.COLOR_UNREACHABLE),
|
||||
colorize(u'failed', stat['failures'], C.COLOR_ERROR),
|
||||
colorize(u'rescued', stat['rescued'], C.COLOR_OK),
|
||||
colorize(u'ignored', stat['ignored'], C.COLOR_WARN)),
|
||||
self._display.display(
|
||||
f"{hostcolor(host, stat)} : {colorize('ok', stat['ok'], C.COLOR_OK)} {colorize('changed', stat['changed'], C.COLOR_CHANGED)} "
|
||||
f"{colorize('unreachable', stat['unreachable'], C.COLOR_UNREACHABLE)} {colorize('failed', stat['failures'], C.COLOR_ERROR)} "
|
||||
f"{colorize('rescued', stat['rescued'], C.COLOR_OK)} {colorize('ignored', stat['ignored'], C.COLOR_WARN)}",
|
||||
screen_only=True
|
||||
)
|
||||
|
||||
self._display.display(u"%s : %s %s %s %s %s %s" % (
|
||||
hostcolor(host, stat, False),
|
||||
colorize(u'ok', stat['ok'], None),
|
||||
colorize(u'changed', stat['changed'], None),
|
||||
colorize(u'unreachable', stat['unreachable'], None),
|
||||
colorize(u'failed', stat['failures'], None),
|
||||
colorize(u'rescued', stat['rescued'], None),
|
||||
colorize(u'ignored', stat['ignored'], None)),
|
||||
self._display.display(
|
||||
f"{hostcolor(host, stat, False)} : {colorize('ok', stat['ok'], None)} {colorize('changed', stat['changed'], None)} "
|
||||
f"{colorize('unreachable', stat['unreachable'], None)} {colorize('failed', stat['failures'], None)} "
|
||||
f"{colorize('rescued', stat['rescued'], None)} {colorize('ignored', stat['ignored'], None)}",
|
||||
log_only=True
|
||||
)
|
||||
|
||||
@@ -124,12 +115,14 @@ class CallbackModule(CallbackBase):
|
||||
for k in sorted(stats.custom.keys()):
|
||||
if k == '_run':
|
||||
continue
|
||||
self._display.display('\t%s: %s' % (k, self._dump_results(stats.custom[k], indent=1).replace('\n', '')))
|
||||
_custom_stats = self._dump_results(stats.custom[k], indent=1).replace('\n', '')
|
||||
self._display.display(f'\t{k}: {_custom_stats}')
|
||||
|
||||
# print per run custom stats
|
||||
if '_run' in stats.custom:
|
||||
self._display.display("", screen_only=True)
|
||||
self._display.display('\tRUN: %s' % self._dump_results(stats.custom['_run'], indent=1).replace('\n', ''))
|
||||
_custom_stats_run = self._dump_results(stats.custom['_run'], indent=1).replace('\n', '')
|
||||
self._display.display(f'\tRUN: {_custom_stats_run}')
|
||||
self._display.display("", screen_only=True)
|
||||
|
||||
def v2_playbook_on_task_start(self, task, is_conditional):
|
||||
@@ -143,13 +136,13 @@ class CallbackModule(CallbackBase):
|
||||
# that they can secure this if they feel that their stdout is insecure
|
||||
# (shoulder surfing, logging stdout straight to a file, etc).
|
||||
if not task.no_log and C.DISPLAY_ARGS_TO_STDOUT:
|
||||
args = ', '.join(('%s=%s' % a for a in task.args.items()))
|
||||
args = ' %s' % args
|
||||
self._display.banner("TASK %d/%d [%s%s]" % (self._task_counter, self._task_total, task.get_name().strip(), args))
|
||||
args = ', '.join(('{k}={v}' for k, v in task.args.items()))
|
||||
args = f' {args}'
|
||||
self._display.banner(f"TASK {self._task_counter}/{self._task_total} [{task.get_name().strip()}{args}]")
|
||||
if self._display.verbosity >= 2:
|
||||
path = task.get_path()
|
||||
if path:
|
||||
self._display.display("task path: %s" % path, color=C.COLOR_DEBUG)
|
||||
self._display.display(f"task path: {path}", color=C.COLOR_DEBUG)
|
||||
self._host_counter = self._previous_batch_total
|
||||
self._task_counter += 1
|
||||
|
||||
@@ -166,15 +159,15 @@ class CallbackModule(CallbackBase):
|
||||
return
|
||||
elif result._result.get('changed', False):
|
||||
if delegated_vars:
|
||||
msg = "changed: %d/%d [%s -> %s]" % (self._host_counter, self._host_total, result._host.get_name(), delegated_vars['ansible_host'])
|
||||
msg = f"changed: {self._host_counter}/{self._host_total} [{result._host.get_name()} -> {delegated_vars['ansible_host']}]"
|
||||
else:
|
||||
msg = "changed: %d/%d [%s]" % (self._host_counter, self._host_total, result._host.get_name())
|
||||
msg = f"changed: {self._host_counter}/{self._host_total} [{result._host.get_name()}]"
|
||||
color = C.COLOR_CHANGED
|
||||
else:
|
||||
if delegated_vars:
|
||||
msg = "ok: %d/%d [%s -> %s]" % (self._host_counter, self._host_total, result._host.get_name(), delegated_vars['ansible_host'])
|
||||
msg = f"ok: {self._host_counter}/{self._host_total} [{result._host.get_name()} -> {delegated_vars['ansible_host']}]"
|
||||
else:
|
||||
msg = "ok: %d/%d [%s]" % (self._host_counter, self._host_total, result._host.get_name())
|
||||
msg = f"ok: {self._host_counter}/{self._host_total} [{result._host.get_name()}]"
|
||||
color = C.COLOR_OK
|
||||
|
||||
self._handle_warnings(result._result)
|
||||
@@ -185,7 +178,7 @@ class CallbackModule(CallbackBase):
|
||||
self._clean_results(result._result, result._task.action)
|
||||
|
||||
if self._run_is_verbose(result):
|
||||
msg += " => %s" % (self._dump_results(result._result),)
|
||||
msg += f" => {self._dump_results(result._result)}"
|
||||
self._display.display(msg, color=color)
|
||||
|
||||
def v2_runner_on_failed(self, result, ignore_errors=False):
|
||||
@@ -206,14 +199,16 @@ class CallbackModule(CallbackBase):
|
||||
|
||||
else:
|
||||
if delegated_vars:
|
||||
self._display.display("fatal: %d/%d [%s -> %s]: FAILED! => %s" % (self._host_counter, self._host_total,
|
||||
result._host.get_name(), delegated_vars['ansible_host'],
|
||||
self._dump_results(result._result)),
|
||||
color=C.COLOR_ERROR)
|
||||
self._display.display(
|
||||
f"fatal: {self._host_counter}/{self._host_total} [{result._host.get_name()} -> "
|
||||
f"{delegated_vars['ansible_host']}]: FAILED! => {self._dump_results(result._result)}",
|
||||
color=C.COLOR_ERROR
|
||||
)
|
||||
else:
|
||||
self._display.display("fatal: %d/%d [%s]: FAILED! => %s" % (self._host_counter, self._host_total,
|
||||
result._host.get_name(), self._dump_results(result._result)),
|
||||
color=C.COLOR_ERROR)
|
||||
self._display.display(
|
||||
f"fatal: {self._host_counter}/{self._host_total} [{result._host.get_name()}]: FAILED! => {self._dump_results(result._result)}",
|
||||
color=C.COLOR_ERROR
|
||||
)
|
||||
|
||||
if ignore_errors:
|
||||
self._display.display("...ignoring", color=C.COLOR_SKIP)
|
||||
@@ -231,9 +226,9 @@ class CallbackModule(CallbackBase):
|
||||
if result._task.loop and 'results' in result._result:
|
||||
self._process_items(result)
|
||||
else:
|
||||
msg = "skipping: %d/%d [%s]" % (self._host_counter, self._host_total, result._host.get_name())
|
||||
msg = f"skipping: {self._host_counter}/{self._host_total} [{result._host.get_name()}]"
|
||||
if self._run_is_verbose(result):
|
||||
msg += " => %s" % self._dump_results(result._result)
|
||||
msg += f" => {self._dump_results(result._result)}"
|
||||
self._display.display(msg, color=C.COLOR_SKIP)
|
||||
|
||||
def v2_runner_on_unreachable(self, result):
|
||||
@@ -244,11 +239,13 @@ class CallbackModule(CallbackBase):
|
||||
|
||||
delegated_vars = result._result.get('_ansible_delegated_vars', None)
|
||||
if delegated_vars:
|
||||
self._display.display("fatal: %d/%d [%s -> %s]: UNREACHABLE! => %s" % (self._host_counter, self._host_total,
|
||||
result._host.get_name(), delegated_vars['ansible_host'],
|
||||
self._dump_results(result._result)),
|
||||
color=C.COLOR_UNREACHABLE)
|
||||
self._display.display(
|
||||
f"fatal: {self._host_counter}/{self._host_total} [{result._host.get_name()} -> "
|
||||
f"{delegated_vars['ansible_host']}]: UNREACHABLE! => {self._dump_results(result._result)}",
|
||||
color=C.COLOR_UNREACHABLE
|
||||
)
|
||||
else:
|
||||
self._display.display("fatal: %d/%d [%s]: UNREACHABLE! => %s" % (self._host_counter, self._host_total,
|
||||
result._host.get_name(), self._dump_results(result._result)),
|
||||
color=C.COLOR_UNREACHABLE)
|
||||
self._display.display(
|
||||
f"fatal: {self._host_counter}/{self._host_total} [{result._host.get_name()}]: UNREACHABLE! => {self._dump_results(result._result)}",
|
||||
color=C.COLOR_UNREACHABLE
|
||||
)
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
name: default_without_diff
|
||||
@@ -29,7 +28,7 @@ ansible_config: |
|
||||
stdout_callback = community.general.default_without_diff
|
||||
|
||||
# Enable callback with environment variables:
|
||||
environment_variable: |
|
||||
environment_variable: |-
|
||||
ANSIBLE_STDOUT_CALLBACK=community.general.default_without_diff
|
||||
"""
|
||||
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
name: dense
|
||||
@@ -195,7 +194,7 @@ class CallbackModule(CallbackModule_default):
|
||||
self.disabled = True
|
||||
|
||||
def __del__(self):
|
||||
sys.stdout.write(vt100.restore + vt100.reset + '\n' + vt100.save + vt100.clearline)
|
||||
sys.stdout.write(f"{vt100.restore}{vt100.reset}\n{vt100.save}{vt100.clearline}")
|
||||
|
||||
def _add_host(self, result, status):
|
||||
name = result._host.get_name()
|
||||
@@ -243,7 +242,7 @@ class CallbackModule(CallbackModule_default):
|
||||
|
||||
def _handle_exceptions(self, result):
|
||||
if 'exception' in result:
|
||||
# Remove the exception from the result so it's not shown every time
|
||||
# Remove the exception from the result so it is not shown every time
|
||||
del result['exception']
|
||||
|
||||
if self._display.verbosity == 1:
|
||||
@@ -252,7 +251,7 @@ class CallbackModule(CallbackModule_default):
|
||||
def _display_progress(self, result=None):
|
||||
# Always rewrite the complete line
|
||||
sys.stdout.write(vt100.restore + vt100.reset + vt100.clearline + vt100.nolinewrap + vt100.underline)
|
||||
sys.stdout.write('%s %d:' % (self.type, self.count[self.type]))
|
||||
sys.stdout.write(f'{self.type} {self.count[self.type]}:')
|
||||
sys.stdout.write(vt100.reset)
|
||||
sys.stdout.flush()
|
||||
|
||||
@@ -260,7 +259,7 @@ class CallbackModule(CallbackModule_default):
|
||||
for name in self.hosts:
|
||||
sys.stdout.write(' ')
|
||||
if self.hosts[name].get('delegate', None):
|
||||
sys.stdout.write(self.hosts[name]['delegate'] + '>')
|
||||
sys.stdout.write(f"{self.hosts[name]['delegate']}>")
|
||||
sys.stdout.write(colors[self.hosts[name]['state']] + name + vt100.reset)
|
||||
sys.stdout.flush()
|
||||
|
||||
@@ -274,8 +273,8 @@ class CallbackModule(CallbackModule_default):
|
||||
if not self.shown_title:
|
||||
self.shown_title = True
|
||||
sys.stdout.write(vt100.restore + vt100.reset + vt100.clearline + vt100.underline)
|
||||
sys.stdout.write('%s %d: %s' % (self.type, self.count[self.type], self.task.get_name().strip()))
|
||||
sys.stdout.write(vt100.restore + vt100.reset + '\n' + vt100.save + vt100.clearline)
|
||||
sys.stdout.write(f'{self.type} {self.count[self.type]}: {self.task.get_name().strip()}')
|
||||
sys.stdout.write(f"{vt100.restore}{vt100.reset}\n{vt100.save}{vt100.clearline}")
|
||||
sys.stdout.flush()
|
||||
else:
|
||||
sys.stdout.write(vt100.restore + vt100.reset + vt100.clearline)
|
||||
@@ -284,7 +283,7 @@ class CallbackModule(CallbackModule_default):
|
||||
def _display_results(self, result, status):
|
||||
# Leave the previous task on screen (as it has changes/errors)
|
||||
if self._display.verbosity == 0 and self.keep:
|
||||
sys.stdout.write(vt100.restore + vt100.reset + '\n' + vt100.save + vt100.clearline)
|
||||
sys.stdout.write(f"{vt100.restore}{vt100.reset}\n{vt100.save}{vt100.clearline}")
|
||||
else:
|
||||
sys.stdout.write(vt100.restore + vt100.reset + vt100.clearline)
|
||||
self.keep = False
|
||||
@@ -309,15 +308,15 @@ class CallbackModule(CallbackModule_default):
|
||||
if result._task.loop and 'results' in result._result:
|
||||
self._process_items(result)
|
||||
else:
|
||||
sys.stdout.write(colors[status] + status + ': ')
|
||||
sys.stdout.write(f"{colors[status] + status}: ")
|
||||
|
||||
delegated_vars = result._result.get('_ansible_delegated_vars', None)
|
||||
if delegated_vars:
|
||||
sys.stdout.write(vt100.reset + result._host.get_name() + '>' + colors[status] + delegated_vars['ansible_host'])
|
||||
sys.stdout.write(f"{vt100.reset + result._host.get_name()}>{colors[status]}{delegated_vars['ansible_host']}")
|
||||
else:
|
||||
sys.stdout.write(result._host.get_name())
|
||||
|
||||
sys.stdout.write(': ' + dump + '\n')
|
||||
sys.stdout.write(f": {dump}\n")
|
||||
sys.stdout.write(vt100.reset + vt100.save + vt100.clearline)
|
||||
sys.stdout.flush()
|
||||
|
||||
@@ -327,7 +326,7 @@ class CallbackModule(CallbackModule_default):
|
||||
def v2_playbook_on_play_start(self, play):
|
||||
# Leave the previous task on screen (as it has changes/errors)
|
||||
if self._display.verbosity == 0 and self.keep:
|
||||
sys.stdout.write(vt100.restore + vt100.reset + '\n' + vt100.save + vt100.clearline + vt100.bold)
|
||||
sys.stdout.write(f"{vt100.restore}{vt100.reset}\n{vt100.save}{vt100.clearline}{vt100.bold}")
|
||||
else:
|
||||
sys.stdout.write(vt100.restore + vt100.reset + vt100.clearline + vt100.bold)
|
||||
|
||||
@@ -341,14 +340,14 @@ class CallbackModule(CallbackModule_default):
|
||||
name = play.get_name().strip()
|
||||
if not name:
|
||||
name = 'unnamed'
|
||||
sys.stdout.write('PLAY %d: %s' % (self.count['play'], name.upper()))
|
||||
sys.stdout.write(vt100.restore + vt100.reset + '\n' + vt100.save + vt100.clearline)
|
||||
sys.stdout.write(f"PLAY {self.count['play']}: {name.upper()}")
|
||||
sys.stdout.write(f"{vt100.restore}{vt100.reset}\n{vt100.save}{vt100.clearline}")
|
||||
sys.stdout.flush()
|
||||
|
||||
def v2_playbook_on_task_start(self, task, is_conditional):
|
||||
# Leave the previous task on screen (as it has changes/errors)
|
||||
if self._display.verbosity == 0 and self.keep:
|
||||
sys.stdout.write(vt100.restore + vt100.reset + '\n' + vt100.save + vt100.clearline + vt100.underline)
|
||||
sys.stdout.write(f"{vt100.restore}{vt100.reset}\n{vt100.save}{vt100.clearline}{vt100.underline}")
|
||||
else:
|
||||
# Do not clear line, since we want to retain the previous output
|
||||
sys.stdout.write(vt100.restore + vt100.reset + vt100.underline)
|
||||
@@ -365,14 +364,14 @@ class CallbackModule(CallbackModule_default):
|
||||
self.count['task'] += 1
|
||||
|
||||
# Write the next task on screen (behind the prompt is the previous output)
|
||||
sys.stdout.write('%s %d.' % (self.type, self.count[self.type]))
|
||||
sys.stdout.write(f'{self.type} {self.count[self.type]}.')
|
||||
sys.stdout.write(vt100.reset)
|
||||
sys.stdout.flush()
|
||||
|
||||
def v2_playbook_on_handler_task_start(self, task):
|
||||
# Leave the previous task on screen (as it has changes/errors)
|
||||
if self._display.verbosity == 0 and self.keep:
|
||||
sys.stdout.write(vt100.restore + vt100.reset + '\n' + vt100.save + vt100.clearline + vt100.underline)
|
||||
sys.stdout.write(f"{vt100.restore}{vt100.reset}\n{vt100.save}{vt100.clearline}{vt100.underline}")
|
||||
else:
|
||||
sys.stdout.write(vt100.restore + vt100.reset + vt100.clearline + vt100.underline)
|
||||
|
||||
@@ -388,7 +387,7 @@ class CallbackModule(CallbackModule_default):
|
||||
self.count[self.type] += 1
|
||||
|
||||
# Write the next task on screen (behind the prompt is the previous output)
|
||||
sys.stdout.write('%s %d.' % (self.type, self.count[self.type]))
|
||||
sys.stdout.write(f'{self.type} {self.count[self.type]}.')
|
||||
sys.stdout.write(vt100.reset)
|
||||
sys.stdout.flush()
|
||||
|
||||
@@ -451,13 +450,13 @@ class CallbackModule(CallbackModule_default):
|
||||
|
||||
def v2_playbook_on_no_hosts_remaining(self):
|
||||
if self._display.verbosity == 0 and self.keep:
|
||||
sys.stdout.write(vt100.restore + vt100.reset + '\n' + vt100.save + vt100.clearline)
|
||||
sys.stdout.write(f"{vt100.restore}{vt100.reset}\n{vt100.save}{vt100.clearline}")
|
||||
else:
|
||||
sys.stdout.write(vt100.restore + vt100.reset + vt100.clearline)
|
||||
self.keep = False
|
||||
|
||||
sys.stdout.write(vt100.white + vt100.redbg + 'NO MORE HOSTS LEFT')
|
||||
sys.stdout.write(vt100.restore + vt100.reset + '\n' + vt100.save + vt100.clearline)
|
||||
sys.stdout.write(f"{vt100.white + vt100.redbg}NO MORE HOSTS LEFT")
|
||||
sys.stdout.write(f"{vt100.restore}{vt100.reset}\n{vt100.save}{vt100.clearline}")
|
||||
sys.stdout.flush()
|
||||
|
||||
def v2_playbook_on_include(self, included_file):
|
||||
@@ -465,7 +464,7 @@ class CallbackModule(CallbackModule_default):
|
||||
|
||||
def v2_playbook_on_stats(self, stats):
|
||||
if self._display.verbosity == 0 and self.keep:
|
||||
sys.stdout.write(vt100.restore + vt100.reset + '\n' + vt100.save + vt100.clearline)
|
||||
sys.stdout.write(f"{vt100.restore}{vt100.reset}\n{vt100.save}{vt100.clearline}")
|
||||
else:
|
||||
sys.stdout.write(vt100.restore + vt100.reset + vt100.clearline)
|
||||
|
||||
@@ -476,22 +475,16 @@ class CallbackModule(CallbackModule_default):
|
||||
sys.stdout.write(vt100.bold + vt100.underline)
|
||||
sys.stdout.write('SUMMARY')
|
||||
|
||||
sys.stdout.write(vt100.restore + vt100.reset + '\n' + vt100.save + vt100.clearline)
|
||||
sys.stdout.write(f"{vt100.restore}{vt100.reset}\n{vt100.save}{vt100.clearline}")
|
||||
sys.stdout.flush()
|
||||
|
||||
hosts = sorted(stats.processed.keys())
|
||||
for h in hosts:
|
||||
t = stats.summarize(h)
|
||||
self._display.display(
|
||||
u"%s : %s %s %s %s %s %s" % (
|
||||
hostcolor(h, t),
|
||||
colorize(u'ok', t['ok'], C.COLOR_OK),
|
||||
colorize(u'changed', t['changed'], C.COLOR_CHANGED),
|
||||
colorize(u'unreachable', t['unreachable'], C.COLOR_UNREACHABLE),
|
||||
colorize(u'failed', t['failures'], C.COLOR_ERROR),
|
||||
colorize(u'rescued', t['rescued'], C.COLOR_OK),
|
||||
colorize(u'ignored', t['ignored'], C.COLOR_WARN),
|
||||
),
|
||||
f"{hostcolor(h, t)} : {colorize('ok', t['ok'], C.COLOR_OK)} {colorize('changed', t['changed'], C.COLOR_CHANGED)} "
|
||||
f"{colorize('unreachable', t['unreachable'], C.COLOR_UNREACHABLE)} {colorize('failed', t['failures'], C.COLOR_ERROR)} "
|
||||
f"{colorize('rescued', t['rescued'], C.COLOR_OK)} {colorize('ignored', t['ignored'], C.COLOR_WARN)}",
|
||||
screen_only=True
|
||||
)
|
||||
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
name: diy
|
||||
@@ -786,12 +785,6 @@ from ansible.vars.manager import VariableManager
|
||||
from ansible.plugins.callback.default import CallbackModule as Default
|
||||
from ansible.module_utils.common.text.converters import to_text
|
||||
|
||||
try:
|
||||
from ansible.template import trust_as_template # noqa: F401, pylint: disable=unused-import
|
||||
SUPPORTS_DATA_TAGGING = True
|
||||
except ImportError:
|
||||
SUPPORTS_DATA_TAGGING = False
|
||||
|
||||
|
||||
class DummyStdout(object):
|
||||
def flush(self):
|
||||
@@ -829,9 +822,9 @@ class CallbackModule(Default):
|
||||
_callback_options = ['msg', 'msg_color']
|
||||
|
||||
for option in _callback_options:
|
||||
_option_name = '%s_%s' % (_callback_type, option)
|
||||
_option_name = f'{_callback_type}_{option}'
|
||||
_option_template = variables.get(
|
||||
self.DIY_NS + "_" + _option_name,
|
||||
f"{self.DIY_NS}_{_option_name}",
|
||||
self.get_option(_option_name)
|
||||
)
|
||||
_ret.update({option: self._template(
|
||||
@@ -845,10 +838,7 @@ class CallbackModule(Default):
|
||||
return _ret
|
||||
|
||||
def _using_diy(self, spec):
|
||||
sentinel = object()
|
||||
omit = spec['vars'].get('omit', sentinel)
|
||||
# With Data Tagging, omit is sentinel
|
||||
return (spec['msg'] is not None) and (spec['msg'] != omit or omit is sentinel)
|
||||
return (spec['msg'] is not None) and (spec['msg'] != spec['vars']['omit'])
|
||||
|
||||
def _parent_has_callback(self):
|
||||
return hasattr(super(CallbackModule, self), sys._getframe(1).f_code.co_name)
|
||||
@@ -871,7 +861,7 @@ class CallbackModule(Default):
|
||||
handler=None, result=None, stats=None, remove_attr_ref_loop=True):
|
||||
def _get_value(obj, attr=None, method=None):
|
||||
if attr:
|
||||
return getattr(obj, attr, getattr(obj, "_" + attr, None))
|
||||
return getattr(obj, attr, getattr(obj, f"_{attr}", None))
|
||||
|
||||
if method:
|
||||
_method = getattr(obj, method)
|
||||
@@ -904,7 +894,7 @@ class CallbackModule(Default):
|
||||
)
|
||||
_ret.update(_all)
|
||||
|
||||
_ret.update(_ret.get(self.DIY_NS, {self.DIY_NS: {} if SUPPORTS_DATA_TAGGING else CallbackDIYDict()}))
|
||||
_ret.update(_ret.get(self.DIY_NS, {self.DIY_NS: CallbackDIYDict()}))
|
||||
|
||||
_ret[self.DIY_NS].update({'playbook': {}})
|
||||
_playbook_attributes = ['entries', 'file_name', 'basedir']
|
||||
|
||||
@@ -2,8 +2,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Victor Martinez (@v1v) <VictorMartinezRubio@gmail.com>
|
||||
@@ -118,7 +117,7 @@ class TaskData:
|
||||
if host.uuid in self.host_data:
|
||||
if host.status == 'included':
|
||||
# concatenate task include output from multiple items
|
||||
host.result = '%s\n%s' % (self.host_data[host.uuid].result, host.result)
|
||||
host.result = f'{self.host_data[host.uuid].result}\n{host.result}'
|
||||
else:
|
||||
return
|
||||
|
||||
@@ -166,7 +165,7 @@ class ElasticSource(object):
|
||||
args = None
|
||||
|
||||
if not task.no_log and not hide_task_arguments:
|
||||
args = ', '.join(('%s=%s' % a for a in task.args.items()))
|
||||
args = ', '.join((f'{k}={v}' for k, v in task.args.items()))
|
||||
|
||||
tasks_data[uuid] = TaskData(uuid, name, path, play_name, action, args)
|
||||
|
||||
@@ -225,7 +224,7 @@ class ElasticSource(object):
|
||||
def create_span_data(self, apm_cli, task_data, host_data):
|
||||
""" create the span with the given TaskData and HostData """
|
||||
|
||||
name = '[%s] %s: %s' % (host_data.name, task_data.play, task_data.name)
|
||||
name = f'[{host_data.name}] {task_data.play}: {task_data.name}'
|
||||
|
||||
message = "success"
|
||||
status = "success"
|
||||
@@ -259,7 +258,7 @@ class ElasticSource(object):
|
||||
"ansible.task.host.status": host_data.status}) as span:
|
||||
span.outcome = status
|
||||
if 'failure' in status:
|
||||
exception = AnsibleRuntimeError(message="{0}: {1} failed with error message {2}".format(task_data.action, name, enriched_error_message))
|
||||
exception = AnsibleRuntimeError(message=f"{task_data.action}: {name} failed with error message {enriched_error_message}")
|
||||
apm_cli.capture_exception(exc_info=(type(exception), exception, exception.__traceback__), handled=True)
|
||||
|
||||
def init_apm_client(self, apm_server_url, apm_service_name, apm_verify_server_cert, apm_secret_token, apm_api_key):
|
||||
@@ -288,7 +287,7 @@ class ElasticSource(object):
|
||||
message = result.get('msg', 'failed')
|
||||
exception = result.get('exception')
|
||||
stderr = result.get('stderr')
|
||||
return ('message: "{0}"\nexception: "{1}"\nstderr: "{2}"').format(message, exception, stderr)
|
||||
return f"message: \"{message}\"\nexception: \"{exception}\"\nstderr: \"{stderr}\""
|
||||
|
||||
|
||||
class CallbackModule(CallbackBase):
|
||||
|
||||
@@ -1,240 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2014, Matt Martz <matt@sivel.net>
|
||||
# Copyright (c) 2017 Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = '''
|
||||
author: Unknown (!UNKNOWN)
|
||||
name: hipchat
|
||||
type: notification
|
||||
requirements:
|
||||
- whitelist in configuration.
|
||||
- prettytable (python lib)
|
||||
short_description: post task events to hipchat
|
||||
description:
|
||||
- This callback plugin sends status updates to a HipChat channel during playbook execution.
|
||||
- Before 2.4 only environment variables were available for configuring this plugin.
|
||||
deprecated:
|
||||
removed_in: 10.0.0
|
||||
why: The hipchat service has been discontinued and the self-hosted variant has been End of Life since 2020.
|
||||
alternative: There is none.
|
||||
options:
|
||||
token:
|
||||
description: HipChat API token for v1 or v2 API.
|
||||
type: str
|
||||
required: true
|
||||
env:
|
||||
- name: HIPCHAT_TOKEN
|
||||
ini:
|
||||
- section: callback_hipchat
|
||||
key: token
|
||||
api_version:
|
||||
description: HipChat API version, v1 or v2.
|
||||
type: str
|
||||
choices:
|
||||
- v1
|
||||
- v2
|
||||
required: false
|
||||
default: v1
|
||||
env:
|
||||
- name: HIPCHAT_API_VERSION
|
||||
ini:
|
||||
- section: callback_hipchat
|
||||
key: api_version
|
||||
room:
|
||||
description: HipChat room to post in.
|
||||
type: str
|
||||
default: ansible
|
||||
env:
|
||||
- name: HIPCHAT_ROOM
|
||||
ini:
|
||||
- section: callback_hipchat
|
||||
key: room
|
||||
from:
|
||||
description: Name to post as
|
||||
type: str
|
||||
default: ansible
|
||||
env:
|
||||
- name: HIPCHAT_FROM
|
||||
ini:
|
||||
- section: callback_hipchat
|
||||
key: from
|
||||
notify:
|
||||
description: Add notify flag to important messages
|
||||
type: bool
|
||||
default: true
|
||||
env:
|
||||
- name: HIPCHAT_NOTIFY
|
||||
ini:
|
||||
- section: callback_hipchat
|
||||
key: notify
|
||||
|
||||
'''
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
try:
|
||||
import prettytable
|
||||
HAS_PRETTYTABLE = True
|
||||
except ImportError:
|
||||
HAS_PRETTYTABLE = False
|
||||
|
||||
from ansible.plugins.callback import CallbackBase
|
||||
from ansible.module_utils.six.moves.urllib.parse import urlencode
|
||||
from ansible.module_utils.urls import open_url
|
||||
|
||||
|
||||
class CallbackModule(CallbackBase):
|
||||
"""This is an example ansible callback plugin that sends status
|
||||
updates to a HipChat channel during playbook execution.
|
||||
"""
|
||||
|
||||
CALLBACK_VERSION = 2.0
|
||||
CALLBACK_TYPE = 'notification'
|
||||
CALLBACK_NAME = 'community.general.hipchat'
|
||||
CALLBACK_NEEDS_WHITELIST = True
|
||||
|
||||
API_V1_URL = 'https://api.hipchat.com/v1/rooms/message'
|
||||
API_V2_URL = 'https://api.hipchat.com/v2/'
|
||||
|
||||
def __init__(self):
|
||||
|
||||
super(CallbackModule, self).__init__()
|
||||
|
||||
if not HAS_PRETTYTABLE:
|
||||
self.disabled = True
|
||||
self._display.warning('The `prettytable` python module is not installed. '
|
||||
'Disabling the HipChat callback plugin.')
|
||||
self.printed_playbook = False
|
||||
self.playbook_name = None
|
||||
self.play = None
|
||||
|
||||
def set_options(self, task_keys=None, var_options=None, direct=None):
|
||||
super(CallbackModule, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)
|
||||
|
||||
self.token = self.get_option('token')
|
||||
self.api_version = self.get_option('api_version')
|
||||
self.from_name = self.get_option('from')
|
||||
self.allow_notify = self.get_option('notify')
|
||||
self.room = self.get_option('room')
|
||||
|
||||
if self.token is None:
|
||||
self.disabled = True
|
||||
self._display.warning('HipChat token could not be loaded. The HipChat '
|
||||
'token can be provided using the `HIPCHAT_TOKEN` '
|
||||
'environment variable.')
|
||||
|
||||
# Pick the request handler.
|
||||
if self.api_version == 'v2':
|
||||
self.send_msg = self.send_msg_v2
|
||||
else:
|
||||
self.send_msg = self.send_msg_v1
|
||||
|
||||
def send_msg_v2(self, msg, msg_format='text', color='yellow', notify=False):
|
||||
"""Method for sending a message to HipChat"""
|
||||
|
||||
headers = {'Authorization': 'Bearer %s' % self.token, 'Content-Type': 'application/json'}
|
||||
|
||||
body = {}
|
||||
body['room_id'] = self.room
|
||||
body['from'] = self.from_name[:15] # max length is 15
|
||||
body['message'] = msg
|
||||
body['message_format'] = msg_format
|
||||
body['color'] = color
|
||||
body['notify'] = self.allow_notify and notify
|
||||
|
||||
data = json.dumps(body)
|
||||
url = self.API_V2_URL + "room/{room_id}/notification".format(room_id=self.room)
|
||||
try:
|
||||
response = open_url(url, data=data, headers=headers, method='POST')
|
||||
return response.read()
|
||||
except Exception as ex:
|
||||
self._display.warning('Could not submit message to hipchat: {0}'.format(ex))
|
||||
|
||||
def send_msg_v1(self, msg, msg_format='text', color='yellow', notify=False):
|
||||
"""Method for sending a message to HipChat"""
|
||||
|
||||
params = {}
|
||||
params['room_id'] = self.room
|
||||
params['from'] = self.from_name[:15] # max length is 15
|
||||
params['message'] = msg
|
||||
params['message_format'] = msg_format
|
||||
params['color'] = color
|
||||
params['notify'] = int(self.allow_notify and notify)
|
||||
|
||||
url = ('%s?auth_token=%s' % (self.API_V1_URL, self.token))
|
||||
try:
|
||||
response = open_url(url, data=urlencode(params))
|
||||
return response.read()
|
||||
except Exception as ex:
|
||||
self._display.warning('Could not submit message to hipchat: {0}'.format(ex))
|
||||
|
||||
def v2_playbook_on_play_start(self, play):
|
||||
"""Display Playbook and play start messages"""
|
||||
|
||||
self.play = play
|
||||
name = play.name
|
||||
# This block sends information about a playbook when it starts
|
||||
# The playbook object is not immediately available at
|
||||
# playbook_on_start so we grab it via the play
|
||||
#
|
||||
# Displays info about playbook being started by a person on an
|
||||
# inventory, as well as Tags, Skip Tags and Limits
|
||||
if not self.printed_playbook:
|
||||
self.playbook_name, dummy = os.path.splitext(os.path.basename(self.play.playbook.filename))
|
||||
host_list = self.play.playbook.inventory.host_list
|
||||
inventory = os.path.basename(os.path.realpath(host_list))
|
||||
self.send_msg("%s: Playbook initiated by %s against %s" %
|
||||
(self.playbook_name,
|
||||
self.play.playbook.remote_user,
|
||||
inventory), notify=True)
|
||||
self.printed_playbook = True
|
||||
subset = self.play.playbook.inventory._subset
|
||||
skip_tags = self.play.playbook.skip_tags
|
||||
self.send_msg("%s:\nTags: %s\nSkip Tags: %s\nLimit: %s" %
|
||||
(self.playbook_name,
|
||||
', '.join(self.play.playbook.only_tags),
|
||||
', '.join(skip_tags) if skip_tags else None,
|
||||
', '.join(subset) if subset else subset))
|
||||
|
||||
# This is where we actually say we are starting a play
|
||||
self.send_msg("%s: Starting play: %s" %
|
||||
(self.playbook_name, name))
|
||||
|
||||
def playbook_on_stats(self, stats):
|
||||
"""Display info about playbook statistics"""
|
||||
hosts = sorted(stats.processed.keys())
|
||||
|
||||
t = prettytable.PrettyTable(['Host', 'Ok', 'Changed', 'Unreachable',
|
||||
'Failures'])
|
||||
|
||||
failures = False
|
||||
unreachable = False
|
||||
|
||||
for h in hosts:
|
||||
s = stats.summarize(h)
|
||||
|
||||
if s['failures'] > 0:
|
||||
failures = True
|
||||
if s['unreachable'] > 0:
|
||||
unreachable = True
|
||||
|
||||
t.add_row([h] + [s[k] for k in ['ok', 'changed', 'unreachable',
|
||||
'failures']])
|
||||
|
||||
self.send_msg("%s: Playbook complete" % self.playbook_name,
|
||||
notify=True)
|
||||
|
||||
if failures or unreachable:
|
||||
color = 'red'
|
||||
self.send_msg("%s: Failures detected" % self.playbook_name,
|
||||
color=color, notify=True)
|
||||
else:
|
||||
color = 'green'
|
||||
|
||||
self.send_msg("/code %s:\n%s" % (self.playbook_name, t), color=color)
|
||||
@@ -4,8 +4,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Unknown (!UNKNOWN)
|
||||
@@ -102,7 +101,7 @@ class CallbackModule(CallbackBase):
|
||||
"""Display Playbook and play start messages"""
|
||||
self.play = play
|
||||
name = play.name
|
||||
self.send_msg("Ansible starting play: %s" % (name))
|
||||
self.send_msg(f"Ansible starting play: {name}")
|
||||
|
||||
def playbook_on_stats(self, stats):
|
||||
name = self.play
|
||||
@@ -118,7 +117,7 @@ class CallbackModule(CallbackBase):
|
||||
|
||||
if failures or unreachable:
|
||||
out = self.debug
|
||||
self.send_msg("%s: Failures detected \n%s \nHost: %s\n Failed at:\n%s" % (name, self.task, h, out))
|
||||
self.send_msg(f"{name}: Failures detected \n{self.task} \nHost: {h}\n Failed at:\n{out}")
|
||||
else:
|
||||
out = self.debug
|
||||
self.send_msg("Great! \n Playbook %s completed:\n%s \n Last task debug:\n %s" % (name, s, out))
|
||||
self.send_msg(f"Great! \n Playbook {name} completed:\n{s} \n Last task debug:\n {out}")
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Unknown (!UNKNOWN)
|
||||
@@ -57,7 +56,10 @@ class CallbackModule(CallbackBase):
|
||||
CALLBACK_NEEDS_WHITELIST = True
|
||||
|
||||
TIME_FORMAT = "%b %d %Y %H:%M:%S"
|
||||
MSG_FORMAT = "%(now)s - %(playbook)s - %(task_name)s - %(task_action)s - %(category)s - %(data)s\n\n"
|
||||
|
||||
@staticmethod
|
||||
def _make_msg(now, playbook, task_name, task_action, category, data):
|
||||
return f"{now} - {playbook} - {task_name} - {task_action} - {category} - {data}\n\n"
|
||||
|
||||
def __init__(self):
|
||||
|
||||
@@ -82,22 +84,12 @@ class CallbackModule(CallbackBase):
|
||||
invocation = data.pop('invocation', None)
|
||||
data = json.dumps(data, cls=AnsibleJSONEncoder)
|
||||
if invocation is not None:
|
||||
data = json.dumps(invocation) + " => %s " % data
|
||||
data = f"{json.dumps(invocation)} => {data} "
|
||||
|
||||
path = os.path.join(self.log_folder, result._host.get_name())
|
||||
now = time.strftime(self.TIME_FORMAT, time.localtime())
|
||||
|
||||
msg = to_bytes(
|
||||
self.MSG_FORMAT
|
||||
% dict(
|
||||
now=now,
|
||||
playbook=self.playbook,
|
||||
task_name=result._task.name,
|
||||
task_action=result._task.action,
|
||||
category=category,
|
||||
data=data,
|
||||
)
|
||||
)
|
||||
msg = to_bytes(self._make_msg(now, self.playbook, result._task.name, result._task.action, category, data))
|
||||
with open(path, "ab") as fd:
|
||||
fd.write(msg)
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
name: loganalytics
|
||||
@@ -84,18 +83,17 @@ class AzureLogAnalyticsSource(object):
|
||||
|
||||
def __build_signature(self, date, workspace_id, shared_key, content_length):
|
||||
# Build authorisation signature for Azure log analytics API call
|
||||
sigs = "POST\n{0}\napplication/json\nx-ms-date:{1}\n/api/logs".format(
|
||||
str(content_length), date)
|
||||
sigs = f"POST\n{content_length}\napplication/json\nx-ms-date:{date}\n/api/logs"
|
||||
utf8_sigs = sigs.encode('utf-8')
|
||||
decoded_shared_key = base64.b64decode(shared_key)
|
||||
hmac_sha256_sigs = hmac.new(
|
||||
decoded_shared_key, utf8_sigs, digestmod=hashlib.sha256).digest()
|
||||
encoded_hash = base64.b64encode(hmac_sha256_sigs).decode('utf-8')
|
||||
signature = "SharedKey {0}:{1}".format(workspace_id, encoded_hash)
|
||||
signature = f"SharedKey {workspace_id}:{encoded_hash}"
|
||||
return signature
|
||||
|
||||
def __build_workspace_url(self, workspace_id):
|
||||
return "https://{0}.ods.opinsights.azure.com/api/logs?api-version=2016-04-01".format(workspace_id)
|
||||
return f"https://{workspace_id}.ods.opinsights.azure.com/api/logs?api-version=2016-04-01"
|
||||
|
||||
def __rfc1123date(self):
|
||||
return now().strftime('%a, %d %b %Y %H:%M:%S GMT')
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Unknown (!UNKNOWN)
|
||||
@@ -73,7 +72,7 @@ except ImportError:
|
||||
|
||||
# Getting MAC Address of system:
|
||||
def get_mac():
|
||||
mac = "%012x" % getnode()
|
||||
mac = f"{getnode():012x}"
|
||||
return ":".join(map(lambda index: mac[index:index + 2], range(int(len(mac) / 2))))
|
||||
|
||||
|
||||
@@ -161,7 +160,7 @@ class CallbackModule(CallbackBase):
|
||||
if ninvalidKeys > 0:
|
||||
for key in invalidKeys:
|
||||
del meta[key]
|
||||
meta['__errors'] = 'These keys have been sanitized: ' + ', '.join(invalidKeys)
|
||||
meta['__errors'] = f"These keys have been sanitized: {', '.join(invalidKeys)}"
|
||||
return meta
|
||||
|
||||
def sanitizeJSON(self, data):
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
# Copyright (c) 2017 Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Unknown (!UNKNOWN)
|
||||
@@ -133,7 +132,7 @@ class PlainTextSocketAppender(object):
|
||||
# Error message displayed when an incorrect Token has been detected
|
||||
self.INVALID_TOKEN = "\n\nIt appears the LOGENTRIES_TOKEN parameter you entered is incorrect!\n\n"
|
||||
# Unicode Line separator character \u2028
|
||||
self.LINE_SEP = u'\u2028'
|
||||
self.LINE_SEP = '\u2028'
|
||||
|
||||
self._display = display
|
||||
self._conn = None
|
||||
@@ -151,7 +150,7 @@ class PlainTextSocketAppender(object):
|
||||
self.open_connection()
|
||||
return
|
||||
except Exception as e:
|
||||
self._display.vvvv(u"Unable to connect to Logentries: %s" % to_text(e))
|
||||
self._display.vvvv(f"Unable to connect to Logentries: {e}")
|
||||
|
||||
root_delay *= 2
|
||||
if root_delay > self.MAX_DELAY:
|
||||
@@ -160,7 +159,7 @@ class PlainTextSocketAppender(object):
|
||||
wait_for = root_delay + random.uniform(0, root_delay)
|
||||
|
||||
try:
|
||||
self._display.vvvv("sleeping %s before retry" % wait_for)
|
||||
self._display.vvvv(f"sleeping {wait_for} before retry")
|
||||
time.sleep(wait_for)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
@@ -173,8 +172,8 @@ class PlainTextSocketAppender(object):
|
||||
# Replace newlines with Unicode line separator
|
||||
# for multi-line events
|
||||
data = to_text(data, errors='surrogate_or_strict')
|
||||
multiline = data.replace(u'\n', self.LINE_SEP)
|
||||
multiline += u"\n"
|
||||
multiline = data.replace('\n', self.LINE_SEP)
|
||||
multiline += "\n"
|
||||
# Send data, reconnect if needed
|
||||
while True:
|
||||
try:
|
||||
@@ -247,7 +246,7 @@ class CallbackModule(CallbackBase):
|
||||
self.use_tls = self.get_option('use_tls')
|
||||
self.flatten = self.get_option('flatten')
|
||||
except KeyError as e:
|
||||
self._display.warning(u"Missing option for Logentries callback plugin: %s" % to_text(e))
|
||||
self._display.warning(f"Missing option for Logentries callback plugin: {e}")
|
||||
self.disabled = True
|
||||
|
||||
try:
|
||||
@@ -266,10 +265,10 @@ class CallbackModule(CallbackBase):
|
||||
|
||||
if not self.disabled:
|
||||
if self.use_tls:
|
||||
self._display.vvvv("Connecting to %s:%s with TLS" % (self.api_url, self.api_tls_port))
|
||||
self._display.vvvv(f"Connecting to {self.api_url}:{self.api_tls_port} with TLS")
|
||||
self._appender = TLSSocketAppender(display=self._display, LE_API=self.api_url, LE_TLS_PORT=self.api_tls_port)
|
||||
else:
|
||||
self._display.vvvv("Connecting to %s:%s" % (self.api_url, self.api_port))
|
||||
self._display.vvvv(f"Connecting to {self.api_url}:{self.api_port}")
|
||||
self._appender = PlainTextSocketAppender(display=self._display, LE_API=self.api_url, LE_PORT=self.api_port)
|
||||
self._appender.reopen_connection()
|
||||
|
||||
@@ -282,7 +281,7 @@ class CallbackModule(CallbackBase):
|
||||
|
||||
def emit(self, record):
|
||||
msg = record.rstrip('\n')
|
||||
msg = "{0} {1}".format(self.token, msg)
|
||||
msg = f"{self.token} {msg}"
|
||||
self._appender.put(msg)
|
||||
self._display.vvvv("Sent event to logentries")
|
||||
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Yevhen Khmelenko (@ujenmr)
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
name: mail
|
||||
@@ -134,14 +133,14 @@ class CallbackModule(CallbackBase):
|
||||
if self.bcc:
|
||||
bcc_addresses = email.utils.getaddresses(self.bcc)
|
||||
|
||||
content = 'Date: %s\n' % email.utils.formatdate()
|
||||
content += 'From: %s\n' % email.utils.formataddr(sender_address)
|
||||
content = f'Date: {email.utils.formatdate()}\n'
|
||||
content += f'From: {email.utils.formataddr(sender_address)}\n'
|
||||
if self.to:
|
||||
content += 'To: %s\n' % ', '.join([email.utils.formataddr(pair) for pair in to_addresses])
|
||||
content += f"To: {', '.join([email.utils.formataddr(pair) for pair in to_addresses])}\n"
|
||||
if self.cc:
|
||||
content += 'Cc: %s\n' % ', '.join([email.utils.formataddr(pair) for pair in cc_addresses])
|
||||
content += 'Message-ID: %s\n' % email.utils.make_msgid(domain=self.get_option('message_id_domain'))
|
||||
content += 'Subject: %s\n\n' % subject.strip()
|
||||
content += f"Cc: {', '.join([email.utils.formataddr(pair) for pair in cc_addresses])}\n"
|
||||
content += f"Message-ID: {email.utils.make_msgid(domain=self.get_option('message_id_domain'))}\n"
|
||||
content += f'Subject: {subject.strip()}\n\n'
|
||||
content += body
|
||||
|
||||
addresses = to_addresses
|
||||
@@ -158,23 +157,22 @@ class CallbackModule(CallbackBase):
|
||||
smtp.quit()
|
||||
|
||||
def subject_msg(self, multiline, failtype, linenr):
|
||||
return '%s: %s' % (failtype, multiline.strip('\r\n').splitlines()[linenr])
|
||||
msg = multiline.strip('\r\n').splitlines()[linenr]
|
||||
return f'{failtype}: {msg}'
|
||||
|
||||
def indent(self, multiline, indent=8):
|
||||
return re.sub('^', ' ' * indent, multiline, flags=re.MULTILINE)
|
||||
|
||||
def body_blob(self, multiline, texttype):
|
||||
''' Turn some text output in a well-indented block for sending in a mail body '''
|
||||
intro = 'with the following %s:\n\n' % texttype
|
||||
blob = ''
|
||||
for line in multiline.strip('\r\n').splitlines():
|
||||
blob += '%s\n' % line
|
||||
return intro + self.indent(blob) + '\n'
|
||||
intro = f'with the following {texttype}:\n\n'
|
||||
blob = "\n".join(multiline.strip('\r\n').splitlines())
|
||||
return f"{intro}{self.indent(blob)}\n"
|
||||
|
||||
def mail_result(self, result, failtype):
|
||||
host = result._host.get_name()
|
||||
if not self.sender:
|
||||
self.sender = '"Ansible: %s" <root>' % host
|
||||
self.sender = f'"Ansible: {host}" <root>'
|
||||
|
||||
# Add subject
|
||||
if self.itembody:
|
||||
@@ -190,31 +188,32 @@ class CallbackModule(CallbackBase):
|
||||
elif result._result.get('exception'): # Unrelated exceptions are added to output :-/
|
||||
subject = self.subject_msg(result._result['exception'], failtype, -1)
|
||||
else:
|
||||
subject = '%s: %s' % (failtype, result._task.name or result._task.action)
|
||||
subject = f'{failtype}: {result._task.name or result._task.action}'
|
||||
|
||||
# Make playbook name visible (e.g. in Outlook/Gmail condensed view)
|
||||
body = 'Playbook: %s\n' % os.path.basename(self.playbook._file_name)
|
||||
body = f'Playbook: {os.path.basename(self.playbook._file_name)}\n'
|
||||
if result._task.name:
|
||||
body += 'Task: %s\n' % result._task.name
|
||||
body += 'Module: %s\n' % result._task.action
|
||||
body += 'Host: %s\n' % host
|
||||
body += f'Task: {result._task.name}\n'
|
||||
body += f'Module: {result._task.action}\n'
|
||||
body += f'Host: {host}\n'
|
||||
body += '\n'
|
||||
|
||||
# Add task information (as much as possible)
|
||||
body += 'The following task failed:\n\n'
|
||||
if 'invocation' in result._result:
|
||||
body += self.indent('%s: %s\n' % (result._task.action, json.dumps(result._result['invocation']['module_args'], indent=4)))
|
||||
body += self.indent(f"{result._task.action}: {json.dumps(result._result['invocation']['module_args'], indent=4)}\n")
|
||||
elif result._task.name:
|
||||
body += self.indent('%s (%s)\n' % (result._task.name, result._task.action))
|
||||
body += self.indent(f'{result._task.name} ({result._task.action})\n')
|
||||
else:
|
||||
body += self.indent('%s\n' % result._task.action)
|
||||
body += self.indent(f'{result._task.action}\n')
|
||||
body += '\n'
|
||||
|
||||
# Add item / message
|
||||
if self.itembody:
|
||||
body += self.itembody
|
||||
elif result._result.get('failed_when_result') is True:
|
||||
body += "due to the following condition:\n\n" + self.indent('failed_when:\n- ' + '\n- '.join(result._task.failed_when)) + '\n\n'
|
||||
fail_cond = self.indent('failed_when:\n- ' + '\n- '.join(result._task.failed_when))
|
||||
body += f"due to the following condition:\n\n{fail_cond}\n\n"
|
||||
elif result._result.get('msg'):
|
||||
body += self.body_blob(result._result['msg'], 'message')
|
||||
|
||||
@@ -227,13 +226,13 @@ class CallbackModule(CallbackBase):
|
||||
body += self.body_blob(result._result['exception'], 'exception')
|
||||
if result._result.get('warnings'):
|
||||
for i in range(len(result._result.get('warnings'))):
|
||||
body += self.body_blob(result._result['warnings'][i], 'exception %d' % (i + 1))
|
||||
body += self.body_blob(result._result['warnings'][i], f'exception {i + 1}')
|
||||
if result._result.get('deprecations'):
|
||||
for i in range(len(result._result.get('deprecations'))):
|
||||
body += self.body_blob(result._result['deprecations'][i], 'exception %d' % (i + 1))
|
||||
body += self.body_blob(result._result['deprecations'][i], f'exception {i + 1}')
|
||||
|
||||
body += 'and a complete dump of the error:\n\n'
|
||||
body += self.indent('%s: %s' % (failtype, json.dumps(result._result, cls=AnsibleJSONEncoder, indent=4)))
|
||||
body += self.indent(f'{failtype}: {json.dumps(result._result, cls=AnsibleJSONEncoder, indent=4)}')
|
||||
|
||||
self.mail(subject=subject, body=body)
|
||||
|
||||
@@ -256,4 +255,4 @@ class CallbackModule(CallbackBase):
|
||||
def v2_runner_item_on_failed(self, result):
|
||||
# Pass item information to task failure
|
||||
self.itemsubject = result._result['msg']
|
||||
self.itembody += self.body_blob(json.dumps(result._result, cls=AnsibleJSONEncoder, indent=4), "failed item dump '%(item)s'" % result._result)
|
||||
self.itembody += self.body_blob(json.dumps(result._result, cls=AnsibleJSONEncoder, indent=4), f"failed item dump '{result._result['item']}'")
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
name: nrdp
|
||||
@@ -132,10 +131,10 @@ class CallbackModule(CallbackBase):
|
||||
xmldata = "<?xml version='1.0'?>\n"
|
||||
xmldata += "<checkresults>\n"
|
||||
xmldata += "<checkresult type='service'>\n"
|
||||
xmldata += "<hostname>%s</hostname>\n" % self.hostname
|
||||
xmldata += "<servicename>%s</servicename>\n" % self.servicename
|
||||
xmldata += "<state>%d</state>\n" % state
|
||||
xmldata += "<output>%s</output>\n" % msg
|
||||
xmldata += f"<hostname>{self.hostname}</hostname>\n"
|
||||
xmldata += f"<servicename>{self.servicename}</servicename>\n"
|
||||
xmldata += f"<state>{state}</state>\n"
|
||||
xmldata += f"<output>{msg}</output>\n"
|
||||
xmldata += "</checkresult>\n"
|
||||
xmldata += "</checkresults>\n"
|
||||
|
||||
@@ -152,7 +151,7 @@ class CallbackModule(CallbackBase):
|
||||
validate_certs=self.validate_nrdp_certs)
|
||||
return response.read()
|
||||
except Exception as ex:
|
||||
self._display.warning("NRDP callback cannot send result {0}".format(ex))
|
||||
self._display.warning(f"NRDP callback cannot send result {ex}")
|
||||
|
||||
def v2_playbook_on_play_start(self, play):
|
||||
'''
|
||||
@@ -170,17 +169,16 @@ class CallbackModule(CallbackBase):
|
||||
critical = warning = 0
|
||||
for host in hosts:
|
||||
stat = stats.summarize(host)
|
||||
gstats += "'%s_ok'=%d '%s_changed'=%d \
|
||||
'%s_unreachable'=%d '%s_failed'=%d " % \
|
||||
(host, stat['ok'], host, stat['changed'],
|
||||
host, stat['unreachable'], host, stat['failures'])
|
||||
gstats += (
|
||||
f"'{host}_ok'={stat['ok']} '{host}_changed'={stat['changed']} '{host}_unreachable'={stat['unreachable']} '{host}_failed'={stat['failures']} "
|
||||
)
|
||||
# Critical when failed tasks or unreachable host
|
||||
critical += stat['failures']
|
||||
critical += stat['unreachable']
|
||||
# Warning when changed tasks
|
||||
warning += stat['changed']
|
||||
|
||||
msg = "%s | %s" % (name, gstats)
|
||||
msg = f"{name} | {gstats}"
|
||||
if critical:
|
||||
# Send Critical
|
||||
self._send_nrdp(self.CRITICAL, msg)
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Unknown (!UNKNOWN)
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Victor Martinez (@v1v) <VictorMartinezRubio@gmail.com>
|
||||
@@ -137,9 +136,8 @@ import getpass
|
||||
import json
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
from time import time_ns
|
||||
|
||||
from collections import OrderedDict
|
||||
from os.path import basename
|
||||
@@ -165,31 +163,12 @@ try:
|
||||
from opentelemetry.sdk.trace.export.in_memory_span_exporter import (
|
||||
InMemorySpanExporter
|
||||
)
|
||||
# Support for opentelemetry-api <= 1.12
|
||||
try:
|
||||
from opentelemetry.util._time import _time_ns
|
||||
except ImportError as imp_exc:
|
||||
OTEL_LIBRARY_TIME_NS_ERROR = imp_exc
|
||||
else:
|
||||
OTEL_LIBRARY_TIME_NS_ERROR = None
|
||||
|
||||
except ImportError as imp_exc:
|
||||
OTEL_LIBRARY_IMPORT_ERROR = imp_exc
|
||||
OTEL_LIBRARY_TIME_NS_ERROR = imp_exc
|
||||
else:
|
||||
OTEL_LIBRARY_IMPORT_ERROR = None
|
||||
|
||||
|
||||
if sys.version_info >= (3, 7):
|
||||
time_ns = time.time_ns
|
||||
elif not OTEL_LIBRARY_TIME_NS_ERROR:
|
||||
time_ns = _time_ns
|
||||
else:
|
||||
def time_ns():
|
||||
# Support versions older than 3.7 with opentelemetry-api > 1.12
|
||||
return int(time.time() * 1e9)
|
||||
|
||||
|
||||
class TaskData:
|
||||
"""
|
||||
Data about an individual task.
|
||||
@@ -210,7 +189,7 @@ class TaskData:
|
||||
if host.uuid in self.host_data:
|
||||
if host.status == 'included':
|
||||
# concatenate task include output from multiple items
|
||||
host.result = '%s\n%s' % (self.host_data[host.uuid].result, host.result)
|
||||
host.result = f'{self.host_data[host.uuid].result}\n{host.result}'
|
||||
else:
|
||||
return
|
||||
|
||||
@@ -348,7 +327,7 @@ class OpenTelemetrySource(object):
|
||||
def update_span_data(self, task_data, host_data, span, disable_logs, disable_attributes_in_logs):
|
||||
""" update the span with the given TaskData and HostData """
|
||||
|
||||
name = '[%s] %s: %s' % (host_data.name, task_data.play, task_data.name)
|
||||
name = f'[{host_data.name}] {task_data.play}: {task_data.name}'
|
||||
|
||||
message = 'success'
|
||||
res = {}
|
||||
@@ -471,7 +450,7 @@ class OpenTelemetrySource(object):
|
||||
def get_error_message_from_results(results, action):
|
||||
for result in results:
|
||||
if result.get('failed', False):
|
||||
return ('{0}({1}) - {2}').format(action, result.get('item', 'none'), OpenTelemetrySource.get_error_message(result))
|
||||
return f"{action}({result.get('item', 'none')}) - {OpenTelemetrySource.get_error_message(result)}"
|
||||
|
||||
@staticmethod
|
||||
def _last_line(text):
|
||||
@@ -483,14 +462,14 @@ class OpenTelemetrySource(object):
|
||||
message = result.get('msg', 'failed')
|
||||
exception = result.get('exception')
|
||||
stderr = result.get('stderr')
|
||||
return ('message: "{0}"\nexception: "{1}"\nstderr: "{2}"').format(message, exception, stderr)
|
||||
return f"message: \"{message}\"\nexception: \"{exception}\"\nstderr: \"{stderr}\""
|
||||
|
||||
@staticmethod
|
||||
def enrich_error_message_from_results(results, action):
|
||||
message = ""
|
||||
for result in results:
|
||||
if result.get('failed', False):
|
||||
message = ('{0}({1}) - {2}\n{3}').format(action, result.get('item', 'none'), OpenTelemetrySource.enrich_error_message(result), message)
|
||||
message = f"{action}({result.get('item', 'none')}) - {OpenTelemetrySource.enrich_error_message(result)}\n{message}"
|
||||
return message
|
||||
|
||||
|
||||
@@ -536,8 +515,9 @@ class CallbackModule(CallbackBase):
|
||||
environment_variable = self.get_option('enable_from_environment')
|
||||
if environment_variable is not None and os.environ.get(environment_variable, 'false').lower() != 'true':
|
||||
self.disabled = True
|
||||
self._display.warning("The `enable_from_environment` option has been set and {0} is not enabled. "
|
||||
"Disabling the `opentelemetry` callback plugin.".format(environment_variable))
|
||||
self._display.warning(
|
||||
f"The `enable_from_environment` option has been set and {environment_variable} is not enabled. Disabling the `opentelemetry` callback plugin."
|
||||
)
|
||||
|
||||
self.hide_task_arguments = self.get_option('hide_task_arguments')
|
||||
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Unknown (!UNKNOWN)
|
||||
@@ -50,7 +49,7 @@ class CallbackModule(CallbackBase):
|
||||
self.synthesizer = get_bin_path('say')
|
||||
if platform.system() != 'Darwin':
|
||||
# 'say' binary available, it might be GNUstep tool which doesn't support 'voice' parameter
|
||||
self._display.warning("'say' executable found but system is '%s': ignoring voice parameter" % platform.system())
|
||||
self._display.warning(f"'say' executable found but system is '{platform.system()}': ignoring voice parameter")
|
||||
else:
|
||||
self.FAILED_VOICE = 'Zarvox'
|
||||
self.REGULAR_VOICE = 'Trinoids'
|
||||
@@ -69,7 +68,7 @@ class CallbackModule(CallbackBase):
|
||||
# ansible will not call any callback if disabled is set to True
|
||||
if not self.synthesizer:
|
||||
self.disabled = True
|
||||
self._display.warning("Unable to find either 'say' or 'espeak' executable, plugin %s disabled" % os.path.basename(__file__))
|
||||
self._display.warning(f"Unable to find either 'say' or 'espeak' executable, plugin {os.path.basename(__file__)} disabled")
|
||||
|
||||
def say(self, msg, voice):
|
||||
cmd = [self.synthesizer, msg]
|
||||
@@ -78,7 +77,7 @@ class CallbackModule(CallbackBase):
|
||||
subprocess.call(cmd)
|
||||
|
||||
def runner_on_failed(self, host, res, ignore_errors=False):
|
||||
self.say("Failure on host %s" % host, self.FAILED_VOICE)
|
||||
self.say(f"Failure on host {host}", self.FAILED_VOICE)
|
||||
|
||||
def runner_on_ok(self, host, res):
|
||||
self.say("pew", self.LASER_VOICE)
|
||||
@@ -87,13 +86,13 @@ class CallbackModule(CallbackBase):
|
||||
self.say("pew", self.LASER_VOICE)
|
||||
|
||||
def runner_on_unreachable(self, host, res):
|
||||
self.say("Failure on host %s" % host, self.FAILED_VOICE)
|
||||
self.say(f"Failure on host {host}", self.FAILED_VOICE)
|
||||
|
||||
def runner_on_async_ok(self, host, res, jid):
|
||||
self.say("pew", self.LASER_VOICE)
|
||||
|
||||
def runner_on_async_failed(self, host, res, jid):
|
||||
self.say("Failure on host %s" % host, self.FAILED_VOICE)
|
||||
self.say(f"Failure on host {host}", self.FAILED_VOICE)
|
||||
|
||||
def playbook_on_start(self):
|
||||
self.say("Running Playbook", self.REGULAR_VOICE)
|
||||
@@ -103,15 +102,15 @@ class CallbackModule(CallbackBase):
|
||||
|
||||
def playbook_on_task_start(self, name, is_conditional):
|
||||
if not is_conditional:
|
||||
self.say("Starting task: %s" % name, self.REGULAR_VOICE)
|
||||
self.say(f"Starting task: {name}", self.REGULAR_VOICE)
|
||||
else:
|
||||
self.say("Notifying task: %s" % name, self.REGULAR_VOICE)
|
||||
self.say(f"Notifying task: {name}", self.REGULAR_VOICE)
|
||||
|
||||
def playbook_on_setup(self):
|
||||
self.say("Gathering facts", self.REGULAR_VOICE)
|
||||
|
||||
def playbook_on_play_start(self, name):
|
||||
self.say("Starting play: %s" % name, self.HAPPY_VOICE)
|
||||
self.say(f"Starting play: {name}", self.HAPPY_VOICE)
|
||||
|
||||
def playbook_on_stats(self, stats):
|
||||
self.say("Play complete", self.HAPPY_VOICE)
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Unknown (!UNKNOWN)
|
||||
@@ -48,13 +47,13 @@ from ansible.module_utils.common.text.converters import to_text
|
||||
DONT_COLORIZE = False
|
||||
COLORS = {
|
||||
'normal': '\033[0m',
|
||||
'ok': '\033[{0}m'.format(C.COLOR_CODES[C.COLOR_OK]),
|
||||
'ok': f'\x1b[{C.COLOR_CODES[C.COLOR_OK]}m',
|
||||
'bold': '\033[1m',
|
||||
'not_so_bold': '\033[1m\033[34m',
|
||||
'changed': '\033[{0}m'.format(C.COLOR_CODES[C.COLOR_CHANGED]),
|
||||
'failed': '\033[{0}m'.format(C.COLOR_CODES[C.COLOR_ERROR]),
|
||||
'changed': f'\x1b[{C.COLOR_CODES[C.COLOR_CHANGED]}m',
|
||||
'failed': f'\x1b[{C.COLOR_CODES[C.COLOR_ERROR]}m',
|
||||
'endc': '\033[0m',
|
||||
'skipped': '\033[{0}m'.format(C.COLOR_CODES[C.COLOR_SKIP]),
|
||||
'skipped': f'\x1b[{C.COLOR_CODES[C.COLOR_SKIP]}m',
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +72,7 @@ def colorize(msg, color):
|
||||
if DONT_COLORIZE:
|
||||
return msg
|
||||
else:
|
||||
return '{0}{1}{2}'.format(COLORS[color], msg, COLORS['endc'])
|
||||
return f"{COLORS[color]}{msg}{COLORS['endc']}"
|
||||
|
||||
|
||||
class CallbackModule(CallbackBase):
|
||||
@@ -106,15 +105,15 @@ class CallbackModule(CallbackBase):
|
||||
line_length = 120
|
||||
if self.last_skipped:
|
||||
print()
|
||||
line = "# {0} ".format(task_name)
|
||||
msg = colorize("{0}{1}".format(line, '*' * (line_length - len(line))), 'bold')
|
||||
line = f"# {task_name} "
|
||||
msg = colorize(f"{line}{'*' * (line_length - len(line))}", 'bold')
|
||||
print(msg)
|
||||
|
||||
def _indent_text(self, text, indent_level):
|
||||
lines = text.splitlines()
|
||||
result_lines = []
|
||||
for l in lines:
|
||||
result_lines.append("{0}{1}".format(' ' * indent_level, l))
|
||||
result_lines.append(f"{' ' * indent_level}{l}")
|
||||
return '\n'.join(result_lines)
|
||||
|
||||
def _print_diff(self, diff, indent_level):
|
||||
@@ -147,19 +146,19 @@ class CallbackModule(CallbackBase):
|
||||
change_string = colorize('FAILED!!!', color)
|
||||
else:
|
||||
color = 'changed' if changed else 'ok'
|
||||
change_string = colorize("changed={0}".format(changed), color)
|
||||
change_string = colorize(f"changed={changed}", color)
|
||||
|
||||
msg = colorize(msg, color)
|
||||
|
||||
line_length = 120
|
||||
spaces = ' ' * (40 - len(name) - indent_level)
|
||||
line = "{0} * {1}{2}- {3}".format(' ' * indent_level, name, spaces, change_string)
|
||||
line = f"{' ' * indent_level} * {name}{spaces}- {change_string}"
|
||||
|
||||
if len(msg) < 50:
|
||||
line += ' -- {0}'.format(msg)
|
||||
print("{0} {1}---------".format(line, '-' * (line_length - len(line))))
|
||||
line += f' -- {msg}'
|
||||
print(f"{line} {'-' * (line_length - len(line))}---------")
|
||||
else:
|
||||
print("{0} {1}".format(line, '-' * (line_length - len(line))))
|
||||
print(f"{line} {'-' * (line_length - len(line))}")
|
||||
print(self._indent_text(msg, indent_level + 4))
|
||||
|
||||
if diff:
|
||||
@@ -239,8 +238,10 @@ class CallbackModule(CallbackBase):
|
||||
else:
|
||||
color = 'ok'
|
||||
|
||||
msg = '{0} : ok={1}\tchanged={2}\tfailed={3}\tunreachable={4}\trescued={5}\tignored={6}'.format(
|
||||
host, s['ok'], s['changed'], s['failures'], s['unreachable'], s['rescued'], s['ignored'])
|
||||
msg = (
|
||||
f"{host} : ok={s['ok']}\tchanged={s['changed']}\tfailed={s['failures']}\tunreachable="
|
||||
f"{s['unreachable']}\trescued={s['rescued']}\tignored={s['ignored']}"
|
||||
)
|
||||
print(colorize(msg, color))
|
||||
|
||||
def v2_runner_on_skipped(self, result, **kwargs):
|
||||
@@ -252,17 +253,15 @@ class CallbackModule(CallbackBase):
|
||||
line_length = 120
|
||||
spaces = ' ' * (31 - len(result._host.name) - 4)
|
||||
|
||||
line = " * {0}{1}- {2}".format(colorize(result._host.name, 'not_so_bold'),
|
||||
spaces,
|
||||
colorize("skipped", 'skipped'),)
|
||||
line = f" * {colorize(result._host.name, 'not_so_bold')}{spaces}- {colorize('skipped', 'skipped')}"
|
||||
|
||||
reason = result._result.get('skipped_reason', '') or \
|
||||
result._result.get('skip_reason', '')
|
||||
if len(reason) < 50:
|
||||
line += ' -- {0}'.format(reason)
|
||||
print("{0} {1}---------".format(line, '-' * (line_length - len(line))))
|
||||
line += f' -- {reason}'
|
||||
print(f"{line} {'-' * (line_length - len(line))}---------")
|
||||
else:
|
||||
print("{0} {1}".format(line, '-' * (line_length - len(line))))
|
||||
print(f"{line} {'-' * (line_length - len(line))}")
|
||||
print(self._indent_text(reason, 8))
|
||||
print(reason)
|
||||
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Unknown (!UNKNOWN)
|
||||
@@ -62,7 +61,6 @@ import os
|
||||
import uuid
|
||||
|
||||
from ansible import context
|
||||
from ansible.module_utils.common.text.converters import to_text
|
||||
from ansible.module_utils.urls import open_url
|
||||
from ansible.plugins.callback import CallbackBase
|
||||
|
||||
@@ -138,14 +136,13 @@ class CallbackModule(CallbackBase):
|
||||
headers=headers)
|
||||
return response.read()
|
||||
except Exception as e:
|
||||
self._display.warning(u'Could not submit message to Slack: %s' %
|
||||
to_text(e))
|
||||
self._display.warning(f'Could not submit message to Slack: {e}')
|
||||
|
||||
def v2_playbook_on_start(self, playbook):
|
||||
self.playbook_name = os.path.basename(playbook._file_name)
|
||||
|
||||
title = [
|
||||
'*Playbook initiated* (_%s_)' % self.guid
|
||||
f'*Playbook initiated* (_{self.guid}_)'
|
||||
]
|
||||
|
||||
invocation_items = []
|
||||
@@ -156,23 +153,23 @@ class CallbackModule(CallbackBase):
|
||||
subset = context.CLIARGS['subset']
|
||||
inventory = [os.path.abspath(i) for i in context.CLIARGS['inventory']]
|
||||
|
||||
invocation_items.append('Inventory: %s' % ', '.join(inventory))
|
||||
invocation_items.append(f"Inventory: {', '.join(inventory)}")
|
||||
if tags and tags != ['all']:
|
||||
invocation_items.append('Tags: %s' % ', '.join(tags))
|
||||
invocation_items.append(f"Tags: {', '.join(tags)}")
|
||||
if skip_tags:
|
||||
invocation_items.append('Skip Tags: %s' % ', '.join(skip_tags))
|
||||
invocation_items.append(f"Skip Tags: {', '.join(skip_tags)}")
|
||||
if subset:
|
||||
invocation_items.append('Limit: %s' % subset)
|
||||
invocation_items.append(f'Limit: {subset}')
|
||||
if extra_vars:
|
||||
invocation_items.append('Extra Vars: %s' %
|
||||
' '.join(extra_vars))
|
||||
invocation_items.append(f"Extra Vars: {' '.join(extra_vars)}")
|
||||
|
||||
title.append('by *%s*' % context.CLIARGS['remote_user'])
|
||||
title.append(f"by *{context.CLIARGS['remote_user']}*")
|
||||
|
||||
title.append('\n\n*%s*' % self.playbook_name)
|
||||
title.append(f'\n\n*{self.playbook_name}*')
|
||||
msg_items = [' '.join(title)]
|
||||
if invocation_items:
|
||||
msg_items.append('```\n%s\n```' % '\n'.join(invocation_items))
|
||||
_inv_item = '\n'.join(invocation_items)
|
||||
msg_items.append(f'```\n{_inv_item}\n```')
|
||||
|
||||
msg = '\n'.join(msg_items)
|
||||
|
||||
@@ -192,8 +189,8 @@ class CallbackModule(CallbackBase):
|
||||
def v2_playbook_on_play_start(self, play):
|
||||
"""Display Play start messages"""
|
||||
|
||||
name = play.name or 'Play name not specified (%s)' % play._uuid
|
||||
msg = '*Starting play* (_%s_)\n\n*%s*' % (self.guid, name)
|
||||
name = play.name or f'Play name not specified ({play._uuid})'
|
||||
msg = f'*Starting play* (_{self.guid}_)\n\n*{name}*'
|
||||
attachments = [
|
||||
{
|
||||
'fallback': msg,
|
||||
@@ -228,7 +225,7 @@ class CallbackModule(CallbackBase):
|
||||
|
||||
attachments = []
|
||||
msg_items = [
|
||||
'*Playbook Complete* (_%s_)' % self.guid
|
||||
f'*Playbook Complete* (_{self.guid}_)'
|
||||
]
|
||||
if failures or unreachable:
|
||||
color = 'danger'
|
||||
@@ -237,7 +234,7 @@ class CallbackModule(CallbackBase):
|
||||
color = 'good'
|
||||
msg_items.append('\n*Success!*')
|
||||
|
||||
msg_items.append('```\n%s\n```' % t)
|
||||
msg_items.append(f'```\n{t}\n```')
|
||||
|
||||
msg = '\n'.join(msg_items)
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
name: splunk
|
||||
@@ -153,15 +152,14 @@ class SplunkHTTPCollectorSource(object):
|
||||
data['ansible_result'] = result._result
|
||||
|
||||
# This wraps the json payload in and outer json event needed by Splunk
|
||||
jsondata = json.dumps(data, cls=AnsibleJSONEncoder, sort_keys=True)
|
||||
jsondata = '{"event":' + jsondata + "}"
|
||||
jsondata = json.dumps({"event": data}, cls=AnsibleJSONEncoder, sort_keys=True)
|
||||
|
||||
open_url(
|
||||
url,
|
||||
jsondata,
|
||||
headers={
|
||||
'Content-type': 'application/json',
|
||||
'Authorization': 'Splunk ' + authtoken
|
||||
'Authorization': f"Splunk {authtoken}"
|
||||
},
|
||||
method='POST',
|
||||
validate_certs=validate_certs
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
name: sumologic
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Unknown (!UNKNOWN)
|
||||
|
||||
@@ -5,9 +5,8 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
from __future__ import annotations
|
||||
|
||||
__metaclass__ = type
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
name: timestamp
|
||||
@@ -84,7 +83,7 @@ def banner(self, msg, color=None, cows=True):
|
||||
msg = to_text(msg)
|
||||
if self.b_cowsay and cows:
|
||||
try:
|
||||
self.banner_cowsay("%s @ %s" % (msg, timestamp))
|
||||
self.banner_cowsay(f"{msg} @ {timestamp}")
|
||||
return
|
||||
except OSError:
|
||||
self.warning("somebody cleverly deleted cowsay or something during the PB run. heh.")
|
||||
@@ -97,7 +96,7 @@ def banner(self, msg, color=None, cows=True):
|
||||
if star_len <= 3:
|
||||
star_len = 3
|
||||
stars = "*" * star_len
|
||||
self.display("\n%s %s %s" % (msg, stars, timestamp), color=color)
|
||||
self.display(f"\n{msg} {stars} {timestamp}", color=color)
|
||||
|
||||
|
||||
class CallbackModule(Default):
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
name: unixy
|
||||
@@ -67,24 +66,24 @@ class CallbackModule(CallbackModule_default):
|
||||
|
||||
def _process_result_output(self, result, msg):
|
||||
task_host = result._host.get_name()
|
||||
task_result = "%s %s" % (task_host, msg)
|
||||
task_result = f"{task_host} {msg}"
|
||||
|
||||
if self._run_is_verbose(result):
|
||||
task_result = "%s %s: %s" % (task_host, msg, self._dump_results(result._result, indent=4))
|
||||
task_result = f"{task_host} {msg}: {self._dump_results(result._result, indent=4)}"
|
||||
return task_result
|
||||
|
||||
if self.delegated_vars:
|
||||
task_delegate_host = self.delegated_vars['ansible_host']
|
||||
task_result = "%s -> %s %s" % (task_host, task_delegate_host, msg)
|
||||
task_result = f"{task_host} -> {task_delegate_host} {msg}"
|
||||
|
||||
if result._result.get('msg') and result._result.get('msg') != "All items completed":
|
||||
task_result += " | msg: " + to_text(result._result.get('msg'))
|
||||
task_result += f" | msg: {to_text(result._result.get('msg'))}"
|
||||
|
||||
if result._result.get('stdout'):
|
||||
task_result += " | stdout: " + result._result.get('stdout')
|
||||
task_result += f" | stdout: {result._result.get('stdout')}"
|
||||
|
||||
if result._result.get('stderr'):
|
||||
task_result += " | stderr: " + result._result.get('stderr')
|
||||
task_result += f" | stderr: {result._result.get('stderr')}"
|
||||
|
||||
return task_result
|
||||
|
||||
@@ -92,30 +91,30 @@ class CallbackModule(CallbackModule_default):
|
||||
self._get_task_display_name(task)
|
||||
if self.task_display_name is not None:
|
||||
if task.check_mode and self.get_option('check_mode_markers'):
|
||||
self._display.display("%s (check mode)..." % self.task_display_name)
|
||||
self._display.display(f"{self.task_display_name} (check mode)...")
|
||||
else:
|
||||
self._display.display("%s..." % self.task_display_name)
|
||||
self._display.display(f"{self.task_display_name}...")
|
||||
|
||||
def v2_playbook_on_handler_task_start(self, task):
|
||||
self._get_task_display_name(task)
|
||||
if self.task_display_name is not None:
|
||||
if task.check_mode and self.get_option('check_mode_markers'):
|
||||
self._display.display("%s (via handler in check mode)... " % self.task_display_name)
|
||||
self._display.display(f"{self.task_display_name} (via handler in check mode)... ")
|
||||
else:
|
||||
self._display.display("%s (via handler)... " % self.task_display_name)
|
||||
self._display.display(f"{self.task_display_name} (via handler)... ")
|
||||
|
||||
def v2_playbook_on_play_start(self, play):
|
||||
name = play.get_name().strip()
|
||||
if play.check_mode and self.get_option('check_mode_markers'):
|
||||
if name and play.hosts:
|
||||
msg = u"\n- %s (in check mode) on hosts: %s -" % (name, ",".join(play.hosts))
|
||||
msg = f"\n- {name} (in check mode) on hosts: {','.join(play.hosts)} -"
|
||||
else:
|
||||
msg = u"- check mode -"
|
||||
msg = "- check mode -"
|
||||
else:
|
||||
if name and play.hosts:
|
||||
msg = u"\n- %s on hosts: %s -" % (name, ",".join(play.hosts))
|
||||
msg = f"\n- {name} on hosts: {','.join(play.hosts)} -"
|
||||
else:
|
||||
msg = u"---"
|
||||
msg = "---"
|
||||
|
||||
self._display.display(msg)
|
||||
|
||||
@@ -126,7 +125,7 @@ class CallbackModule(CallbackModule_default):
|
||||
msg = "skipped"
|
||||
|
||||
task_result = self._process_result_output(result, msg)
|
||||
self._display.display(" " + task_result, display_color)
|
||||
self._display.display(f" {task_result}", display_color)
|
||||
else:
|
||||
return
|
||||
|
||||
@@ -136,10 +135,10 @@ class CallbackModule(CallbackModule_default):
|
||||
msg = "failed"
|
||||
item_value = self._get_item_label(result._result)
|
||||
if item_value:
|
||||
msg += " | item: %s" % (item_value,)
|
||||
msg += f" | item: {item_value}"
|
||||
|
||||
task_result = self._process_result_output(result, msg)
|
||||
self._display.display(" " + task_result, display_color, stderr=self.get_option('display_failed_stderr'))
|
||||
self._display.display(f" {task_result}", display_color, stderr=self.get_option('display_failed_stderr'))
|
||||
|
||||
def v2_runner_on_ok(self, result, msg="ok", display_color=C.COLOR_OK):
|
||||
self._preprocess_result(result)
|
||||
@@ -149,13 +148,13 @@ class CallbackModule(CallbackModule_default):
|
||||
msg = "done"
|
||||
item_value = self._get_item_label(result._result)
|
||||
if item_value:
|
||||
msg += " | item: %s" % (item_value,)
|
||||
msg += f" | item: {item_value}"
|
||||
display_color = C.COLOR_CHANGED
|
||||
task_result = self._process_result_output(result, msg)
|
||||
self._display.display(" " + task_result, display_color)
|
||||
self._display.display(f" {task_result}", display_color)
|
||||
elif self.get_option('display_ok_hosts'):
|
||||
task_result = self._process_result_output(result, msg)
|
||||
self._display.display(" " + task_result, display_color)
|
||||
self._display.display(f" {task_result}", display_color)
|
||||
|
||||
def v2_runner_item_on_skipped(self, result):
|
||||
self.v2_runner_on_skipped(result)
|
||||
@@ -173,7 +172,7 @@ class CallbackModule(CallbackModule_default):
|
||||
display_color = C.COLOR_UNREACHABLE
|
||||
task_result = self._process_result_output(result, msg)
|
||||
|
||||
self._display.display(" " + task_result, display_color, stderr=self.get_option('display_failed_stderr'))
|
||||
self._display.display(f" {task_result}", display_color, stderr=self.get_option('display_failed_stderr'))
|
||||
|
||||
def v2_on_file_diff(self, result):
|
||||
if result._task.loop and 'results' in result._result:
|
||||
@@ -195,25 +194,17 @@ class CallbackModule(CallbackModule_default):
|
||||
# TODO how else can we display these?
|
||||
t = stats.summarize(h)
|
||||
|
||||
self._display.display(u" %s : %s %s %s %s %s %s" % (
|
||||
hostcolor(h, t),
|
||||
colorize(u'ok', t['ok'], C.COLOR_OK),
|
||||
colorize(u'changed', t['changed'], C.COLOR_CHANGED),
|
||||
colorize(u'unreachable', t['unreachable'], C.COLOR_UNREACHABLE),
|
||||
colorize(u'failed', t['failures'], C.COLOR_ERROR),
|
||||
colorize(u'rescued', t['rescued'], C.COLOR_OK),
|
||||
colorize(u'ignored', t['ignored'], C.COLOR_WARN)),
|
||||
self._display.display(
|
||||
f" {hostcolor(h, t)} : {colorize('ok', t['ok'], C.COLOR_OK)} {colorize('changed', t['changed'], C.COLOR_CHANGED)} "
|
||||
f"{colorize('unreachable', t['unreachable'], C.COLOR_UNREACHABLE)} {colorize('failed', t['failures'], C.COLOR_ERROR)} "
|
||||
f"{colorize('rescued', t['rescued'], C.COLOR_OK)} {colorize('ignored', t['ignored'], C.COLOR_WARN)}",
|
||||
screen_only=True
|
||||
)
|
||||
|
||||
self._display.display(u" %s : %s %s %s %s %s %s" % (
|
||||
hostcolor(h, t, False),
|
||||
colorize(u'ok', t['ok'], None),
|
||||
colorize(u'changed', t['changed'], None),
|
||||
colorize(u'unreachable', t['unreachable'], None),
|
||||
colorize(u'failed', t['failures'], None),
|
||||
colorize(u'rescued', t['rescued'], None),
|
||||
colorize(u'ignored', t['ignored'], None)),
|
||||
self._display.display(
|
||||
f" {hostcolor(h, t, False)} : {colorize('ok', t['ok'], None)} {colorize('changed', t['changed'], None)} "
|
||||
f"{colorize('unreachable', t['unreachable'], None)} {colorize('failed', t['failures'], None)} {colorize('rescued', t['rescued'], None)} "
|
||||
f"{colorize('ignored', t['ignored'], None)}",
|
||||
log_only=True
|
||||
)
|
||||
if stats.custom and self.get_option('show_custom_stats'):
|
||||
@@ -223,12 +214,14 @@ class CallbackModule(CallbackModule_default):
|
||||
for k in sorted(stats.custom.keys()):
|
||||
if k == '_run':
|
||||
continue
|
||||
self._display.display('\t%s: %s' % (k, self._dump_results(stats.custom[k], indent=1).replace('\n', '')))
|
||||
stat_val = self._dump_results(stats.custom[k], indent=1).replace('\n', '')
|
||||
self._display.display(f'\t{k}: {stat_val}')
|
||||
|
||||
# print per run custom stats
|
||||
if '_run' in stats.custom:
|
||||
self._display.display("", screen_only=True)
|
||||
self._display.display('\tRUN: %s' % self._dump_results(stats.custom['_run'], indent=1).replace('\n', ''))
|
||||
stat_val_run = self._dump_results(stats.custom['_run'], indent=1).replace('\n', '')
|
||||
self._display.display(f'\tRUN: {stat_val_run}')
|
||||
self._display.display("", screen_only=True)
|
||||
|
||||
def v2_playbook_on_no_hosts_matched(self):
|
||||
@@ -239,23 +232,23 @@ class CallbackModule(CallbackModule_default):
|
||||
|
||||
def v2_playbook_on_start(self, playbook):
|
||||
if context.CLIARGS['check'] and self.get_option('check_mode_markers'):
|
||||
self._display.display("Executing playbook %s in check mode" % basename(playbook._file_name))
|
||||
self._display.display(f"Executing playbook {basename(playbook._file_name)} in check mode")
|
||||
else:
|
||||
self._display.display("Executing playbook %s" % basename(playbook._file_name))
|
||||
self._display.display(f"Executing playbook {basename(playbook._file_name)}")
|
||||
|
||||
# show CLI arguments
|
||||
if self._display.verbosity > 3:
|
||||
if context.CLIARGS.get('args'):
|
||||
self._display.display('Positional arguments: %s' % ' '.join(context.CLIARGS['args']),
|
||||
self._display.display(f"Positional arguments: {' '.join(context.CLIARGS['args'])}",
|
||||
color=C.COLOR_VERBOSE, screen_only=True)
|
||||
|
||||
for argument in (a for a in context.CLIARGS if a != 'args'):
|
||||
val = context.CLIARGS[argument]
|
||||
if val:
|
||||
self._display.vvvv('%s: %s' % (argument, val))
|
||||
self._display.vvvv(f'{argument}: {val}')
|
||||
|
||||
def v2_runner_retry(self, result):
|
||||
msg = " Retrying... (%d of %d)" % (result._result['attempts'], result._result['retries'])
|
||||
msg = f" Retrying... ({result._result['attempts']} of {result._result['retries']})"
|
||||
if self._run_is_verbose(result):
|
||||
msg += "Result was: %s" % self._dump_results(result._result)
|
||||
msg += f"Result was: {self._dump_results(result._result)}"
|
||||
self._display.display(msg, color=C.COLOR_DEBUG)
|
||||
|
||||
@@ -4,14 +4,18 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Unknown (!UNKNOWN)
|
||||
name: yaml
|
||||
type: stdout
|
||||
short_description: YAML-ized Ansible screen output
|
||||
deprecated:
|
||||
removed_in: 13.0.0
|
||||
why: Starting in ansible-core 2.13, the P(ansible.builtin.default#callback) callback has support for printing output in
|
||||
YAML format.
|
||||
alternative: Use O(ansible.builtin.default#callback:result_format=yaml).
|
||||
description:
|
||||
- Ansible output that can be quite a bit easier to read than the default JSON formatting.
|
||||
extends_documentation_fragment:
|
||||
@@ -33,9 +37,9 @@ import yaml
|
||||
import json
|
||||
import re
|
||||
import string
|
||||
from collections.abc import Mapping, Sequence
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_text
|
||||
from ansible.parsing.yaml.dumper import AnsibleDumper
|
||||
from ansible.plugins.callback import strip_internal_keys, module_response_deepcopy
|
||||
from ansible.plugins.callback.default import CallbackModule as Default
|
||||
|
||||
@@ -43,83 +47,35 @@ from ansible.plugins.callback.default import CallbackModule as Default
|
||||
# from http://stackoverflow.com/a/15423007/115478
|
||||
def should_use_block(value):
|
||||
"""Returns true if string should be in block format"""
|
||||
for c in u"\u000a\u000d\u001c\u001d\u001e\u0085\u2028\u2029":
|
||||
for c in "\u000a\u000d\u001c\u001d\u001e\u0085\u2028\u2029":
|
||||
if c in value:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def adjust_str_value_for_block(value):
|
||||
# we care more about readable than accuracy, so...
|
||||
# ...no trailing space
|
||||
value = value.rstrip()
|
||||
# ...and non-printable characters
|
||||
value = ''.join(x for x in value if x in string.printable or ord(x) >= 0xA0)
|
||||
# ...tabs prevent blocks from expanding
|
||||
value = value.expandtabs()
|
||||
# ...and odd bits of whitespace
|
||||
value = re.sub(r'[\x0b\x0c\r]', '', value)
|
||||
# ...as does trailing space
|
||||
value = re.sub(r' +\n', '\n', value)
|
||||
return value
|
||||
|
||||
|
||||
def create_string_node(tag, value, style, default_style):
|
||||
if style is None:
|
||||
if should_use_block(value):
|
||||
style = '|'
|
||||
value = adjust_str_value_for_block(value)
|
||||
else:
|
||||
style = default_style
|
||||
return yaml.representer.ScalarNode(tag, value, style=style)
|
||||
|
||||
|
||||
try:
|
||||
from ansible.module_utils.common.yaml import HAS_LIBYAML
|
||||
# import below was added in https://github.com/ansible/ansible/pull/85039,
|
||||
# first contained in ansible-core 2.19.0b2:
|
||||
from ansible.utils.vars import transform_to_native_types
|
||||
|
||||
if HAS_LIBYAML:
|
||||
from yaml.cyaml import CSafeDumper as SafeDumper
|
||||
else:
|
||||
from yaml import SafeDumper
|
||||
|
||||
class MyDumper(SafeDumper):
|
||||
def represent_scalar(self, tag, value, style=None):
|
||||
"""Uses block style for multi-line strings"""
|
||||
node = create_string_node(tag, value, style, self.default_style)
|
||||
if self.alias_key is not None:
|
||||
self.represented_objects[self.alias_key] = node
|
||||
return node
|
||||
|
||||
except ImportError:
|
||||
# In case transform_to_native_types cannot be imported, we either have ansible-core 2.19.0b1
|
||||
# (or some random commit from the devel or stable-2.19 branch after merging the DT changes
|
||||
# and before transform_to_native_types was added), or we have a version without the DT changes.
|
||||
|
||||
# Here we simply assume we have a version without the DT changes, and thus can continue as
|
||||
# with ansible-core 2.18 and before.
|
||||
|
||||
transform_to_native_types = None
|
||||
|
||||
from ansible.parsing.yaml.dumper import AnsibleDumper
|
||||
|
||||
class MyDumper(AnsibleDumper): # pylint: disable=inherit-non-class
|
||||
def represent_scalar(self, tag, value, style=None):
|
||||
"""Uses block style for multi-line strings"""
|
||||
node = create_string_node(tag, value, style, self.default_style)
|
||||
if self.alias_key is not None:
|
||||
self.represented_objects[self.alias_key] = node
|
||||
return node
|
||||
|
||||
|
||||
def transform_recursively(value, transform):
|
||||
if isinstance(value, Mapping):
|
||||
return {transform(k): transform(v) for k, v in value.items()}
|
||||
if isinstance(value, Sequence) and not isinstance(value, (str, bytes)):
|
||||
return [transform(e) for e in value]
|
||||
return transform(value)
|
||||
class MyDumper(AnsibleDumper):
|
||||
def represent_scalar(self, tag, value, style=None):
|
||||
"""Uses block style for multi-line strings"""
|
||||
if style is None:
|
||||
if should_use_block(value):
|
||||
style = '|'
|
||||
# we care more about readable than accuracy, so...
|
||||
# ...no trailing space
|
||||
value = value.rstrip()
|
||||
# ...and non-printable characters
|
||||
value = ''.join(x for x in value if x in string.printable or ord(x) >= 0xA0)
|
||||
# ...tabs prevent blocks from expanding
|
||||
value = value.expandtabs()
|
||||
# ...and odd bits of whitespace
|
||||
value = re.sub(r'[\x0b\x0c\r]', '', value)
|
||||
# ...as does trailing space
|
||||
value = re.sub(r' +\n', '\n', value)
|
||||
else:
|
||||
style = self.default_style
|
||||
node = yaml.representer.ScalarNode(tag, value, style=style)
|
||||
if self.alias_key is not None:
|
||||
self.represented_objects[self.alias_key] = node
|
||||
return node
|
||||
|
||||
|
||||
class CallbackModule(Default):
|
||||
@@ -159,11 +115,11 @@ class CallbackModule(Default):
|
||||
|
||||
# put changed and skipped into a header line
|
||||
if 'changed' in abridged_result:
|
||||
dumped += 'changed=' + str(abridged_result['changed']).lower() + ' '
|
||||
dumped += f"changed={str(abridged_result['changed']).lower()} "
|
||||
del abridged_result['changed']
|
||||
|
||||
if 'skipped' in abridged_result:
|
||||
dumped += 'skipped=' + str(abridged_result['skipped']).lower() + ' '
|
||||
dumped += f"skipped={str(abridged_result['skipped']).lower()} "
|
||||
del abridged_result['skipped']
|
||||
|
||||
# if we already have stdout, we don't need stdout_lines
|
||||
@@ -176,8 +132,6 @@ class CallbackModule(Default):
|
||||
|
||||
if abridged_result:
|
||||
dumped += '\n'
|
||||
if transform_to_native_types is not None:
|
||||
abridged_result = transform_recursively(abridged_result, lambda v: transform_to_native_types(v, redact=False))
|
||||
dumped += to_text(yaml.dump(abridged_result, allow_unicode=True, width=1000, Dumper=MyDumper, default_flow_style=False))
|
||||
|
||||
# indent by a couple of spaces
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Maykel Moya (!UNKNOWN) <mmoya@speedyrails.com>
|
||||
@@ -81,7 +80,7 @@ from ansible.errors import AnsibleError
|
||||
from ansible.module_utils.basic import is_executable
|
||||
from ansible.module_utils.common.process import get_bin_path
|
||||
from ansible.module_utils.six.moves import shlex_quote
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_native
|
||||
from ansible.module_utils.common.text.converters import to_bytes
|
||||
from ansible.plugins.connection import ConnectionBase, BUFSIZE
|
||||
from ansible.utils.display import Display
|
||||
|
||||
@@ -107,15 +106,15 @@ class Connection(ConnectionBase):
|
||||
|
||||
# do some trivial checks for ensuring 'host' is actually a chroot'able dir
|
||||
if not os.path.isdir(self.chroot):
|
||||
raise AnsibleError("%s is not a directory" % self.chroot)
|
||||
raise AnsibleError(f"{self.chroot} is not a directory")
|
||||
|
||||
chrootsh = os.path.join(self.chroot, 'bin/sh')
|
||||
# Want to check for a usable bourne shell inside the chroot.
|
||||
# is_executable() == True is sufficient. For symlinks it
|
||||
# gets really complicated really fast. So we punt on finding that
|
||||
# out. As long as it's a symlink we assume that it will work
|
||||
# out. As long as it is a symlink we assume that it will work
|
||||
if not (is_executable(chrootsh) or (os.path.lexists(chrootsh) and os.path.islink(chrootsh))):
|
||||
raise AnsibleError("%s does not look like a chrootable dir (/bin/sh missing)" % self.chroot)
|
||||
raise AnsibleError(f"{self.chroot} does not look like a chrootable dir (/bin/sh missing)")
|
||||
|
||||
def _connect(self):
|
||||
""" connect to the chroot """
|
||||
@@ -130,7 +129,7 @@ class Connection(ConnectionBase):
|
||||
try:
|
||||
self.chroot_cmd = get_bin_path(self.get_option('chroot_exe'))
|
||||
except ValueError as e:
|
||||
raise AnsibleError(to_native(e))
|
||||
raise AnsibleError(str(e))
|
||||
|
||||
super(Connection, self)._connect()
|
||||
if not self._connected:
|
||||
@@ -148,7 +147,7 @@ class Connection(ConnectionBase):
|
||||
executable = self.get_option('executable')
|
||||
local_cmd = [self.chroot_cmd, self.chroot, executable, '-c', cmd]
|
||||
|
||||
display.vvv("EXEC %s" % local_cmd, host=self.chroot)
|
||||
display.vvv(f"EXEC {local_cmd}", host=self.chroot)
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
p = subprocess.Popen(local_cmd, shell=False, stdin=stdin,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
@@ -173,7 +172,7 @@ class Connection(ConnectionBase):
|
||||
exist in any given chroot. So for now we're choosing "/" instead.
|
||||
This also happens to be the former default.
|
||||
|
||||
Can revisit using $HOME instead if it's a problem
|
||||
Can revisit using $HOME instead if it is a problem
|
||||
"""
|
||||
if not remote_path.startswith(os.path.sep):
|
||||
remote_path = os.path.join(os.path.sep, remote_path)
|
||||
@@ -182,7 +181,7 @@ class Connection(ConnectionBase):
|
||||
def put_file(self, in_path, out_path):
|
||||
""" transfer a file from local to chroot """
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.chroot)
|
||||
display.vvv(f"PUT {in_path} TO {out_path}", host=self.chroot)
|
||||
|
||||
out_path = shlex_quote(self._prefix_login_path(out_path))
|
||||
try:
|
||||
@@ -192,27 +191,27 @@ class Connection(ConnectionBase):
|
||||
else:
|
||||
count = ''
|
||||
try:
|
||||
p = self._buffered_exec_command('dd of=%s bs=%s%s' % (out_path, BUFSIZE, count), stdin=in_file)
|
||||
p = self._buffered_exec_command(f'dd of={out_path} bs={BUFSIZE}{count}', stdin=in_file)
|
||||
except OSError:
|
||||
raise AnsibleError("chroot connection requires dd command in the chroot")
|
||||
try:
|
||||
stdout, stderr = p.communicate()
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
raise AnsibleError("failed to transfer file %s to %s" % (in_path, out_path))
|
||||
raise AnsibleError(f"failed to transfer file {in_path} to {out_path}")
|
||||
if p.returncode != 0:
|
||||
raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
|
||||
raise AnsibleError(f"failed to transfer file {in_path} to {out_path}:\n{stdout}\n{stderr}")
|
||||
except IOError:
|
||||
raise AnsibleError("file or module does not exist at: %s" % in_path)
|
||||
raise AnsibleError(f"file or module does not exist at: {in_path}")
|
||||
|
||||
def fetch_file(self, in_path, out_path):
|
||||
""" fetch a file from chroot to local """
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.chroot)
|
||||
display.vvv(f"FETCH {in_path} TO {out_path}", host=self.chroot)
|
||||
|
||||
in_path = shlex_quote(self._prefix_login_path(in_path))
|
||||
try:
|
||||
p = self._buffered_exec_command('dd if=%s bs=%s' % (in_path, BUFSIZE))
|
||||
p = self._buffered_exec_command(f'dd if={in_path} bs={BUFSIZE}')
|
||||
except OSError:
|
||||
raise AnsibleError("chroot connection requires dd command in the chroot")
|
||||
|
||||
@@ -224,10 +223,10 @@ class Connection(ConnectionBase):
|
||||
chunk = p.stdout.read(BUFSIZE)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
raise AnsibleError("failed to transfer file %s to %s" % (in_path, out_path))
|
||||
raise AnsibleError(f"failed to transfer file {in_path} to {out_path}")
|
||||
stdout, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
|
||||
raise AnsibleError(f"failed to transfer file {in_path} to {out_path}:\n{stdout}\n{stderr}")
|
||||
|
||||
def close(self):
|
||||
""" terminate the connection; nothing to do here """
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Michael Scherer (@mscherer) <misc@zarb.org>
|
||||
@@ -72,7 +71,7 @@ class Connection(ConnectionBase):
|
||||
raise AnsibleError("Internal Error: this module does not support optimized module pipelining")
|
||||
|
||||
# totally ignores privilege escalation
|
||||
display.vvv("EXEC %s" % cmd, host=self.host)
|
||||
display.vvv(f"EXEC {cmd}", host=self.host)
|
||||
p = self.client.command.run(cmd)[self.host]
|
||||
return p[0], p[1], p[2]
|
||||
|
||||
@@ -87,14 +86,14 @@ class Connection(ConnectionBase):
|
||||
""" transfer a file from local to remote """
|
||||
|
||||
out_path = self._normalize_path(out_path, '/')
|
||||
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.host)
|
||||
display.vvv(f"PUT {in_path} TO {out_path}", host=self.host)
|
||||
self.client.local.copyfile.send(in_path, out_path)
|
||||
|
||||
def fetch_file(self, in_path, out_path):
|
||||
""" fetch a file from remote to local """
|
||||
|
||||
in_path = self._normalize_path(in_path, '/')
|
||||
display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.host)
|
||||
display.vvv(f"FETCH {in_path} TO {out_path}", host=self.host)
|
||||
# need to use a tmp dir due to difference of semantic for getfile
|
||||
# ( who take a # directory as destination) and fetch_file, who
|
||||
# take a file directly
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Stéphane Graber (@stgraber)
|
||||
@@ -80,7 +79,7 @@ class Connection(ConnectionBase):
|
||||
super(Connection, self)._connect()
|
||||
|
||||
if not self._connected:
|
||||
self._display.vvv(u"ESTABLISH Incus CONNECTION FOR USER: root",
|
||||
self._display.vvv("ESTABLISH Incus CONNECTION FOR USER: root",
|
||||
host=self._instance())
|
||||
self._connected = True
|
||||
|
||||
@@ -93,14 +92,14 @@ class Connection(ConnectionBase):
|
||||
""" execute a command on the Incus host """
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
self._display.vvv(u"EXEC {0}".format(cmd),
|
||||
self._display.vvv(f"EXEC {cmd}",
|
||||
host=self._instance())
|
||||
|
||||
local_cmd = [
|
||||
self._incus_cmd,
|
||||
"--project", self.get_option("project"),
|
||||
"exec",
|
||||
"%s:%s" % (self.get_option("remote"), self._instance()),
|
||||
f"{self.get_option('remote')}:{self._instance()}",
|
||||
"--",
|
||||
self._play_context.executable, "-c", cmd]
|
||||
|
||||
@@ -114,12 +113,10 @@ class Connection(ConnectionBase):
|
||||
stderr = to_text(stderr)
|
||||
|
||||
if stderr == "Error: Instance is not running.\n":
|
||||
raise AnsibleConnectionFailure("instance not running: %s" %
|
||||
self._instance())
|
||||
raise AnsibleConnectionFailure(f"instance not running: {self._instance()}")
|
||||
|
||||
if stderr == "Error: Instance not found\n":
|
||||
raise AnsibleConnectionFailure("instance not found: %s" %
|
||||
self._instance())
|
||||
raise AnsibleConnectionFailure(f"instance not found: {self._instance()}")
|
||||
|
||||
return process.returncode, stdout, stderr
|
||||
|
||||
@@ -127,20 +124,18 @@ class Connection(ConnectionBase):
|
||||
""" put a file from local to Incus """
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
|
||||
self._display.vvv(u"PUT {0} TO {1}".format(in_path, out_path),
|
||||
self._display.vvv(f"PUT {in_path} TO {out_path}",
|
||||
host=self._instance())
|
||||
|
||||
if not os.path.isfile(to_bytes(in_path, errors='surrogate_or_strict')):
|
||||
raise AnsibleFileNotFound("input path is not a file: %s" % in_path)
|
||||
raise AnsibleFileNotFound(f"input path is not a file: {in_path}")
|
||||
|
||||
local_cmd = [
|
||||
self._incus_cmd,
|
||||
"--project", self.get_option("project"),
|
||||
"file", "push", "--quiet",
|
||||
in_path,
|
||||
"%s:%s/%s" % (self.get_option("remote"),
|
||||
self._instance(),
|
||||
out_path)]
|
||||
f"{self.get_option('remote')}:{self._instance()}/{out_path}"]
|
||||
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
|
||||
@@ -150,16 +145,14 @@ class Connection(ConnectionBase):
|
||||
""" fetch a file from Incus to local """
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
|
||||
self._display.vvv(u"FETCH {0} TO {1}".format(in_path, out_path),
|
||||
self._display.vvv(f"FETCH {in_path} TO {out_path}",
|
||||
host=self._instance())
|
||||
|
||||
local_cmd = [
|
||||
self._incus_cmd,
|
||||
"--project", self.get_option("project"),
|
||||
"file", "pull", "--quiet",
|
||||
"%s:%s/%s" % (self.get_option("remote"),
|
||||
self._instance(),
|
||||
in_path),
|
||||
f"{self.get_option('remote')}:{self._instance()}/{in_path}",
|
||||
out_path]
|
||||
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Stephan Lohse (!UNKNOWN) <dev-github@ploek.org>
|
||||
@@ -55,11 +54,12 @@ class Connection(Jail):
|
||||
|
||||
jail_uuid = self.get_jail_uuid()
|
||||
|
||||
kwargs[Jail.modified_jailname_key] = 'ioc-{0}'.format(jail_uuid)
|
||||
kwargs[Jail.modified_jailname_key] = f'ioc-{jail_uuid}'
|
||||
|
||||
display.vvv(u"Jail {iocjail} has been translated to {rawjail}".format(
|
||||
iocjail=self.ioc_jail, rawjail=kwargs[Jail.modified_jailname_key]),
|
||||
host=kwargs[Jail.modified_jailname_key])
|
||||
display.vvv(
|
||||
f"Jail {self.ioc_jail} has been translated to {kwargs[Jail.modified_jailname_key]}",
|
||||
host=kwargs[Jail.modified_jailname_key]
|
||||
)
|
||||
|
||||
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
|
||||
|
||||
@@ -81,6 +81,6 @@ class Connection(Jail):
|
||||
p.wait()
|
||||
|
||||
if p.returncode != 0:
|
||||
raise AnsibleError(u"iocage returned an error: {0}".format(stdout))
|
||||
raise AnsibleError(f"iocage returned an error: {stdout}")
|
||||
|
||||
return stdout.strip('\n')
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Ansible Core Team
|
||||
@@ -75,14 +74,14 @@ class Connection(ConnectionBase):
|
||||
self.jexec_cmd = self._search_executable('jexec')
|
||||
|
||||
if self.jail not in self.list_jails():
|
||||
raise AnsibleError("incorrect jail name %s" % self.jail)
|
||||
raise AnsibleError(f"incorrect jail name {self.jail}")
|
||||
|
||||
@staticmethod
|
||||
def _search_executable(executable):
|
||||
try:
|
||||
return get_bin_path(executable)
|
||||
except ValueError:
|
||||
raise AnsibleError("%s command not found in PATH" % executable)
|
||||
raise AnsibleError(f"{executable} command not found in PATH")
|
||||
|
||||
def list_jails(self):
|
||||
p = subprocess.Popen([self.jls_cmd, '-q', 'name'],
|
||||
@@ -97,7 +96,7 @@ class Connection(ConnectionBase):
|
||||
""" connect to the jail; nothing to do here """
|
||||
super(Connection, self)._connect()
|
||||
if not self._connected:
|
||||
display.vvv(u"ESTABLISH JAIL CONNECTION FOR USER: {0}".format(self._play_context.remote_user), host=self.jail)
|
||||
display.vvv(f"ESTABLISH JAIL CONNECTION FOR USER: {self._play_context.remote_user}", host=self.jail)
|
||||
self._connected = True
|
||||
|
||||
def _buffered_exec_command(self, cmd, stdin=subprocess.PIPE):
|
||||
@@ -115,11 +114,11 @@ class Connection(ConnectionBase):
|
||||
if self._play_context.remote_user is not None:
|
||||
local_cmd += ['-U', self._play_context.remote_user]
|
||||
# update HOME since -U does not update the jail environment
|
||||
set_env = 'HOME=~' + self._play_context.remote_user + ' '
|
||||
set_env = f"HOME=~{self._play_context.remote_user} "
|
||||
|
||||
local_cmd += [self.jail, self._play_context.executable, '-c', set_env + cmd]
|
||||
|
||||
display.vvv("EXEC %s" % (local_cmd,), host=self.jail)
|
||||
display.vvv(f"EXEC {local_cmd}", host=self.jail)
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
p = subprocess.Popen(local_cmd, shell=False, stdin=stdin,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
@@ -144,7 +143,7 @@ class Connection(ConnectionBase):
|
||||
exist in any given chroot. So for now we're choosing "/" instead.
|
||||
This also happens to be the former default.
|
||||
|
||||
Can revisit using $HOME instead if it's a problem
|
||||
Can revisit using $HOME instead if it is a problem
|
||||
"""
|
||||
if not remote_path.startswith(os.path.sep):
|
||||
remote_path = os.path.join(os.path.sep, remote_path)
|
||||
@@ -153,7 +152,7 @@ class Connection(ConnectionBase):
|
||||
def put_file(self, in_path, out_path):
|
||||
""" transfer a file from local to jail """
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.jail)
|
||||
display.vvv(f"PUT {in_path} TO {out_path}", host=self.jail)
|
||||
|
||||
out_path = shlex_quote(self._prefix_login_path(out_path))
|
||||
try:
|
||||
@@ -163,27 +162,27 @@ class Connection(ConnectionBase):
|
||||
else:
|
||||
count = ''
|
||||
try:
|
||||
p = self._buffered_exec_command('dd of=%s bs=%s%s' % (out_path, BUFSIZE, count), stdin=in_file)
|
||||
p = self._buffered_exec_command(f'dd of={out_path} bs={BUFSIZE}{count}', stdin=in_file)
|
||||
except OSError:
|
||||
raise AnsibleError("jail connection requires dd command in the jail")
|
||||
try:
|
||||
stdout, stderr = p.communicate()
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
raise AnsibleError("failed to transfer file %s to %s" % (in_path, out_path))
|
||||
raise AnsibleError(f"failed to transfer file {in_path} to {out_path}")
|
||||
if p.returncode != 0:
|
||||
raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, to_native(stdout), to_native(stderr)))
|
||||
raise AnsibleError(f"failed to transfer file {in_path} to {out_path}:\n{to_native(stdout)}\n{to_native(stderr)}")
|
||||
except IOError:
|
||||
raise AnsibleError("file or module does not exist at: %s" % in_path)
|
||||
raise AnsibleError(f"file or module does not exist at: {in_path}")
|
||||
|
||||
def fetch_file(self, in_path, out_path):
|
||||
""" fetch a file from jail to local """
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.jail)
|
||||
display.vvv(f"FETCH {in_path} TO {out_path}", host=self.jail)
|
||||
|
||||
in_path = shlex_quote(self._prefix_login_path(in_path))
|
||||
try:
|
||||
p = self._buffered_exec_command('dd if=%s bs=%s' % (in_path, BUFSIZE))
|
||||
p = self._buffered_exec_command(f'dd if={in_path} bs={BUFSIZE}')
|
||||
except OSError:
|
||||
raise AnsibleError("jail connection requires dd command in the jail")
|
||||
|
||||
@@ -195,10 +194,10 @@ class Connection(ConnectionBase):
|
||||
chunk = p.stdout.read(BUFSIZE)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
raise AnsibleError("failed to transfer file %s to %s" % (in_path, out_path))
|
||||
raise AnsibleError(f"failed to transfer file {in_path} to {out_path}")
|
||||
stdout, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, to_native(stdout), to_native(stderr)))
|
||||
raise AnsibleError(f"failed to transfer file {in_path} to {out_path}:\n{to_native(stdout)}\n{to_native(stderr)}")
|
||||
|
||||
def close(self):
|
||||
""" terminate the connection; nothing to do here """
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Joerg Thalheim (!UNKNOWN) <joerg@higgsboson.tk>
|
||||
@@ -82,7 +81,7 @@ class Connection(ConnectionBase):
|
||||
self._display.vvv("THIS IS A LOCAL LXC DIR", host=self.container_name)
|
||||
self.container = _lxc.Container(self.container_name)
|
||||
if self.container.state == "STOPPED":
|
||||
raise errors.AnsibleError("%s is not running" % self.container_name)
|
||||
raise errors.AnsibleError(f"{self.container_name} is not running")
|
||||
|
||||
@staticmethod
|
||||
def _communicate(pid, in_data, stdin, stdout, stderr):
|
||||
@@ -144,10 +143,10 @@ class Connection(ConnectionBase):
|
||||
read_stdin, write_stdin = os.pipe()
|
||||
kwargs['stdin'] = self._set_nonblocking(read_stdin)
|
||||
|
||||
self._display.vvv("EXEC %s" % (local_cmd), host=self.container_name)
|
||||
self._display.vvv(f"EXEC {local_cmd}", host=self.container_name)
|
||||
pid = self.container.attach(_lxc.attach_run_command, local_cmd, **kwargs)
|
||||
if pid == -1:
|
||||
msg = "failed to attach to container %s" % self.container_name
|
||||
msg = f"failed to attach to container {self.container_name}"
|
||||
raise errors.AnsibleError(msg)
|
||||
|
||||
write_stdout = os.close(write_stdout)
|
||||
@@ -174,18 +173,18 @@ class Connection(ConnectionBase):
|
||||
def put_file(self, in_path, out_path):
|
||||
''' transfer a file from local to lxc '''
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
self._display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.container_name)
|
||||
self._display.vvv(f"PUT {in_path} TO {out_path}", host=self.container_name)
|
||||
in_path = to_bytes(in_path, errors='surrogate_or_strict')
|
||||
out_path = to_bytes(out_path, errors='surrogate_or_strict')
|
||||
|
||||
if not os.path.exists(in_path):
|
||||
msg = "file or module does not exist: %s" % in_path
|
||||
msg = f"file or module does not exist: {in_path}"
|
||||
raise errors.AnsibleFileNotFound(msg)
|
||||
try:
|
||||
src_file = open(in_path, "rb")
|
||||
except IOError:
|
||||
traceback.print_exc()
|
||||
raise errors.AnsibleError("failed to open input file to %s" % in_path)
|
||||
raise errors.AnsibleError(f"failed to open input file to {in_path}")
|
||||
try:
|
||||
def write_file(args):
|
||||
with open(out_path, 'wb+') as dst_file:
|
||||
@@ -194,7 +193,7 @@ class Connection(ConnectionBase):
|
||||
self.container.attach_wait(write_file, None)
|
||||
except IOError:
|
||||
traceback.print_exc()
|
||||
msg = "failed to transfer file to %s" % out_path
|
||||
msg = f"failed to transfer file to {out_path}"
|
||||
raise errors.AnsibleError(msg)
|
||||
finally:
|
||||
src_file.close()
|
||||
@@ -202,7 +201,7 @@ class Connection(ConnectionBase):
|
||||
def fetch_file(self, in_path, out_path):
|
||||
''' fetch a file from lxc to local '''
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
self._display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.container_name)
|
||||
self._display.vvv(f"FETCH {in_path} TO {out_path}", host=self.container_name)
|
||||
in_path = to_bytes(in_path, errors='surrogate_or_strict')
|
||||
out_path = to_bytes(out_path, errors='surrogate_or_strict')
|
||||
|
||||
@@ -210,7 +209,7 @@ class Connection(ConnectionBase):
|
||||
dst_file = open(out_path, "wb")
|
||||
except IOError:
|
||||
traceback.print_exc()
|
||||
msg = "failed to open output file %s" % out_path
|
||||
msg = f"failed to open output file {out_path}"
|
||||
raise errors.AnsibleError(msg)
|
||||
try:
|
||||
def write_file(args):
|
||||
@@ -225,7 +224,7 @@ class Connection(ConnectionBase):
|
||||
self.container.attach_wait(write_file, None)
|
||||
except IOError:
|
||||
traceback.print_exc()
|
||||
msg = "failed to transfer file from %s to %s" % (in_path, out_path)
|
||||
msg = f"failed to transfer file from {in_path} to {out_path}"
|
||||
raise errors.AnsibleError(msg)
|
||||
finally:
|
||||
dst_file.close()
|
||||
|
||||
@@ -4,8 +4,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Matt Clay (@mattclay) <matt@mystile.com>
|
||||
@@ -86,26 +85,26 @@ class Connection(ConnectionBase):
|
||||
super(Connection, self)._connect()
|
||||
|
||||
if not self._connected:
|
||||
self._display.vvv(u"ESTABLISH LXD CONNECTION FOR USER: root", host=self._host())
|
||||
self._display.vvv("ESTABLISH LXD CONNECTION FOR USER: root", host=self._host())
|
||||
self._connected = True
|
||||
|
||||
def exec_command(self, cmd, in_data=None, sudoable=True):
|
||||
""" execute a command on the lxd host """
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
self._display.vvv(u"EXEC {0}".format(cmd), host=self._host())
|
||||
self._display.vvv(f"EXEC {cmd}", host=self._host())
|
||||
|
||||
local_cmd = [self._lxc_cmd]
|
||||
if self.get_option("project"):
|
||||
local_cmd.extend(["--project", self.get_option("project")])
|
||||
local_cmd.extend([
|
||||
"exec",
|
||||
"%s:%s" % (self.get_option("remote"), self._host()),
|
||||
f"{self.get_option('remote')}:{self._host()}",
|
||||
"--",
|
||||
self.get_option("executable"), "-c", cmd
|
||||
])
|
||||
|
||||
self._display.vvvvv(u"EXEC {0}".format(local_cmd), host=self._host())
|
||||
self._display.vvvvv(f"EXEC {local_cmd}", host=self._host())
|
||||
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
in_data = to_bytes(in_data, errors='surrogate_or_strict', nonstring='passthru')
|
||||
@@ -116,13 +115,13 @@ class Connection(ConnectionBase):
|
||||
stdout = to_text(stdout)
|
||||
stderr = to_text(stderr)
|
||||
|
||||
self._display.vvvvv(u"EXEC lxc output: {0} {1}".format(stdout, stderr), host=self._host())
|
||||
self._display.vvvvv(f"EXEC lxc output: {stdout} {stderr}", host=self._host())
|
||||
|
||||
if "is not running" in stderr:
|
||||
raise AnsibleConnectionFailure("instance not running: %s" % self._host())
|
||||
raise AnsibleConnectionFailure(f"instance not running: {self._host()}")
|
||||
|
||||
if stderr.strip() == "Error: Instance not found" or stderr.strip() == "error: not found":
|
||||
raise AnsibleConnectionFailure("instance not found: %s" % self._host())
|
||||
raise AnsibleConnectionFailure(f"instance not found: {self._host()}")
|
||||
|
||||
return process.returncode, stdout, stderr
|
||||
|
||||
@@ -130,10 +129,10 @@ class Connection(ConnectionBase):
|
||||
""" put a file from local to lxd """
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
|
||||
self._display.vvv(u"PUT {0} TO {1}".format(in_path, out_path), host=self._host())
|
||||
self._display.vvv(f"PUT {in_path} TO {out_path}", host=self._host())
|
||||
|
||||
if not os.path.isfile(to_bytes(in_path, errors='surrogate_or_strict')):
|
||||
raise AnsibleFileNotFound("input path is not a file: %s" % in_path)
|
||||
raise AnsibleFileNotFound(f"input path is not a file: {in_path}")
|
||||
|
||||
local_cmd = [self._lxc_cmd]
|
||||
if self.get_option("project"):
|
||||
@@ -141,7 +140,7 @@ class Connection(ConnectionBase):
|
||||
local_cmd.extend([
|
||||
"file", "push",
|
||||
in_path,
|
||||
"%s:%s/%s" % (self.get_option("remote"), self._host(), out_path)
|
||||
f"{self.get_option('remote')}:{self._host()}/{out_path}"
|
||||
])
|
||||
|
||||
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
|
||||
@@ -153,14 +152,14 @@ class Connection(ConnectionBase):
|
||||
""" fetch a file from lxd to local """
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
|
||||
self._display.vvv(u"FETCH {0} TO {1}".format(in_path, out_path), host=self._host())
|
||||
self._display.vvv(f"FETCH {in_path} TO {out_path}", host=self._host())
|
||||
|
||||
local_cmd = [self._lxc_cmd]
|
||||
if self.get_option("project"):
|
||||
local_cmd.extend(["--project", self.get_option("project")])
|
||||
local_cmd.extend([
|
||||
"file", "pull",
|
||||
"%s:%s/%s" % (self.get_option("remote"), self._host(), in_path),
|
||||
f"{self.get_option('remote')}:{self._host()}/{in_path}",
|
||||
out_path
|
||||
])
|
||||
|
||||
|
||||
857
plugins/connection/proxmox_pct_remote.py
Normal file
857
plugins/connection/proxmox_pct_remote.py
Normal file
@@ -0,0 +1,857 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Derived from ansible/plugins/connection/paramiko_ssh.py (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
|
||||
# Copyright (c) 2024 Nils Stein (@mietzen) <github.nstein@mailbox.org>
|
||||
# Copyright (c) 2024 Ansible Project
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Nils Stein (@mietzen) <github.nstein@mailbox.org>
|
||||
name: proxmox_pct_remote
|
||||
short_description: Run tasks in Proxmox LXC container instances using pct CLI via SSH
|
||||
requirements:
|
||||
- paramiko
|
||||
description:
|
||||
- Run commands or put/fetch files to an existing Proxmox LXC container using pct CLI via SSH.
|
||||
- Uses the Python SSH implementation (Paramiko) to connect to the Proxmox host.
|
||||
version_added: "10.3.0"
|
||||
options:
|
||||
remote_addr:
|
||||
description:
|
||||
- Address of the remote target.
|
||||
default: inventory_hostname
|
||||
type: string
|
||||
vars:
|
||||
- name: inventory_hostname
|
||||
- name: ansible_host
|
||||
- name: ansible_ssh_host
|
||||
- name: ansible_paramiko_host
|
||||
port:
|
||||
description: Remote port to connect to.
|
||||
type: int
|
||||
default: 22
|
||||
ini:
|
||||
- section: defaults
|
||||
key: remote_port
|
||||
- section: paramiko_connection
|
||||
key: remote_port
|
||||
env:
|
||||
- name: ANSIBLE_REMOTE_PORT
|
||||
- name: ANSIBLE_REMOTE_PARAMIKO_PORT
|
||||
vars:
|
||||
- name: ansible_port
|
||||
- name: ansible_ssh_port
|
||||
- name: ansible_paramiko_port
|
||||
keyword:
|
||||
- name: port
|
||||
remote_user:
|
||||
description:
|
||||
- User to login/authenticate as.
|
||||
- Can be set from the CLI via the C(--user) or C(-u) options.
|
||||
type: string
|
||||
vars:
|
||||
- name: ansible_user
|
||||
- name: ansible_ssh_user
|
||||
- name: ansible_paramiko_user
|
||||
env:
|
||||
- name: ANSIBLE_REMOTE_USER
|
||||
- name: ANSIBLE_PARAMIKO_REMOTE_USER
|
||||
ini:
|
||||
- section: defaults
|
||||
key: remote_user
|
||||
- section: paramiko_connection
|
||||
key: remote_user
|
||||
keyword:
|
||||
- name: remote_user
|
||||
password:
|
||||
description:
|
||||
- Secret used to either login the SSH server or as a passphrase for SSH keys that require it.
|
||||
- Can be set from the CLI via the C(--ask-pass) option.
|
||||
type: string
|
||||
vars:
|
||||
- name: ansible_password
|
||||
- name: ansible_ssh_pass
|
||||
- name: ansible_ssh_password
|
||||
- name: ansible_paramiko_pass
|
||||
- name: ansible_paramiko_password
|
||||
use_rsa_sha2_algorithms:
|
||||
description:
|
||||
- Whether or not to enable RSA SHA2 algorithms for pubkeys and hostkeys.
|
||||
- On paramiko versions older than 2.9, this only affects hostkeys.
|
||||
- For behavior matching paramiko<2.9 set this to V(false).
|
||||
vars:
|
||||
- name: ansible_paramiko_use_rsa_sha2_algorithms
|
||||
ini:
|
||||
- {key: use_rsa_sha2_algorithms, section: paramiko_connection}
|
||||
env:
|
||||
- {name: ANSIBLE_PARAMIKO_USE_RSA_SHA2_ALGORITHMS}
|
||||
default: true
|
||||
type: boolean
|
||||
host_key_auto_add:
|
||||
description: "Automatically add host keys to C(~/.ssh/known_hosts)."
|
||||
env:
|
||||
- name: ANSIBLE_PARAMIKO_HOST_KEY_AUTO_ADD
|
||||
ini:
|
||||
- key: host_key_auto_add
|
||||
section: paramiko_connection
|
||||
type: boolean
|
||||
look_for_keys:
|
||||
default: True
|
||||
description: "Set to V(false) to disable searching for private key files in C(~/.ssh/)."
|
||||
env:
|
||||
- name: ANSIBLE_PARAMIKO_LOOK_FOR_KEYS
|
||||
ini:
|
||||
- {key: look_for_keys, section: paramiko_connection}
|
||||
type: boolean
|
||||
proxy_command:
|
||||
default: ""
|
||||
description:
|
||||
- Proxy information for running the connection via a jumphost.
|
||||
type: string
|
||||
env:
|
||||
- name: ANSIBLE_PARAMIKO_PROXY_COMMAND
|
||||
ini:
|
||||
- {key: proxy_command, section: paramiko_connection}
|
||||
vars:
|
||||
- name: ansible_paramiko_proxy_command
|
||||
pty:
|
||||
default: True
|
||||
description: "C(sudo) usually requires a PTY, V(true) to give a PTY and V(false) to not give a PTY."
|
||||
env:
|
||||
- name: ANSIBLE_PARAMIKO_PTY
|
||||
ini:
|
||||
- section: paramiko_connection
|
||||
key: pty
|
||||
type: boolean
|
||||
record_host_keys:
|
||||
default: True
|
||||
description: "Save the host keys to a file."
|
||||
env:
|
||||
- name: ANSIBLE_PARAMIKO_RECORD_HOST_KEYS
|
||||
ini:
|
||||
- section: paramiko_connection
|
||||
key: record_host_keys
|
||||
type: boolean
|
||||
host_key_checking:
|
||||
description: "Set this to V(false) if you want to avoid host key checking by the underlying tools Ansible uses to connect to the host."
|
||||
type: boolean
|
||||
default: true
|
||||
env:
|
||||
- name: ANSIBLE_HOST_KEY_CHECKING
|
||||
- name: ANSIBLE_SSH_HOST_KEY_CHECKING
|
||||
- name: ANSIBLE_PARAMIKO_HOST_KEY_CHECKING
|
||||
ini:
|
||||
- section: defaults
|
||||
key: host_key_checking
|
||||
- section: paramiko_connection
|
||||
key: host_key_checking
|
||||
vars:
|
||||
- name: ansible_host_key_checking
|
||||
- name: ansible_ssh_host_key_checking
|
||||
- name: ansible_paramiko_host_key_checking
|
||||
use_persistent_connections:
|
||||
description: "Toggles the use of persistence for connections."
|
||||
type: boolean
|
||||
default: False
|
||||
env:
|
||||
- name: ANSIBLE_USE_PERSISTENT_CONNECTIONS
|
||||
ini:
|
||||
- section: defaults
|
||||
key: use_persistent_connections
|
||||
banner_timeout:
|
||||
type: float
|
||||
default: 30
|
||||
description:
|
||||
- Configures, in seconds, the amount of time to wait for the SSH
|
||||
banner to be presented. This option is supported by paramiko
|
||||
version 1.15.0 or newer.
|
||||
ini:
|
||||
- section: paramiko_connection
|
||||
key: banner_timeout
|
||||
env:
|
||||
- name: ANSIBLE_PARAMIKO_BANNER_TIMEOUT
|
||||
timeout:
|
||||
type: int
|
||||
default: 10
|
||||
description: Number of seconds until the plugin gives up on failing to establish a TCP connection.
|
||||
ini:
|
||||
- section: defaults
|
||||
key: timeout
|
||||
- section: ssh_connection
|
||||
key: timeout
|
||||
- section: paramiko_connection
|
||||
key: timeout
|
||||
env:
|
||||
- name: ANSIBLE_TIMEOUT
|
||||
- name: ANSIBLE_SSH_TIMEOUT
|
||||
- name: ANSIBLE_PARAMIKO_TIMEOUT
|
||||
vars:
|
||||
- name: ansible_ssh_timeout
|
||||
- name: ansible_paramiko_timeout
|
||||
cli:
|
||||
- name: timeout
|
||||
lock_file_timeout:
|
||||
type: int
|
||||
default: 60
|
||||
description: Number of seconds until the plugin gives up on trying to write a lock file when writing SSH known host keys.
|
||||
vars:
|
||||
- name: ansible_lock_file_timeout
|
||||
env:
|
||||
- name: ANSIBLE_LOCK_FILE_TIMEOUT
|
||||
private_key_file:
|
||||
description:
|
||||
- Path to private key file to use for authentication.
|
||||
type: string
|
||||
ini:
|
||||
- section: defaults
|
||||
key: private_key_file
|
||||
- section: paramiko_connection
|
||||
key: private_key_file
|
||||
env:
|
||||
- name: ANSIBLE_PRIVATE_KEY_FILE
|
||||
- name: ANSIBLE_PARAMIKO_PRIVATE_KEY_FILE
|
||||
vars:
|
||||
- name: ansible_private_key_file
|
||||
- name: ansible_ssh_private_key_file
|
||||
- name: ansible_paramiko_private_key_file
|
||||
cli:
|
||||
- name: private_key_file
|
||||
option: "--private-key"
|
||||
vmid:
|
||||
description:
|
||||
- LXC Container ID
|
||||
type: int
|
||||
vars:
|
||||
- name: proxmox_vmid
|
||||
proxmox_become_method:
|
||||
description:
|
||||
- Become command used in proxmox
|
||||
type: str
|
||||
default: sudo
|
||||
vars:
|
||||
- name: proxmox_become_method
|
||||
notes:
|
||||
- >
|
||||
When NOT using this plugin as root, you need to have a become mechanism,
|
||||
e.g. C(sudo), installed on Proxmox and setup so we can run it without prompting for the password.
|
||||
Inside the container, we need a shell, for example C(sh) and the C(cat) command to be available in the C(PATH) for this plugin to work.
|
||||
"""
|
||||
|
||||
EXAMPLES = r"""
|
||||
# --------------------------------------------------------------
|
||||
# Setup sudo with password less access to pct for user 'ansible':
|
||||
# --------------------------------------------------------------
|
||||
#
|
||||
# Open a Proxmox root shell and execute:
|
||||
# $ useradd -d /opt/ansible-pct -r -m -s /bin/sh ansible
|
||||
# $ mkdir -p /opt/ansible-pct/.ssh
|
||||
# $ ssh-keygen -t ed25519 -C 'ansible' -N "" -f /opt/ansible-pct/.ssh/ansible <<< y > /dev/null
|
||||
# $ cat /opt/ansible-pct/.ssh/ansible
|
||||
# $ mv /opt/ansible-pct/.ssh/ansible.pub /opt/ansible-pct/.ssh/authorized-keys
|
||||
# $ rm /opt/ansible-pct/.ssh/ansible*
|
||||
# $ chown -R ansible:ansible /opt/ansible-pct/.ssh
|
||||
# $ chmod 700 /opt/ansible-pct/.ssh
|
||||
# $ chmod 600 /opt/ansible-pct/.ssh/authorized-keys
|
||||
# $ echo 'ansible ALL = (root) NOPASSWD: /usr/sbin/pct' > /etc/sudoers.d/ansible_pct
|
||||
#
|
||||
# Save the displayed private key and add it to your ssh-agent
|
||||
#
|
||||
# Or use ansible:
|
||||
# ---
|
||||
# - name: Setup ansible-pct user and configure environment on Proxmox host
|
||||
# hosts: proxmox
|
||||
# become: true
|
||||
# gather_facts: false
|
||||
#
|
||||
# tasks:
|
||||
# - name: Create ansible user
|
||||
# ansible.builtin.user:
|
||||
# name: ansible
|
||||
# comment: Ansible User
|
||||
# home: /opt/ansible-pct
|
||||
# shell: /bin/sh
|
||||
# create_home: true
|
||||
# system: true
|
||||
#
|
||||
# - name: Create .ssh directory
|
||||
# ansible.builtin.file:
|
||||
# path: /opt/ansible-pct/.ssh
|
||||
# state: directory
|
||||
# owner: ansible
|
||||
# group: ansible
|
||||
# mode: '0700'
|
||||
#
|
||||
# - name: Generate SSH key for ansible user
|
||||
# community.crypto.openssh_keypair:
|
||||
# path: /opt/ansible-pct/.ssh/ansible
|
||||
# type: ed25519
|
||||
# comment: 'ansible'
|
||||
# force: true
|
||||
# mode: '0600'
|
||||
# owner: ansible
|
||||
# group: ansible
|
||||
#
|
||||
# - name: Set public key as authorized key
|
||||
# ansible.builtin.copy:
|
||||
# src: /opt/ansible-pct/.ssh/ansible.pub
|
||||
# dest: /opt/ansible-pct/.ssh/authorized-keys
|
||||
# remote_src: yes
|
||||
# owner: ansible
|
||||
# group: ansible
|
||||
# mode: '0600'
|
||||
#
|
||||
# - name: Add sudoers entry for ansible user
|
||||
# ansible.builtin.copy:
|
||||
# content: 'ansible ALL = (root) NOPASSWD: /usr/sbin/pct'
|
||||
# dest: /etc/sudoers.d/ansible_pct
|
||||
# owner: root
|
||||
# group: root
|
||||
# mode: '0440'
|
||||
#
|
||||
# - name: Fetch private SSH key to localhost
|
||||
# ansible.builtin.fetch:
|
||||
# src: /opt/ansible-pct/.ssh/ansible
|
||||
# dest: ~/.ssh/proxmox_ansible_private_key
|
||||
# flat: yes
|
||||
# fail_on_missing: true
|
||||
#
|
||||
# - name: Clean up generated SSH keys
|
||||
# ansible.builtin.file:
|
||||
# path: /opt/ansible-pct/.ssh/ansible*
|
||||
# state: absent
|
||||
#
|
||||
# - name: Configure private key permissions on localhost
|
||||
# hosts: localhost
|
||||
# tasks:
|
||||
# - name: Set permissions for fetched private key
|
||||
# ansible.builtin.file:
|
||||
# path: ~/.ssh/proxmox_ansible_private_key
|
||||
# mode: '0600'
|
||||
#
|
||||
# --------------------------------
|
||||
# Static inventory file: hosts.yml
|
||||
# --------------------------------
|
||||
# all:
|
||||
# children:
|
||||
# lxc:
|
||||
# hosts:
|
||||
# container-1:
|
||||
# ansible_host: 10.0.0.10
|
||||
# proxmox_vmid: 100
|
||||
# ansible_connection: community.general.proxmox_pct_remote
|
||||
# ansible_user: ansible
|
||||
# container-2:
|
||||
# ansible_host: 10.0.0.10
|
||||
# proxmox_vmid: 200
|
||||
# ansible_connection: community.general.proxmox_pct_remote
|
||||
# ansible_user: ansible
|
||||
# proxmox:
|
||||
# hosts:
|
||||
# proxmox-1:
|
||||
# ansible_host: 10.0.0.10
|
||||
#
|
||||
#
|
||||
# ---------------------------------------------
|
||||
# Dynamic inventory file: inventory.proxmox.yml
|
||||
# ---------------------------------------------
|
||||
# plugin: community.general.proxmox
|
||||
# url: https://10.0.0.10:8006
|
||||
# validate_certs: false
|
||||
# user: ansible@pam
|
||||
# token_id: ansible
|
||||
# token_secret: !vault |
|
||||
# $ANSIBLE_VAULT;1.1;AES256
|
||||
# ...
|
||||
|
||||
# want_facts: true
|
||||
# exclude_nodes: true
|
||||
# filters:
|
||||
# - proxmox_vmtype == "lxc"
|
||||
# want_proxmox_nodes_ansible_host: false
|
||||
# compose:
|
||||
# ansible_host: "'10.0.0.10'"
|
||||
# ansible_connection: "'community.general.proxmox_pct_remote'"
|
||||
# ansible_user: "'ansible'"
|
||||
#
|
||||
#
|
||||
# ----------------------
|
||||
# Playbook: playbook.yml
|
||||
# ----------------------
|
||||
---
|
||||
- hosts: lxc
|
||||
# On nodes with many containers you might want to deactivate the devices facts
|
||||
# or set `gather_facts: false` if you don't need them.
|
||||
# More info on gathering fact subsets:
|
||||
# https://docs.ansible.com/ansible/latest/collections/ansible/builtin/setup_module.html
|
||||
#
|
||||
# gather_facts: true
|
||||
# gather_subset:
|
||||
# - "!devices"
|
||||
tasks:
|
||||
- name: Ping LXC container
|
||||
ansible.builtin.ping:
|
||||
"""
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import socket
|
||||
import tempfile
|
||||
import typing as t
|
||||
|
||||
from ansible.errors import (
|
||||
AnsibleAuthenticationFailure,
|
||||
AnsibleConnectionFailure,
|
||||
AnsibleError,
|
||||
)
|
||||
from ansible_collections.community.general.plugins.module_utils._filelock import FileLock, LockTimeout
|
||||
from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
|
||||
from ansible.module_utils.compat.paramiko import PARAMIKO_IMPORT_ERR, paramiko
|
||||
from ansible.module_utils.compat.version import LooseVersion
|
||||
from ansible.plugins.connection import ConnectionBase
|
||||
from ansible.utils.display import Display
|
||||
from ansible.utils.path import makedirs_safe
|
||||
from binascii import hexlify
|
||||
|
||||
|
||||
display = Display()
|
||||
|
||||
|
||||
def authenticity_msg(hostname: str, ktype: str, fingerprint: str) -> str:
|
||||
msg = f"""
|
||||
paramiko: The authenticity of host '{hostname}' can't be established.
|
||||
The {ktype} key fingerprint is {fingerprint}.
|
||||
Are you sure you want to continue connecting (yes/no)?
|
||||
"""
|
||||
return msg
|
||||
|
||||
|
||||
MissingHostKeyPolicy: type = object
|
||||
if paramiko:
|
||||
MissingHostKeyPolicy = paramiko.MissingHostKeyPolicy
|
||||
|
||||
|
||||
class MyAddPolicy(MissingHostKeyPolicy):
|
||||
"""
|
||||
Based on AutoAddPolicy in paramiko so we can determine when keys are added
|
||||
|
||||
and also prompt for input.
|
||||
|
||||
Policy for automatically adding the hostname and new host key to the
|
||||
local L{HostKeys} object, and saving it. This is used by L{SSHClient}.
|
||||
"""
|
||||
|
||||
def __init__(self, connection: Connection) -> None:
|
||||
self.connection = connection
|
||||
self._options = connection._options
|
||||
|
||||
def missing_host_key(self, client, hostname, key) -> None:
|
||||
|
||||
if all((self.connection.get_option('host_key_checking'), not self.connection.get_option('host_key_auto_add'))):
|
||||
|
||||
fingerprint = hexlify(key.get_fingerprint())
|
||||
ktype = key.get_name()
|
||||
|
||||
if self.connection.get_option('use_persistent_connections') or self.connection.force_persistence:
|
||||
# don't print the prompt string since the user cannot respond
|
||||
# to the question anyway
|
||||
raise AnsibleError(authenticity_msg(hostname, ktype, fingerprint)[1:92])
|
||||
|
||||
inp = to_text(
|
||||
display.prompt_until(authenticity_msg(hostname, ktype, fingerprint), private=False),
|
||||
errors='surrogate_or_strict'
|
||||
)
|
||||
|
||||
if inp.lower() not in ['yes', 'y', '']:
|
||||
raise AnsibleError('host connection rejected by user')
|
||||
|
||||
key._added_by_ansible_this_time = True
|
||||
|
||||
# existing implementation below:
|
||||
client._host_keys.add(hostname, key.get_name(), key)
|
||||
|
||||
# host keys are actually saved in close() function below
|
||||
# in order to control ordering.
|
||||
|
||||
|
||||
class Connection(ConnectionBase):
|
||||
""" SSH based connections (paramiko) to Proxmox pct """
|
||||
|
||||
transport = 'community.general.proxmox_pct_remote'
|
||||
_log_channel: str | None = None
|
||||
|
||||
def __init__(self, play_context, new_stdin, *args, **kwargs):
|
||||
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
|
||||
|
||||
def _set_log_channel(self, name: str) -> None:
|
||||
""" Mimic paramiko.SSHClient.set_log_channel """
|
||||
self._log_channel = name
|
||||
|
||||
def _parse_proxy_command(self, port: int = 22) -> dict[str, t.Any]:
|
||||
proxy_command = self.get_option('proxy_command') or None
|
||||
|
||||
sock_kwarg = {}
|
||||
if proxy_command:
|
||||
replacers = {
|
||||
'%h': self.get_option('remote_addr'),
|
||||
'%p': port,
|
||||
'%r': self.get_option('remote_user')
|
||||
}
|
||||
for find, replace in replacers.items():
|
||||
proxy_command = proxy_command.replace(find, str(replace))
|
||||
try:
|
||||
sock_kwarg = {'sock': paramiko.ProxyCommand(proxy_command)}
|
||||
display.vvv(f'CONFIGURE PROXY COMMAND FOR CONNECTION: {proxy_command}', host=self.get_option('remote_addr'))
|
||||
except AttributeError:
|
||||
display.warning('Paramiko ProxyCommand support unavailable. '
|
||||
'Please upgrade to Paramiko 1.9.0 or newer. '
|
||||
'Not using configured ProxyCommand')
|
||||
|
||||
return sock_kwarg
|
||||
|
||||
def _connect(self) -> Connection:
|
||||
""" activates the connection object """
|
||||
|
||||
if paramiko is None:
|
||||
raise AnsibleError(f'paramiko is not installed: {to_native(PARAMIKO_IMPORT_ERR)}')
|
||||
|
||||
port = self.get_option('port')
|
||||
display.vvv(f'ESTABLISH PARAMIKO SSH CONNECTION FOR USER: {self.get_option("remote_user")} on PORT {to_text(port)} TO {self.get_option("remote_addr")}',
|
||||
host=self.get_option('remote_addr'))
|
||||
|
||||
ssh = paramiko.SSHClient()
|
||||
|
||||
# Set pubkey and hostkey algorithms to disable, the only manipulation allowed currently
|
||||
# is keeping or omitting rsa-sha2 algorithms
|
||||
# default_keys: t.Tuple[str] = ()
|
||||
paramiko_preferred_pubkeys = getattr(paramiko.Transport, '_preferred_pubkeys', ())
|
||||
paramiko_preferred_hostkeys = getattr(paramiko.Transport, '_preferred_keys', ())
|
||||
use_rsa_sha2_algorithms = self.get_option('use_rsa_sha2_algorithms')
|
||||
disabled_algorithms: t.Dict[str, t.Iterable[str]] = {}
|
||||
if not use_rsa_sha2_algorithms:
|
||||
if paramiko_preferred_pubkeys:
|
||||
disabled_algorithms['pubkeys'] = tuple(a for a in paramiko_preferred_pubkeys if 'rsa-sha2' in a)
|
||||
if paramiko_preferred_hostkeys:
|
||||
disabled_algorithms['keys'] = tuple(a for a in paramiko_preferred_hostkeys if 'rsa-sha2' in a)
|
||||
|
||||
# override paramiko's default logger name
|
||||
if self._log_channel is not None:
|
||||
ssh.set_log_channel(self._log_channel)
|
||||
|
||||
self.keyfile = os.path.expanduser('~/.ssh/known_hosts')
|
||||
|
||||
if self.get_option('host_key_checking'):
|
||||
for ssh_known_hosts in ('/etc/ssh/ssh_known_hosts', '/etc/openssh/ssh_known_hosts'):
|
||||
try:
|
||||
ssh.load_system_host_keys(ssh_known_hosts)
|
||||
break
|
||||
except IOError:
|
||||
pass # file was not found, but not required to function
|
||||
except paramiko.hostkeys.InvalidHostKey as e:
|
||||
raise AnsibleConnectionFailure(f'Invalid host key: {to_text(e.line)}')
|
||||
try:
|
||||
ssh.load_system_host_keys()
|
||||
except paramiko.hostkeys.InvalidHostKey as e:
|
||||
raise AnsibleConnectionFailure(f'Invalid host key: {to_text(e.line)}')
|
||||
|
||||
ssh_connect_kwargs = self._parse_proxy_command(port)
|
||||
ssh.set_missing_host_key_policy(MyAddPolicy(self))
|
||||
conn_password = self.get_option('password')
|
||||
allow_agent = True
|
||||
|
||||
if conn_password is not None:
|
||||
allow_agent = False
|
||||
|
||||
try:
|
||||
key_filename = None
|
||||
if self.get_option('private_key_file'):
|
||||
key_filename = os.path.expanduser(self.get_option('private_key_file'))
|
||||
|
||||
# paramiko 2.2 introduced auth_timeout parameter
|
||||
if LooseVersion(paramiko.__version__) >= LooseVersion('2.2.0'):
|
||||
ssh_connect_kwargs['auth_timeout'] = self.get_option('timeout')
|
||||
|
||||
# paramiko 1.15 introduced banner timeout parameter
|
||||
if LooseVersion(paramiko.__version__) >= LooseVersion('1.15.0'):
|
||||
ssh_connect_kwargs['banner_timeout'] = self.get_option('banner_timeout')
|
||||
|
||||
ssh.connect(
|
||||
self.get_option('remote_addr').lower(),
|
||||
username=self.get_option('remote_user'),
|
||||
allow_agent=allow_agent,
|
||||
look_for_keys=self.get_option('look_for_keys'),
|
||||
key_filename=key_filename,
|
||||
password=conn_password,
|
||||
timeout=self.get_option('timeout'),
|
||||
port=port,
|
||||
disabled_algorithms=disabled_algorithms,
|
||||
**ssh_connect_kwargs,
|
||||
)
|
||||
except paramiko.ssh_exception.BadHostKeyException as e:
|
||||
raise AnsibleConnectionFailure(f'host key mismatch for {to_text(e.hostname)}')
|
||||
except paramiko.ssh_exception.AuthenticationException as e:
|
||||
msg = f'Failed to authenticate: {e}'
|
||||
raise AnsibleAuthenticationFailure(msg)
|
||||
except Exception as e:
|
||||
msg = to_text(e)
|
||||
if u'PID check failed' in msg:
|
||||
raise AnsibleError('paramiko version issue, please upgrade paramiko on the machine running ansible')
|
||||
elif u'Private key file is encrypted' in msg:
|
||||
msg = f'ssh {self.get_option("remote_user")}@{self.get_options("remote_addr")}:{port} : ' + \
|
||||
f'{msg}\nTo connect as a different user, use -u <username>.'
|
||||
raise AnsibleConnectionFailure(msg)
|
||||
else:
|
||||
raise AnsibleConnectionFailure(msg)
|
||||
self.ssh = ssh
|
||||
self._connected = True
|
||||
return self
|
||||
|
||||
def _any_keys_added(self) -> bool:
|
||||
for hostname, keys in self.ssh._host_keys.items():
|
||||
for keytype, key in keys.items():
|
||||
added_this_time = getattr(key, '_added_by_ansible_this_time', False)
|
||||
if added_this_time:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _save_ssh_host_keys(self, filename: str) -> None:
|
||||
"""
|
||||
not using the paramiko save_ssh_host_keys function as we want to add new SSH keys at the bottom so folks
|
||||
don't complain about it :)
|
||||
"""
|
||||
|
||||
if not self._any_keys_added():
|
||||
return
|
||||
|
||||
path = os.path.expanduser('~/.ssh')
|
||||
makedirs_safe(path)
|
||||
|
||||
with open(filename, 'w') as f:
|
||||
for hostname, keys in self.ssh._host_keys.items():
|
||||
for keytype, key in keys.items():
|
||||
# was f.write
|
||||
added_this_time = getattr(key, '_added_by_ansible_this_time', False)
|
||||
if not added_this_time:
|
||||
f.write(f'{hostname} {keytype} {key.get_base64()}\n')
|
||||
|
||||
for hostname, keys in self.ssh._host_keys.items():
|
||||
for keytype, key in keys.items():
|
||||
added_this_time = getattr(key, '_added_by_ansible_this_time', False)
|
||||
if added_this_time:
|
||||
f.write(f'{hostname} {keytype} {key.get_base64()}\n')
|
||||
|
||||
def _build_pct_command(self, cmd: str) -> str:
|
||||
cmd = ['/usr/sbin/pct', 'exec', str(self.get_option('vmid')), '--', cmd]
|
||||
if self.get_option('remote_user') != 'root':
|
||||
cmd = [self.get_option('proxmox_become_method')] + cmd
|
||||
display.vvv(f'INFO Running as non root user: {self.get_option("remote_user")}, trying to run pct with become method: ' +
|
||||
f'{self.get_option("proxmox_become_method")}',
|
||||
host=self.get_option('remote_addr'))
|
||||
return ' '.join(cmd)
|
||||
|
||||
def exec_command(self, cmd: str, in_data: bytes | None = None, sudoable: bool = True) -> tuple[int, bytes, bytes]:
|
||||
""" run a command on inside the LXC container """
|
||||
|
||||
cmd = self._build_pct_command(cmd)
|
||||
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
bufsize = 4096
|
||||
|
||||
try:
|
||||
self.ssh.get_transport().set_keepalive(5)
|
||||
chan = self.ssh.get_transport().open_session()
|
||||
except Exception as e:
|
||||
text_e = to_text(e)
|
||||
msg = 'Failed to open session'
|
||||
if text_e:
|
||||
msg += f': {text_e}'
|
||||
raise AnsibleConnectionFailure(to_native(msg))
|
||||
|
||||
# sudo usually requires a PTY (cf. requiretty option), therefore
|
||||
# we give it one by default (pty=True in ansible.cfg), and we try
|
||||
# to initialise from the calling environment when sudoable is enabled
|
||||
if self.get_option('pty') and sudoable:
|
||||
chan.get_pty(term=os.getenv('TERM', 'vt100'), width=int(os.getenv('COLUMNS', 0)), height=int(os.getenv('LINES', 0)))
|
||||
|
||||
display.vvv(f'EXEC {cmd}', host=self.get_option('remote_addr'))
|
||||
|
||||
cmd = to_bytes(cmd, errors='surrogate_or_strict')
|
||||
|
||||
no_prompt_out = b''
|
||||
no_prompt_err = b''
|
||||
become_output = b''
|
||||
|
||||
try:
|
||||
chan.exec_command(cmd)
|
||||
if self.become and self.become.expect_prompt():
|
||||
password_prompt = False
|
||||
become_success = False
|
||||
while not (become_success or password_prompt):
|
||||
display.debug('Waiting for Privilege Escalation input')
|
||||
|
||||
chunk = chan.recv(bufsize)
|
||||
display.debug(f'chunk is: {to_text(chunk)}')
|
||||
if not chunk:
|
||||
if b'unknown user' in become_output:
|
||||
n_become_user = to_native(self.become.get_option('become_user'))
|
||||
raise AnsibleError(f'user {n_become_user} does not exist')
|
||||
else:
|
||||
break
|
||||
# raise AnsibleError('ssh connection closed waiting for password prompt')
|
||||
become_output += chunk
|
||||
|
||||
# need to check every line because we might get lectured
|
||||
# and we might get the middle of a line in a chunk
|
||||
for line in become_output.splitlines(True):
|
||||
if self.become.check_success(line):
|
||||
become_success = True
|
||||
break
|
||||
elif self.become.check_password_prompt(line):
|
||||
password_prompt = True
|
||||
break
|
||||
|
||||
if password_prompt:
|
||||
if self.become:
|
||||
become_pass = self.become.get_option('become_pass')
|
||||
chan.sendall(to_bytes(become_pass, errors='surrogate_or_strict') + b'\n')
|
||||
else:
|
||||
raise AnsibleError('A password is required but none was supplied')
|
||||
else:
|
||||
no_prompt_out += become_output
|
||||
no_prompt_err += become_output
|
||||
|
||||
if in_data:
|
||||
for i in range(0, len(in_data), bufsize):
|
||||
chan.send(in_data[i:i + bufsize])
|
||||
chan.shutdown_write()
|
||||
elif in_data == b'':
|
||||
chan.shutdown_write()
|
||||
|
||||
except socket.timeout:
|
||||
raise AnsibleError('ssh timed out waiting for privilege escalation.\n' + to_text(become_output))
|
||||
|
||||
stdout = b''.join(chan.makefile('rb', bufsize))
|
||||
stderr = b''.join(chan.makefile_stderr('rb', bufsize))
|
||||
returncode = chan.recv_exit_status()
|
||||
|
||||
if 'pct: not found' in stderr.decode('utf-8'):
|
||||
raise AnsibleError(
|
||||
f'pct not found in path of host: {to_text(self.get_option("remote_addr"))}')
|
||||
|
||||
return (returncode, no_prompt_out + stdout, no_prompt_out + stderr)
|
||||
|
||||
def put_file(self, in_path: str, out_path: str) -> None:
|
||||
""" transfer a file from local to remote """
|
||||
|
||||
display.vvv(f'PUT {in_path} TO {out_path}', host=self.get_option('remote_addr'))
|
||||
try:
|
||||
with open(in_path, 'rb') as f:
|
||||
data = f.read()
|
||||
returncode, stdout, stderr = self.exec_command(
|
||||
' '.join([
|
||||
self._shell.executable, '-c',
|
||||
self._shell.quote(f'cat > {out_path}')]),
|
||||
in_data=data,
|
||||
sudoable=False)
|
||||
if returncode != 0:
|
||||
if 'cat: not found' in stderr.decode('utf-8'):
|
||||
raise AnsibleError(
|
||||
f'cat not found in path of container: {to_text(self.get_option("vmid"))}')
|
||||
raise AnsibleError(
|
||||
f'{to_text(stdout)}\n{to_text(stderr)}')
|
||||
except Exception as e:
|
||||
raise AnsibleError(
|
||||
f'error occurred while putting file from {in_path} to {out_path}!\n{to_text(e)}')
|
||||
|
||||
def fetch_file(self, in_path: str, out_path: str) -> None:
|
||||
""" save a remote file to the specified path """
|
||||
|
||||
display.vvv(f'FETCH {in_path} TO {out_path}', host=self.get_option('remote_addr'))
|
||||
try:
|
||||
returncode, stdout, stderr = self.exec_command(
|
||||
' '.join([
|
||||
self._shell.executable, '-c',
|
||||
self._shell.quote(f'cat {in_path}')]),
|
||||
sudoable=False)
|
||||
if returncode != 0:
|
||||
if 'cat: not found' in stderr.decode('utf-8'):
|
||||
raise AnsibleError(
|
||||
f'cat not found in path of container: {to_text(self.get_option("vmid"))}')
|
||||
raise AnsibleError(
|
||||
f'{to_text(stdout)}\n{to_text(stderr)}')
|
||||
with open(out_path, 'wb') as f:
|
||||
f.write(stdout)
|
||||
except Exception as e:
|
||||
raise AnsibleError(
|
||||
f'error occurred while fetching file from {in_path} to {out_path}!\n{to_text(e)}')
|
||||
|
||||
def reset(self) -> None:
|
||||
""" reset the connection """
|
||||
|
||||
if not self._connected:
|
||||
return
|
||||
self.close()
|
||||
self._connect()
|
||||
|
||||
def close(self) -> None:
|
||||
""" terminate the connection """
|
||||
|
||||
if self.get_option('host_key_checking') and self.get_option('record_host_keys') and self._any_keys_added():
|
||||
# add any new SSH host keys -- warning -- this could be slow
|
||||
# (This doesn't acquire the connection lock because it needs
|
||||
# to exclude only other known_hosts writers, not connections
|
||||
# that are starting up.)
|
||||
lockfile = os.path.basename(self.keyfile)
|
||||
dirname = os.path.dirname(self.keyfile)
|
||||
makedirs_safe(dirname)
|
||||
tmp_keyfile_name = None
|
||||
try:
|
||||
with FileLock().lock_file(lockfile, dirname, self.get_option('lock_file_timeout')):
|
||||
# just in case any were added recently
|
||||
|
||||
self.ssh.load_system_host_keys()
|
||||
self.ssh._host_keys.update(self.ssh._system_host_keys)
|
||||
|
||||
# gather information about the current key file, so
|
||||
# we can ensure the new file has the correct mode/owner
|
||||
|
||||
key_dir = os.path.dirname(self.keyfile)
|
||||
if os.path.exists(self.keyfile):
|
||||
key_stat = os.stat(self.keyfile)
|
||||
mode = key_stat.st_mode & 0o777
|
||||
uid = key_stat.st_uid
|
||||
gid = key_stat.st_gid
|
||||
else:
|
||||
mode = 0o644
|
||||
uid = os.getuid()
|
||||
gid = os.getgid()
|
||||
|
||||
# Save the new keys to a temporary file and move it into place
|
||||
# rather than rewriting the file. We set delete=False because
|
||||
# the file will be moved into place rather than cleaned up.
|
||||
|
||||
with tempfile.NamedTemporaryFile(dir=key_dir, delete=False) as tmp_keyfile:
|
||||
tmp_keyfile_name = tmp_keyfile.name
|
||||
os.chmod(tmp_keyfile_name, mode)
|
||||
os.chown(tmp_keyfile_name, uid, gid)
|
||||
self._save_ssh_host_keys(tmp_keyfile_name)
|
||||
|
||||
os.rename(tmp_keyfile_name, self.keyfile)
|
||||
except LockTimeout:
|
||||
raise AnsibleError(
|
||||
f'writing lock file for {self.keyfile} ran in to the timeout of {self.get_option("lock_file_timeout")}s')
|
||||
except paramiko.hostkeys.InvalidHostKey as e:
|
||||
raise AnsibleConnectionFailure(f'Invalid host key: {e.line}')
|
||||
except Exception as e:
|
||||
# unable to save keys, including scenario when key was invalid
|
||||
# and caught earlier
|
||||
raise AnsibleError(
|
||||
f'error occurred while writing SSH host keys!\n{to_text(e)}')
|
||||
finally:
|
||||
if tmp_keyfile_name is not None:
|
||||
pathlib.Path(tmp_keyfile_name).unlink(missing_ok=True)
|
||||
|
||||
self.ssh.close()
|
||||
self._connected = False
|
||||
@@ -8,8 +8,7 @@
|
||||
#
|
||||
# Written by: Kushal Das (https://github.com/kushaldas)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
@@ -77,7 +76,7 @@ class Connection(ConnectionBase):
|
||||
"""
|
||||
display.vvvv("CMD: ", cmd)
|
||||
if not cmd.endswith("\n"):
|
||||
cmd = cmd + "\n"
|
||||
cmd = f"{cmd}\n"
|
||||
local_cmd = []
|
||||
|
||||
# For dom0
|
||||
@@ -94,7 +93,7 @@ class Connection(ConnectionBase):
|
||||
|
||||
display.vvvv("Local cmd: ", local_cmd)
|
||||
|
||||
display.vvv("RUN %s" % (local_cmd,), host=self._remote_vmname)
|
||||
display.vvv(f"RUN {local_cmd}", host=self._remote_vmname)
|
||||
p = subprocess.Popen(local_cmd, shell=False, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
@@ -113,42 +112,42 @@ class Connection(ConnectionBase):
|
||||
"""Run specified command in a running QubesVM """
|
||||
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
|
||||
|
||||
display.vvvv("CMD IS: %s" % cmd)
|
||||
display.vvvv(f"CMD IS: {cmd}")
|
||||
|
||||
rc, stdout, stderr = self._qubes(cmd)
|
||||
|
||||
display.vvvvv("STDOUT %r STDERR %r" % (stdout, stderr))
|
||||
display.vvvvv(f"STDOUT {stdout!r} STDERR {stderr!r}")
|
||||
return rc, stdout, stderr
|
||||
|
||||
def put_file(self, in_path, out_path):
|
||||
""" Place a local file located in 'in_path' inside VM at 'out_path' """
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._remote_vmname)
|
||||
display.vvv(f"PUT {in_path} TO {out_path}", host=self._remote_vmname)
|
||||
|
||||
with open(in_path, "rb") as fobj:
|
||||
source_data = fobj.read()
|
||||
|
||||
retcode, dummy, dummy = self._qubes('cat > "{0}"\n'.format(out_path), source_data, "qubes.VMRootShell")
|
||||
retcode, dummy, dummy = self._qubes(f'cat > "{out_path}\"\n', source_data, "qubes.VMRootShell")
|
||||
# if qubes.VMRootShell service not supported, fallback to qubes.VMShell and
|
||||
# hope it will have appropriate permissions
|
||||
if retcode == 127:
|
||||
retcode, dummy, dummy = self._qubes('cat > "{0}"\n'.format(out_path), source_data)
|
||||
retcode, dummy, dummy = self._qubes(f'cat > "{out_path}\"\n', source_data)
|
||||
|
||||
if retcode != 0:
|
||||
raise AnsibleConnectionFailure('Failed to put_file to {0}'.format(out_path))
|
||||
raise AnsibleConnectionFailure(f'Failed to put_file to {out_path}')
|
||||
|
||||
def fetch_file(self, in_path, out_path):
|
||||
"""Obtain file specified via 'in_path' from the container and place it at 'out_path' """
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self._remote_vmname)
|
||||
display.vvv(f"FETCH {in_path} TO {out_path}", host=self._remote_vmname)
|
||||
|
||||
# We are running in dom0
|
||||
cmd_args_list = ["qvm-run", "--pass-io", self._remote_vmname, "cat {0}".format(in_path)]
|
||||
cmd_args_list = ["qvm-run", "--pass-io", self._remote_vmname, f"cat {in_path}"]
|
||||
with open(out_path, "wb") as fobj:
|
||||
p = subprocess.Popen(cmd_args_list, shell=False, stdout=fobj)
|
||||
p.communicate()
|
||||
if p.returncode != 0:
|
||||
raise AnsibleConnectionFailure('Failed to fetch file to {0}'.format(out_path))
|
||||
raise AnsibleConnectionFailure(f'Failed to fetch file to {out_path}')
|
||||
|
||||
def close(self):
|
||||
""" Closing the connection """
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Michael Scherer (@mscherer) <misc@zarb.org>
|
||||
@@ -59,11 +58,11 @@ class Connection(ConnectionBase):
|
||||
if in_data:
|
||||
raise errors.AnsibleError("Internal Error: this module does not support optimized module pipelining")
|
||||
|
||||
self._display.vvv("EXEC %s" % cmd, host=self.host)
|
||||
self._display.vvv(f"EXEC {cmd}", host=self.host)
|
||||
# need to add 'true;' to work around https://github.com/saltstack/salt/issues/28077
|
||||
res = self.client.cmd(self.host, 'cmd.exec_code_all', ['bash', 'true;' + cmd])
|
||||
res = self.client.cmd(self.host, 'cmd.exec_code_all', ['bash', f"true;{cmd}"])
|
||||
if self.host not in res:
|
||||
raise errors.AnsibleError("Minion %s didn't answer, check if salt-minion is running and the name is correct" % self.host)
|
||||
raise errors.AnsibleError(f"Minion {self.host} didn't answer, check if salt-minion is running and the name is correct")
|
||||
|
||||
p = res[self.host]
|
||||
return p['retcode'], p['stdout'], p['stderr']
|
||||
@@ -81,7 +80,7 @@ class Connection(ConnectionBase):
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
|
||||
out_path = self._normalize_path(out_path, '/')
|
||||
self._display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.host)
|
||||
self._display.vvv(f"PUT {in_path} TO {out_path}", host=self.host)
|
||||
with open(in_path, 'rb') as in_fh:
|
||||
content = in_fh.read()
|
||||
self.client.cmd(self.host, 'hashutil.base64_decodefile', [base64.b64encode(content), out_path])
|
||||
@@ -93,7 +92,7 @@ class Connection(ConnectionBase):
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
|
||||
in_path = self._normalize_path(in_path, '/')
|
||||
self._display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.host)
|
||||
self._display.vvv(f"FETCH {in_path} TO {out_path}", host=self.host)
|
||||
content = self.client.cmd(self.host, 'cp.get_file_str', [in_path])[self.host]
|
||||
open(out_path, 'wb').write(content)
|
||||
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
from __future__ import annotations
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
author: Ansible Core Team
|
||||
@@ -62,14 +61,14 @@ class Connection(ConnectionBase):
|
||||
self.zlogin_cmd = to_bytes(self._search_executable('zlogin'))
|
||||
|
||||
if self.zone not in self.list_zones():
|
||||
raise AnsibleError("incorrect zone name %s" % self.zone)
|
||||
raise AnsibleError(f"incorrect zone name {self.zone}")
|
||||
|
||||
@staticmethod
|
||||
def _search_executable(executable):
|
||||
try:
|
||||
return get_bin_path(executable)
|
||||
except ValueError:
|
||||
raise AnsibleError("%s command not found in PATH" % executable)
|
||||
raise AnsibleError(f"{executable} command not found in PATH")
|
||||
|
||||
def list_zones(self):
|
||||
process = subprocess.Popen([self.zoneadm_cmd, 'list', '-ip'],
|
||||
@@ -94,7 +93,7 @@ class Connection(ConnectionBase):
|
||||
|
||||
# stdout, stderr = p.communicate()
|
||||
path = process.stdout.readlines()[0].split(':')[3]
|
||||
return path + '/root'
|
||||
return f"{path}/root"
|
||||
|
||||
def _connect(self):
|
||||
""" connect to the zone; nothing to do here """
|
||||
@@ -117,7 +116,7 @@ class Connection(ConnectionBase):
|
||||
local_cmd = [self.zlogin_cmd, self.zone, cmd]
|
||||
local_cmd = map(to_bytes, local_cmd)
|
||||
|
||||
display.vvv("EXEC %s" % (local_cmd), host=self.zone)
|
||||
display.vvv(f"EXEC {local_cmd}", host=self.zone)
|
||||
p = subprocess.Popen(local_cmd, shell=False, stdin=stdin,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
@@ -140,7 +139,7 @@ class Connection(ConnectionBase):
|
||||
exist in any given chroot. So for now we're choosing "/" instead.
|
||||
This also happens to be the former default.
|
||||
|
||||
Can revisit using $HOME instead if it's a problem
|
||||
Can revisit using $HOME instead if it is a problem
|
||||
"""
|
||||
if not remote_path.startswith(os.path.sep):
|
||||
remote_path = os.path.join(os.path.sep, remote_path)
|
||||
@@ -149,7 +148,7 @@ class Connection(ConnectionBase):
|
||||
def put_file(self, in_path, out_path):
|
||||
""" transfer a file from local to zone """
|
||||
super(Connection, self).put_file(in_path, out_path)
|
||||
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self.zone)
|
||||
display.vvv(f"PUT {in_path} TO {out_path}", host=self.zone)
|
||||
|
||||
out_path = shlex_quote(self._prefix_login_path(out_path))
|
||||
try:
|
||||
@@ -159,27 +158,27 @@ class Connection(ConnectionBase):
|
||||
else:
|
||||
count = ''
|
||||
try:
|
||||
p = self._buffered_exec_command('dd of=%s bs=%s%s' % (out_path, BUFSIZE, count), stdin=in_file)
|
||||
p = self._buffered_exec_command(f'dd of={out_path} bs={BUFSIZE}{count}', stdin=in_file)
|
||||
except OSError:
|
||||
raise AnsibleError("jail connection requires dd command in the jail")
|
||||
try:
|
||||
stdout, stderr = p.communicate()
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
raise AnsibleError("failed to transfer file %s to %s" % (in_path, out_path))
|
||||
raise AnsibleError(f"failed to transfer file {in_path} to {out_path}")
|
||||
if p.returncode != 0:
|
||||
raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
|
||||
raise AnsibleError(f"failed to transfer file {in_path} to {out_path}:\n{stdout}\n{stderr}")
|
||||
except IOError:
|
||||
raise AnsibleError("file or module does not exist at: %s" % in_path)
|
||||
raise AnsibleError(f"file or module does not exist at: {in_path}")
|
||||
|
||||
def fetch_file(self, in_path, out_path):
|
||||
""" fetch a file from zone to local """
|
||||
super(Connection, self).fetch_file(in_path, out_path)
|
||||
display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self.zone)
|
||||
display.vvv(f"FETCH {in_path} TO {out_path}", host=self.zone)
|
||||
|
||||
in_path = shlex_quote(self._prefix_login_path(in_path))
|
||||
try:
|
||||
p = self._buffered_exec_command('dd if=%s bs=%s' % (in_path, BUFSIZE))
|
||||
p = self._buffered_exec_command(f'dd if={in_path} bs={BUFSIZE}')
|
||||
except OSError:
|
||||
raise AnsibleError("zone connection requires dd command in the zone")
|
||||
|
||||
@@ -191,10 +190,10 @@ class Connection(ConnectionBase):
|
||||
chunk = p.stdout.read(BUFSIZE)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
raise AnsibleError("failed to transfer file %s to %s" % (in_path, out_path))
|
||||
raise AnsibleError(f"failed to transfer file {in_path} to {out_path}")
|
||||
stdout, stderr = p.communicate()
|
||||
if p.returncode != 0:
|
||||
raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
|
||||
raise AnsibleError(f"failed to transfer file {in_path} to {out_path}:\n{stdout}\n{stderr}")
|
||||
|
||||
def close(self):
|
||||
""" terminate the connection; nothing to do here """
|
||||
|
||||
@@ -11,75 +11,73 @@ __metaclass__ = type
|
||||
class ModuleDocFragment(object):
|
||||
|
||||
# Alicloud only documentation fragment
|
||||
DOCUMENTATION = r'''
|
||||
DOCUMENTATION = r"""
|
||||
options:
|
||||
alicloud_access_key:
|
||||
description:
|
||||
- Alibaba Cloud access key. If not set then the value of environment variable E(ALICLOUD_ACCESS_KEY),
|
||||
E(ALICLOUD_ACCESS_KEY_ID) will be used instead.
|
||||
- Alibaba Cloud access key. If not set then the value of environment variable E(ALICLOUD_ACCESS_KEY), E(ALICLOUD_ACCESS_KEY_ID)
|
||||
will be used instead.
|
||||
aliases: ['access_key_id', 'access_key']
|
||||
type: str
|
||||
alicloud_secret_key:
|
||||
description:
|
||||
- Alibaba Cloud secret key. If not set then the value of environment variable E(ALICLOUD_SECRET_KEY),
|
||||
E(ALICLOUD_SECRET_ACCESS_KEY) will be used instead.
|
||||
- Alibaba Cloud secret key. If not set then the value of environment variable E(ALICLOUD_SECRET_KEY), E(ALICLOUD_SECRET_ACCESS_KEY)
|
||||
will be used instead.
|
||||
aliases: ['secret_access_key', 'secret_key']
|
||||
type: str
|
||||
alicloud_region:
|
||||
description:
|
||||
- The Alibaba Cloud region to use. If not specified then the value of environment variable
|
||||
E(ALICLOUD_REGION), E(ALICLOUD_REGION_ID) will be used instead.
|
||||
- The Alibaba Cloud region to use. If not specified then the value of environment variable E(ALICLOUD_REGION), E(ALICLOUD_REGION_ID)
|
||||
will be used instead.
|
||||
aliases: ['region', 'region_id']
|
||||
required: true
|
||||
type: str
|
||||
alicloud_security_token:
|
||||
description:
|
||||
- The Alibaba Cloud security token. If not specified then the value of environment variable
|
||||
E(ALICLOUD_SECURITY_TOKEN) will be used instead.
|
||||
- The Alibaba Cloud security token. If not specified then the value of environment variable E(ALICLOUD_SECURITY_TOKEN)
|
||||
will be used instead.
|
||||
aliases: ['security_token']
|
||||
type: str
|
||||
alicloud_assume_role:
|
||||
description:
|
||||
- If provided with a role ARN, Ansible will attempt to assume this role using the supplied credentials.
|
||||
- The nested assume_role block supports C(alicloud_assume_role_arn), C(alicloud_assume_role_session_name),
|
||||
C(alicloud_assume_role_session_expiration) and C(alicloud_assume_role_policy).
|
||||
- The nested assume_role block supports C(alicloud_assume_role_arn), C(alicloud_assume_role_session_name), C(alicloud_assume_role_session_expiration)
|
||||
and C(alicloud_assume_role_policy).
|
||||
type: dict
|
||||
aliases: ['assume_role']
|
||||
alicloud_assume_role_arn:
|
||||
description:
|
||||
- The Alibaba Cloud C(role_arn). The ARN of the role to assume. If ARN is set to an empty string,
|
||||
it does not perform role switching. It supports environment variable E(ALICLOUD_ASSUME_ROLE_ARN).
|
||||
ansible will execute with provided credentials.
|
||||
- The Alibaba Cloud C(role_arn). The ARN of the role to assume. If ARN is set to an empty string, it does not perform
|
||||
role switching. It supports environment variable E(ALICLOUD_ASSUME_ROLE_ARN). ansible will execute with provided credentials.
|
||||
aliases: ['assume_role_arn']
|
||||
type: str
|
||||
alicloud_assume_role_session_name:
|
||||
description:
|
||||
- The Alibaba Cloud session_name. The session name to use when assuming the role. If omitted,
|
||||
'ansible' is passed to the AssumeRole call as session name. It supports environment variable
|
||||
E(ALICLOUD_ASSUME_ROLE_SESSION_NAME).
|
||||
- The Alibaba Cloud session_name. The session name to use when assuming the role. If omitted, 'ansible' is passed to
|
||||
the AssumeRole call as session name. It supports environment variable E(ALICLOUD_ASSUME_ROLE_SESSION_NAME).
|
||||
aliases: ['assume_role_session_name']
|
||||
type: str
|
||||
alicloud_assume_role_session_expiration:
|
||||
description:
|
||||
- The Alibaba Cloud C(session_expiration). The time after which the established session for assuming
|
||||
role expires. Valid value range 900-3600 seconds. Default to 3600 (in this case Alicloud use own default
|
||||
value). It supports environment variable E(ALICLOUD_ASSUME_ROLE_SESSION_EXPIRATION).
|
||||
- The Alibaba Cloud C(session_expiration). The time after which the established session for assuming role expires. Valid
|
||||
value range 900-3600 seconds. Default to 3600 (in this case Alicloud use own default value). It supports environment
|
||||
variable E(ALICLOUD_ASSUME_ROLE_SESSION_EXPIRATION).
|
||||
aliases: ['assume_role_session_expiration']
|
||||
type: int
|
||||
ecs_role_name:
|
||||
description:
|
||||
- The RAM Role Name attached on a ECS instance for API operations. You can retrieve this from the 'Access Control'
|
||||
section of the Alibaba Cloud console.
|
||||
- If you're running Ansible from an ECS instance with RAM Instance using RAM Role, Ansible will just access the
|
||||
metadata U(http://100.100.100.200/latest/meta-data/ram/security-credentials/<ecs_role_name>) to obtain the STS
|
||||
credential. This is a preferred approach over any other when running in ECS as you can avoid hard coding
|
||||
credentials. Instead these are leased on-the-fly by Ansible which reduces the chance of leakage.
|
||||
- The RAM Role Name attached on a ECS instance for API operations. You can retrieve this from the 'Access Control' section
|
||||
of the Alibaba Cloud console.
|
||||
- If you are running Ansible from an ECS instance with RAM Instance using RAM Role, Ansible will just access the metadata
|
||||
U(http://100.100.100.200/latest/meta-data/ram/security-credentials/<ecs_role_name>) to obtain the STS credential.
|
||||
This is a preferred approach over any other when running in ECS as you can avoid hard coding credentials. Instead
|
||||
these are leased on-the-fly by Ansible which reduces the chance of leakage.
|
||||
aliases: ['role_name']
|
||||
type: str
|
||||
profile:
|
||||
description:
|
||||
- This is the Alicloud profile name as set in the shared credentials file. It can also be sourced from the
|
||||
E(ALICLOUD_PROFILE) environment variable.
|
||||
- This is the Alicloud profile name as set in the shared credentials file. It can also be sourced from the E(ALICLOUD_PROFILE)
|
||||
environment variable.
|
||||
type: str
|
||||
shared_credentials_file:
|
||||
description:
|
||||
@@ -88,22 +86,14 @@ options:
|
||||
- If this is not set and a profile is specified, C(~/.aliyun/config.json) will be used.
|
||||
type: str
|
||||
author:
|
||||
- "He Guimin (@xiaozhu36)"
|
||||
- "He Guimin (@xiaozhu36)"
|
||||
requirements:
|
||||
- "Python >= 3.6"
|
||||
- "Python >= 3.6"
|
||||
notes:
|
||||
- If parameters are not set within the module, the following
|
||||
environment variables can be used in decreasing order of precedence
|
||||
E(ALICLOUD_ACCESS_KEY) or E(ALICLOUD_ACCESS_KEY_ID),
|
||||
E(ALICLOUD_SECRET_KEY) or E(ALICLOUD_SECRET_ACCESS_KEY),
|
||||
E(ALICLOUD_REGION) or E(ALICLOUD_REGION_ID),
|
||||
E(ALICLOUD_SECURITY_TOKEN),
|
||||
E(ALICLOUD_ECS_ROLE_NAME),
|
||||
E(ALICLOUD_SHARED_CREDENTIALS_FILE),
|
||||
E(ALICLOUD_PROFILE),
|
||||
E(ALICLOUD_ASSUME_ROLE_ARN),
|
||||
E(ALICLOUD_ASSUME_ROLE_SESSION_NAME),
|
||||
E(ALICLOUD_ASSUME_ROLE_SESSION_EXPIRATION).
|
||||
- E(ALICLOUD_REGION) or E(ALICLOUD_REGION_ID) can be typically be used to specify the
|
||||
Alicloud region, when required, but this can also be configured in the footmark config file
|
||||
'''
|
||||
- If parameters are not set within the module, the following environment variables can be used in decreasing order of precedence
|
||||
E(ALICLOUD_ACCESS_KEY) or E(ALICLOUD_ACCESS_KEY_ID), E(ALICLOUD_SECRET_KEY) or E(ALICLOUD_SECRET_ACCESS_KEY), E(ALICLOUD_REGION)
|
||||
or E(ALICLOUD_REGION_ID), E(ALICLOUD_SECURITY_TOKEN), E(ALICLOUD_ECS_ROLE_NAME), E(ALICLOUD_SHARED_CREDENTIALS_FILE),
|
||||
E(ALICLOUD_PROFILE), E(ALICLOUD_ASSUME_ROLE_ARN), E(ALICLOUD_ASSUME_ROLE_SESSION_NAME), E(ALICLOUD_ASSUME_ROLE_SESSION_EXPIRATION).
|
||||
- E(ALICLOUD_REGION) or E(ALICLOUD_REGION_ID) can be typically be used to specify the Alicloud region, when required, but
|
||||
this can also be configured in the footmark config file.
|
||||
"""
|
||||
|
||||
@@ -11,22 +11,22 @@ __metaclass__ = type
|
||||
class ModuleDocFragment(object):
|
||||
|
||||
# Standard documentation fragment
|
||||
DOCUMENTATION = r'''
|
||||
DOCUMENTATION = r"""
|
||||
options: {}
|
||||
attributes:
|
||||
check_mode:
|
||||
description: Can run in C(check_mode) and return changed status prediction without modifying target.
|
||||
diff_mode:
|
||||
description: Will return details on what has changed (or possibly needs changing in C(check_mode)), when in diff mode.
|
||||
'''
|
||||
check_mode:
|
||||
description: Can run in C(check_mode) and return changed status prediction without modifying target.
|
||||
diff_mode:
|
||||
description: Will return details on what has changed (or possibly needs changing in C(check_mode)), when in diff mode.
|
||||
"""
|
||||
|
||||
PLATFORM = r'''
|
||||
PLATFORM = r"""
|
||||
options: {}
|
||||
attributes:
|
||||
platform:
|
||||
description: Target OS/families that can be operated against.
|
||||
support: N/A
|
||||
'''
|
||||
platform:
|
||||
description: Target OS/families that can be operated against.
|
||||
support: N/A
|
||||
"""
|
||||
|
||||
# Should be used together with the standard fragment
|
||||
INFO_MODULE = r'''
|
||||
@@ -42,23 +42,23 @@ attributes:
|
||||
- This action does not modify state.
|
||||
'''
|
||||
|
||||
CONN = r'''
|
||||
CONN = r"""
|
||||
options: {}
|
||||
attributes:
|
||||
become:
|
||||
description: Is usable alongside C(become) keywords.
|
||||
connection:
|
||||
description: Uses the target's configured connection information to execute code on it.
|
||||
delegation:
|
||||
description: Can be used in conjunction with C(delegate_to) and related keywords.
|
||||
'''
|
||||
become:
|
||||
description: Is usable alongside C(become) keywords.
|
||||
connection:
|
||||
description: Uses the target's configured connection information to execute code on it.
|
||||
delegation:
|
||||
description: Can be used in conjunction with C(delegate_to) and related keywords.
|
||||
"""
|
||||
|
||||
FACTS = r'''
|
||||
FACTS = r"""
|
||||
options: {}
|
||||
attributes:
|
||||
facts:
|
||||
description: Action returns an C(ansible_facts) dictionary that will update existing host facts.
|
||||
'''
|
||||
facts:
|
||||
description: Action returns an C(ansible_facts) dictionary that will update existing host facts.
|
||||
"""
|
||||
|
||||
# Should be used together with the standard fragment and the FACTS fragment
|
||||
FACTS_MODULE = r'''
|
||||
@@ -76,18 +76,18 @@ attributes:
|
||||
support: full
|
||||
'''
|
||||
|
||||
FILES = r'''
|
||||
FILES = r"""
|
||||
options: {}
|
||||
attributes:
|
||||
safe_file_operations:
|
||||
description: Uses Ansible's strict file operation functions to ensure proper permissions and avoid data corruption.
|
||||
'''
|
||||
safe_file_operations:
|
||||
description: Uses Ansible's strict file operation functions to ensure proper permissions and avoid data corruption.
|
||||
"""
|
||||
|
||||
FLOW = r'''
|
||||
FLOW = r"""
|
||||
options: {}
|
||||
attributes:
|
||||
action:
|
||||
description: Indicates this has a corresponding action plugin so some parts of the options can be executed on the controller.
|
||||
async:
|
||||
description: Supports being used with the C(async) keyword.
|
||||
'''
|
||||
action:
|
||||
description: Indicates this has a corresponding action plugin so some parts of the options can be executed on the controller.
|
||||
async:
|
||||
description: Supports being used with the C(async) keyword.
|
||||
"""
|
||||
|
||||
@@ -10,7 +10,7 @@ __metaclass__ = type
|
||||
class ModuleDocFragment(object):
|
||||
|
||||
# Standard files documentation fragment
|
||||
DOCUMENTATION = r'''
|
||||
DOCUMENTATION = r"""
|
||||
options:
|
||||
api_url:
|
||||
description:
|
||||
@@ -29,4 +29,4 @@ options:
|
||||
- Whether or not to validate SSL certs when supplying a HTTPS endpoint.
|
||||
type: bool
|
||||
default: true
|
||||
'''
|
||||
"""
|
||||
|
||||
@@ -11,7 +11,7 @@ __metaclass__ = type
|
||||
class ModuleDocFragment(object):
|
||||
|
||||
# Standard documentation fragment
|
||||
DOCUMENTATION = r'''
|
||||
DOCUMENTATION = r"""
|
||||
options:
|
||||
client_id:
|
||||
description:
|
||||
@@ -30,7 +30,7 @@ options:
|
||||
- O(ignore:username) is an alias of O(user) since community.general 6.0.0. It was an alias of O(workspace) before.
|
||||
type: str
|
||||
version_added: 4.0.0
|
||||
aliases: [ username ]
|
||||
aliases: [username]
|
||||
password:
|
||||
description:
|
||||
- The App password.
|
||||
@@ -41,4 +41,4 @@ notes:
|
||||
- Bitbucket OAuth consumer key and secret can be obtained from Bitbucket profile -> Settings -> Access Management -> OAuth.
|
||||
- Bitbucket App password can be created from Bitbucket profile -> Personal Settings -> App passwords.
|
||||
- If both OAuth and Basic Auth credentials are passed, OAuth credentials take precedence.
|
||||
'''
|
||||
"""
|
||||
|
||||
28
plugins/doc_fragments/clc.py
Normal file
28
plugins/doc_fragments/clc.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2024, Alexei Znamensky <russoz@gmail.com>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class ModuleDocFragment(object):
|
||||
|
||||
# Standard documentation fragment
|
||||
DOCUMENTATION = r"""
|
||||
options: {}
|
||||
requirements:
|
||||
- requests >= 2.5.0
|
||||
- clc-sdk
|
||||
notes:
|
||||
- To use this module, it is required to set the below environment variables which enables access to the Centurylink Cloud.
|
||||
- E(CLC_V2_API_USERNAME), the account login ID for the Centurylink Cloud.
|
||||
- E(CLC_V2_API_PASSWORD), the account password for the Centurylink Cloud.
|
||||
- Alternatively, the module accepts the API token and account alias. The API token can be generated using the CLC account
|
||||
login and password using the HTTP API call @ U(https://api.ctl.io/v2/authentication/login).
|
||||
- E(CLC_V2_API_TOKEN), the API token generated from U(https://api.ctl.io/v2/authentication/login).
|
||||
- E(CLC_ACCT_ALIAS), the account alias associated with the Centurylink Cloud.
|
||||
- Users can set E(CLC_V2_API_URL) to specify an endpoint for pointing to a different CLC environment.
|
||||
"""
|
||||
@@ -15,7 +15,7 @@ class ModuleDocFragment:
|
||||
options:
|
||||
host:
|
||||
description:
|
||||
- Host of the consul agent, defaults to V(localhost).
|
||||
- Host of the Consul agent.
|
||||
default: localhost
|
||||
type: str
|
||||
port:
|
||||
@@ -25,18 +25,18 @@ options:
|
||||
default: 8500
|
||||
scheme:
|
||||
description:
|
||||
- The protocol scheme on which the consul agent is running.
|
||||
Defaults to V(http) and can be set to V(https) for secure connections.
|
||||
- The protocol scheme on which the Consul agent is running. Defaults to V(http) and can be set to V(https) for secure
|
||||
connections.
|
||||
default: http
|
||||
type: str
|
||||
validate_certs:
|
||||
type: bool
|
||||
description:
|
||||
- Whether to verify the TLS certificate of the consul agent.
|
||||
- Whether to verify the TLS certificate of the Consul agent.
|
||||
default: true
|
||||
ca_path:
|
||||
description:
|
||||
- The CA bundle to use for https connections
|
||||
- The CA bundle to use for https connections.
|
||||
type: str
|
||||
"""
|
||||
|
||||
|
||||
@@ -14,8 +14,7 @@ __metaclass__ = type
|
||||
class ModuleDocFragment(object):
|
||||
|
||||
# Dimension Data doc fragment
|
||||
DOCUMENTATION = r'''
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
options:
|
||||
region:
|
||||
description:
|
||||
@@ -48,4 +47,4 @@ options:
|
||||
- This should only be used on private instances of the CloudControl API that use self-signed certificates.
|
||||
type: bool
|
||||
default: true
|
||||
'''
|
||||
"""
|
||||
|
||||
@@ -14,8 +14,7 @@ __metaclass__ = type
|
||||
class ModuleDocFragment(object):
|
||||
|
||||
# Dimension Data ("wait-for-completion" parameters) doc fragment
|
||||
DOCUMENTATION = r'''
|
||||
|
||||
DOCUMENTATION = r"""
|
||||
options:
|
||||
wait:
|
||||
description:
|
||||
@@ -34,4 +33,4 @@ options:
|
||||
- Only applicable if O(wait=true).
|
||||
type: int
|
||||
default: 2
|
||||
'''
|
||||
"""
|
||||
|
||||
@@ -8,7 +8,7 @@ __metaclass__ = type
|
||||
|
||||
|
||||
class ModuleDocFragment(object):
|
||||
DOCUMENTATION = r'''
|
||||
DOCUMENTATION = r"""
|
||||
options:
|
||||
venv:
|
||||
description:
|
||||
@@ -43,20 +43,19 @@ options:
|
||||
|
||||
notes:
|
||||
- The C(django-admin) command is always executed using the C(C) locale, and the option C(--no-color) is always passed.
|
||||
|
||||
seealso:
|
||||
- name: django-admin and manage.py in official Django documentation
|
||||
description: >-
|
||||
Refer to this documentation for the builtin commands and options of C(django-admin).
|
||||
Please make sure that you select the right version of Django in the version selector on that page.
|
||||
Refer to this documentation for the builtin commands and options of C(django-admin). Please make sure that you select
|
||||
the right version of Django in the version selector on that page.
|
||||
link: https://docs.djangoproject.com/en/5.0/ref/django-admin/
|
||||
'''
|
||||
"""
|
||||
|
||||
DATABASE = r'''
|
||||
DATABASE = r"""
|
||||
options:
|
||||
database:
|
||||
description:
|
||||
- Specify the database to be used.
|
||||
type: str
|
||||
default: default
|
||||
'''
|
||||
"""
|
||||
|
||||
@@ -10,15 +10,6 @@ __metaclass__ = type
|
||||
|
||||
class ModuleDocFragment(object):
|
||||
|
||||
DOCUMENTATION = r'''
|
||||
options:
|
||||
- See respective platform section for more details
|
||||
requirements:
|
||||
- See respective platform section for more details
|
||||
notes:
|
||||
- Ansible modules are available for EMC VNX.
|
||||
'''
|
||||
|
||||
# Documentation fragment for VNX (emc_vnx)
|
||||
EMC_VNX = r'''
|
||||
options:
|
||||
|
||||
@@ -10,7 +10,7 @@ __metaclass__ = type
|
||||
class ModuleDocFragment(object):
|
||||
|
||||
# Standard files documentation fragment
|
||||
DOCUMENTATION = r'''
|
||||
DOCUMENTATION = r"""
|
||||
requirements:
|
||||
- requests (Python library U(https://pypi.org/project/requests/))
|
||||
|
||||
@@ -34,4 +34,4 @@ options:
|
||||
- The CA certificates bundle to use to verify GitLab server certificate.
|
||||
type: str
|
||||
version_added: 8.1.0
|
||||
'''
|
||||
"""
|
||||
|
||||
@@ -10,26 +10,26 @@ __metaclass__ = type
|
||||
class ModuleDocFragment(object):
|
||||
|
||||
# HPE 3PAR doc fragment
|
||||
DOCUMENTATION = '''
|
||||
DOCUMENTATION = r"""
|
||||
options:
|
||||
storage_system_ip:
|
||||
description:
|
||||
- The storage system IP address.
|
||||
type: str
|
||||
required: true
|
||||
storage_system_password:
|
||||
description:
|
||||
- The storage system password.
|
||||
type: str
|
||||
required: true
|
||||
storage_system_username:
|
||||
description:
|
||||
- The storage system user name.
|
||||
type: str
|
||||
required: true
|
||||
storage_system_ip:
|
||||
description:
|
||||
- The storage system IP address.
|
||||
type: str
|
||||
required: true
|
||||
storage_system_password:
|
||||
description:
|
||||
- The storage system password.
|
||||
type: str
|
||||
required: true
|
||||
storage_system_username:
|
||||
description:
|
||||
- The storage system user name.
|
||||
type: str
|
||||
required: true
|
||||
|
||||
requirements:
|
||||
- hpe3par_sdk >= 1.0.2. Install using C(pip install hpe3par_sdk).
|
||||
- WSAPI service should be enabled on the 3PAR storage array.
|
||||
notes:
|
||||
'''
|
||||
"""
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user