ansible-core 2.18.8__py3-none-any.whl → 2.18.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of ansible-core might be problematic. Click here for more details.
- ansible/module_utils/ansible_release.py +1 -1
- ansible/modules/dnf.py +4 -1
- ansible/modules/service_facts.py +10 -3
- ansible/modules/user.py +17 -9
- ansible/release.py +1 -1
- {ansible_core-2.18.8.dist-info → ansible_core-2.18.9.dist-info}/METADATA +1 -1
- {ansible_core-2.18.8.dist-info → ansible_core-2.18.9.dist-info}/RECORD +22 -22
- ansible_test/_internal/ci/__init__.py +64 -130
- ansible_test/_internal/ci/azp.py +15 -17
- ansible_test/_internal/ci/local.py +117 -21
- ansible_test/_internal/core_ci.py +31 -11
- ansible_test/_internal/host_profiles.py +5 -2
- ansible_test/_internal/provisioning.py +3 -0
- ansible_test/_internal/util.py +1 -0
- {ansible_core-2.18.8.dist-info → ansible_core-2.18.9.dist-info}/WHEEL +0 -0
- {ansible_core-2.18.8.dist-info → ansible_core-2.18.9.dist-info}/entry_points.txt +0 -0
- {ansible_core-2.18.8.dist-info → ansible_core-2.18.9.dist-info}/licenses/COPYING +0 -0
- {ansible_core-2.18.8.dist-info → ansible_core-2.18.9.dist-info}/licenses/licenses/Apache-License.txt +0 -0
- {ansible_core-2.18.8.dist-info → ansible_core-2.18.9.dist-info}/licenses/licenses/MIT-license.txt +0 -0
- {ansible_core-2.18.8.dist-info → ansible_core-2.18.9.dist-info}/licenses/licenses/PSF-license.txt +0 -0
- {ansible_core-2.18.8.dist-info → ansible_core-2.18.9.dist-info}/licenses/licenses/simplified_bsd.txt +0 -0
- {ansible_core-2.18.8.dist-info → ansible_core-2.18.9.dist-info}/top_level.txt +0 -0
ansible/modules/dnf.py
CHANGED
|
@@ -526,7 +526,10 @@ class DnfModule(YumDnf):
|
|
|
526
526
|
conf.config_file_path = conf_file
|
|
527
527
|
|
|
528
528
|
# Read the configuration file
|
|
529
|
-
|
|
529
|
+
try:
|
|
530
|
+
conf.read()
|
|
531
|
+
except dnf.exceptions.ConfigError as e:
|
|
532
|
+
self.module.fail_json(msg=f"Failed to read configuration file: {e}", conf_file=conf_file)
|
|
530
533
|
|
|
531
534
|
# Turn off debug messages in the output
|
|
532
535
|
conf.debuglevel = 0
|
ansible/modules/service_facts.py
CHANGED
|
@@ -209,8 +209,8 @@ class ServiceScanService(BaseService):
|
|
|
209
209
|
|
|
210
210
|
def _list_openrc(self, services):
|
|
211
211
|
all_services_runlevels = {}
|
|
212
|
-
|
|
213
|
-
|
|
212
|
+
dummy, stdout, dummy = self.module.run_command("%s -a -s -m 2>&1 | grep '^ ' | tr -d '[]'" % self.rc_status_path, use_unsafe_shell=True)
|
|
213
|
+
dummy, stdout_u, dummy = self.module.run_command("%s show -v 2>&1 | grep '|'" % self.rc_update_path, use_unsafe_shell=True)
|
|
214
214
|
for line in stdout_u.split('\n'):
|
|
215
215
|
line_data = line.split('|')
|
|
216
216
|
if len(line_data) < 2:
|
|
@@ -226,8 +226,15 @@ class ServiceScanService(BaseService):
|
|
|
226
226
|
if len(line_data) < 2:
|
|
227
227
|
continue
|
|
228
228
|
service_name = line_data[0]
|
|
229
|
+
# Skip lines which are not service names
|
|
230
|
+
if service_name == "*":
|
|
231
|
+
continue
|
|
229
232
|
service_state = line_data[1]
|
|
230
|
-
|
|
233
|
+
try:
|
|
234
|
+
service_runlevels = all_services_runlevels[service_name]
|
|
235
|
+
except KeyError:
|
|
236
|
+
self.module.warn(f"Service {service_name} not found in the service list")
|
|
237
|
+
continue
|
|
231
238
|
service_data = {"name": service_name, "runlevels": service_runlevels, "state": service_state, "source": "openrc"}
|
|
232
239
|
services[service_name] = service_data
|
|
233
240
|
|
ansible/modules/user.py
CHANGED
|
@@ -1376,16 +1376,24 @@ class User(object):
|
|
|
1376
1376
|
self.module.exit_json(failed=True, msg="%s" % to_native(e))
|
|
1377
1377
|
# get umask from /etc/login.defs and set correct home mode
|
|
1378
1378
|
if os.path.exists(self.LOGIN_DEFS):
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1379
|
+
# fallback if neither HOME_MODE nor UMASK are set;
|
|
1380
|
+
# follow behaviour of useradd initializing UMASK = 022
|
|
1381
|
+
mode = 0o755
|
|
1382
|
+
with open(self.LOGIN_DEFS, 'r') as fh:
|
|
1383
|
+
for line in fh:
|
|
1384
|
+
# HOME_MODE has higher precedence as UMASK
|
|
1385
|
+
match = re.match(r'^HOME_MODE\s+(\d+)$', line)
|
|
1386
|
+
if match:
|
|
1387
|
+
mode = int(match.group(1), 8)
|
|
1388
|
+
break # higher precedence
|
|
1389
|
+
match = re.match(r'^UMASK\s+(\d+)$', line)
|
|
1390
|
+
if match:
|
|
1391
|
+
umask = int(match.group(1), 8)
|
|
1384
1392
|
mode = 0o777 & ~umask
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1393
|
+
try:
|
|
1394
|
+
os.chmod(path, mode)
|
|
1395
|
+
except OSError as e:
|
|
1396
|
+
self.module.exit_json(failed=True, msg=to_native(e))
|
|
1389
1397
|
|
|
1390
1398
|
def chown_homedir(self, uid, gid, path):
|
|
1391
1399
|
try:
|
ansible/release.py
CHANGED
|
@@ -3,7 +3,7 @@ ansible/__main__.py,sha256=24j-7-YT4lZ2fmV80JD-VRoYBnxR7YoP_VP-orJtDt0,796
|
|
|
3
3
|
ansible/constants.py,sha256=dSgbrzNsmhYc4GQOWZvRm4XKgf--_MUWcMa_9_7l5Pc,9757
|
|
4
4
|
ansible/context.py,sha256=oKYyfjfWpy8vDeProtqfnqSmuij_t75_5e5t0U_hQ1g,1933
|
|
5
5
|
ansible/keyword_desc.yml,sha256=xD-MRMB8mSRaj2ADwRnjIEbOwJKbc6BYadouGPfS0mI,7462
|
|
6
|
-
ansible/release.py,sha256=
|
|
6
|
+
ansible/release.py,sha256=4LCba40y5QHx9pqj1DjRXtC5vNhixn7NpXSoEakcIJ8,836
|
|
7
7
|
ansible/_vendor/__init__.py,sha256=2QBeBwT7uG7M3Aw-pIdCpt6XPtHMCpbEKfACYKA7xIg,2033
|
|
8
8
|
ansible/cli/__init__.py,sha256=e0KjeLfG1Ketbwl-uOmQ-zXoq3_El80LnHTGu80d1gs,28111
|
|
9
9
|
ansible/cli/adhoc.py,sha256=quJ9WzRzf3dz_dtDGmahNMffqyNVy1jzQCMo21YL5Qg,8194
|
|
@@ -141,7 +141,7 @@ ansible/inventory/host.py,sha256=PDb5OTplhfpUIvdHiP2BckUOB1gUl302N-3sW0_sTyg,503
|
|
|
141
141
|
ansible/inventory/manager.py,sha256=45mHgZTAkQ3IjAtrgsNzJXvynC-HIEor-JJE-V3xXN4,29454
|
|
142
142
|
ansible/module_utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
143
143
|
ansible/module_utils/_text.py,sha256=VkWgAnSNVCbTQqZgllUObBFsH3uM4EUW5srl1UR9t1g,544
|
|
144
|
-
ansible/module_utils/ansible_release.py,sha256=
|
|
144
|
+
ansible/module_utils/ansible_release.py,sha256=4LCba40y5QHx9pqj1DjRXtC5vNhixn7NpXSoEakcIJ8,836
|
|
145
145
|
ansible/module_utils/api.py,sha256=r4wd6XZGhUnxMF416Ry6ebgq8BIhjCPSPOvO2ZtrYxE,5785
|
|
146
146
|
ansible/module_utils/basic.py,sha256=fogfpo_l7JtS34WvgwwOebmPfMhFjQaJN5CwjKgUJVE,86291
|
|
147
147
|
ansible/module_utils/connection.py,sha256=8TviwCucQ7d_JILwaUHE4tCuNfR3U1WFkmxLMxWa8Rw,7671
|
|
@@ -295,7 +295,7 @@ ansible/modules/cron.py,sha256=qR5ePdI3GZQeBSCn9YqxTxWlNEblCaTFnBRZqLjtnPo,26353
|
|
|
295
295
|
ansible/modules/deb822_repository.py,sha256=SLJM8bBLc70WYu3-OA67wd5hMft3pznYAMIidYOtmUU,15791
|
|
296
296
|
ansible/modules/debconf.py,sha256=Y49U5pM6UpKvYAvDbOhYe6kmQFAaxjl7YoYnPrOaGGU,9362
|
|
297
297
|
ansible/modules/debug.py,sha256=BFbzrU_vl-Try5DuLV20_sLgqxEJlPV9uOrgAtby2e8,2908
|
|
298
|
-
ansible/modules/dnf.py,sha256=
|
|
298
|
+
ansible/modules/dnf.py,sha256=NsraDYODkJAS2BgaHwBa866qk1Qg8bQ0rXgq_4rHWRM,52455
|
|
299
299
|
ansible/modules/dnf5.py,sha256=VZ5KpEO1lZScawgmnsrdd2FVW2vyRpg4dikXvWcD6pA,30288
|
|
300
300
|
ansible/modules/dpkg_selections.py,sha256=lTWBhmVFrf6PsV4_BoR23wVTJOloCH1YNPcAn0m7DTY,2805
|
|
301
301
|
ansible/modules/expect.py,sha256=O4emRoJ09i3OLmVX5j84WHkGKWg6bMytYpZlExOrSmc,9369
|
|
@@ -332,7 +332,7 @@ ansible/modules/replace.py,sha256=L7JI3VFY4NKPlQAt2s27sngHBFSGLVG_l2sWUqwekck,11
|
|
|
332
332
|
ansible/modules/rpm_key.py,sha256=BXFIAY6FOxA5LCvu6RaneVPqzVqptF5hdWmFdJQ63Bg,9325
|
|
333
333
|
ansible/modules/script.py,sha256=bcn4C3BuCvwEE1Ebc7IA5NGOjEcmkiAkx8jBDjQyOAw,4410
|
|
334
334
|
ansible/modules/service.py,sha256=kLRfjE2Ns99FwoY8gh5i283ArBGVcWXIjrxd3PsDHtw,62341
|
|
335
|
-
ansible/modules/service_facts.py,sha256=
|
|
335
|
+
ansible/modules/service_facts.py,sha256=WMhBOCjGxq8rjWED0N5Sv5Y4o306OXnUsiu7-q1VPGw,18668
|
|
336
336
|
ansible/modules/set_fact.py,sha256=iT7kQWXdUoSYWue66dmqYDUW74s_wUlO7dixnawPkgs,5721
|
|
337
337
|
ansible/modules/set_stats.py,sha256=RlZZuMJ4aW1Z8Eba7s-yDlBxHnXS12OmYoiWOooyDzY,2641
|
|
338
338
|
ansible/modules/setup.py,sha256=OyJ3aXrbVW5ybwpyIiA-MzS2PeX_XGK7VpG9saHpjFY,11034
|
|
@@ -347,7 +347,7 @@ ansible/modules/tempfile.py,sha256=lA9e8lyFXf9J5ud0R6Jkt8sIFyRcOwzhc9Jz-5_HOZQ,3
|
|
|
347
347
|
ansible/modules/template.py,sha256=D1sm36GB_mEimH0CfWq1cJ4w1eRvpcsHwZ-ufVzC_Gs,4537
|
|
348
348
|
ansible/modules/unarchive.py,sha256=wdSOFKhZqbAFq5j_tBZtUSamfr_EEW6_cTZDw-erMjs,45405
|
|
349
349
|
ansible/modules/uri.py,sha256=UCANRxecG45I4HGtj1AX9k0I01w8dfTplWksejLHrIg,27846
|
|
350
|
-
ansible/modules/user.py,sha256=
|
|
350
|
+
ansible/modules/user.py,sha256=tQK7ZloLNe485D-FD0ZQsDNhxjOVDlbvs6kqYW2pSh0,123973
|
|
351
351
|
ansible/modules/validate_argument_spec.py,sha256=XbWlUr4ElgLfdxo3qCN7M-IES_X2iTl3AgawzCOMQpo,3042
|
|
352
352
|
ansible/modules/wait_for.py,sha256=rsx_PR73-BRImLNErVUM1o9wRdPlXkGu5y6FhJ8wLcY,27355
|
|
353
353
|
ansible/modules/wait_for_connection.py,sha256=8ySz5bhK7LGSaT_7Jzk3jvACcnngyR2g_ziyFLKk6Rk,3367
|
|
@@ -687,11 +687,11 @@ ansible/vars/hostvars.py,sha256=o11xrzDVYn23renGbb3lx3R-nH9qOjLFju5IYJanDxg,5324
|
|
|
687
687
|
ansible/vars/manager.py,sha256=JF2KTL4iYSbcdnFNjhQPktwH05YhWJhTWtjSlF0qg9E,31260
|
|
688
688
|
ansible/vars/plugins.py,sha256=PocWZPMqFl1LoNgWlGFNxwg9nZnUzhQmlXO4g7bcP2A,4503
|
|
689
689
|
ansible/vars/reserved.py,sha256=Tsc4m2UwVce3dOvSWrjT2wB3lpNJtUyNZn45zNhsW0I,2869
|
|
690
|
-
ansible_core-2.18.
|
|
691
|
-
ansible_core-2.18.
|
|
692
|
-
ansible_core-2.18.
|
|
693
|
-
ansible_core-2.18.
|
|
694
|
-
ansible_core-2.18.
|
|
690
|
+
ansible_core-2.18.9.dist-info/licenses/COPYING,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
691
|
+
ansible_core-2.18.9.dist-info/licenses/licenses/Apache-License.txt,sha256=y16Ofl9KOYjhBjwULGDcLfdWBfTEZRXnduOspt-XbhQ,11325
|
|
692
|
+
ansible_core-2.18.9.dist-info/licenses/licenses/MIT-license.txt,sha256=jLXp2XurnyZKbye40g9tfmLGtVlxh3pPD4n8xNqX8xc,1023
|
|
693
|
+
ansible_core-2.18.9.dist-info/licenses/licenses/PSF-license.txt,sha256=g7BC_H1qyg8Q1o5F76Vrm8ChSWYI5-dyj-CdGlNKBUo,2484
|
|
694
|
+
ansible_core-2.18.9.dist-info/licenses/licenses/simplified_bsd.txt,sha256=8R5R7R7sOa0h1Fi6RNgFgHowHBfun-OVOMzJ4rKAk2w,1237
|
|
695
695
|
ansible_test/__init__.py,sha256=20VPOj11c6Ut1Av9RaurgwJvFhMqkWG3vAvcCbecNKw,66
|
|
696
696
|
ansible_test/_data/ansible.cfg,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
697
697
|
ansible_test/_data/coveragerc,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -751,7 +751,7 @@ ansible_test/_internal/connections.py,sha256=-gK9FqvmpsjENdYNkvWgFgqYHJSS_F2XkvQ
|
|
|
751
751
|
ansible_test/_internal/constants.py,sha256=Zwgp8wtUuge_8xMPg0pDUt58fBd9KA7YEPTQqAQv8ac,1969
|
|
752
752
|
ansible_test/_internal/containers.py,sha256=TrkHL4ntmb7HrmD55BzSdqF35s7oFKu0vCywpWfRt-k,34137
|
|
753
753
|
ansible_test/_internal/content_config.py,sha256=QKR_XVBgYRNZL-XawF2pN2ERTZ6lSm1AJg9ZQRD6IHE,5588
|
|
754
|
-
ansible_test/_internal/core_ci.py,sha256=
|
|
754
|
+
ansible_test/_internal/core_ci.py,sha256=9RQU92QfSlEPcf9Wet8c3UWmZGrkmZCMQrT1h51iQuk,17976
|
|
755
755
|
ansible_test/_internal/coverage_util.py,sha256=6SFmRqRPiv5l5Aj3VV1PtnHIgM5CNVvsWrilUch1NtY,9407
|
|
756
756
|
ansible_test/_internal/data.py,sha256=OFDpRa47yqBqQO1aSvTZVQQpScHvBHsr861586MQEUI,11184
|
|
757
757
|
ansible_test/_internal/delegation.py,sha256=D8hluDQf_YN3DtVG_8HW0iumRBY3gjp_zP-rlc3VNY4,13418
|
|
@@ -761,7 +761,7 @@ ansible_test/_internal/encoding.py,sha256=4GAcVhcswUJW77XSBTLRIP7ll6kT9J-yIBtVsF
|
|
|
761
761
|
ansible_test/_internal/executor.py,sha256=KW5yI-f-giErQ077MTj707fTtFkf_Kr8IV_Nr36NNmc,2959
|
|
762
762
|
ansible_test/_internal/git.py,sha256=njtciWq2DlzZ1DAkQi08HRRP-TgH0mgeGZsWcsJGctI,4366
|
|
763
763
|
ansible_test/_internal/host_configs.py,sha256=ElAo2t3kSyb7bShqBENiHH0oEe-8Am8JXG9xY5l7HY4,18634
|
|
764
|
-
ansible_test/_internal/host_profiles.py,sha256=
|
|
764
|
+
ansible_test/_internal/host_profiles.py,sha256=pTKpncORGaRPaDPE-dDVXlSus2CkbrjkM66cxNG5KH8,65721
|
|
765
765
|
ansible_test/_internal/http.py,sha256=ENuIPnBXIuvgDSxC-r5eOxfGzscxB6MOVJzT4OQXQSA,3864
|
|
766
766
|
ansible_test/_internal/init.py,sha256=f2ZN7F-FyjMgN73SUgxwbVtWNhkJv7BIlZ-q4ALHyjM,505
|
|
767
767
|
ansible_test/_internal/inventory.py,sha256=c79s-xc1uv2nD7rPISv0JKkKspY-X2-kHoozF2R4e1Q,5408
|
|
@@ -770,7 +770,7 @@ ansible_test/_internal/junit_xml.py,sha256=5op7cjGK7Et0OSjcAAuUEqNWNAv5ZoNI0rkLx
|
|
|
770
770
|
ansible_test/_internal/locale_util.py,sha256=tjRbwKmgMQc1ysIhvP8yBhFcNA-2UCaWfQBDgrRFUxU,2161
|
|
771
771
|
ansible_test/_internal/metadata.py,sha256=c9ThXPUlgeKYhaTUmfCSS4INRNQ1JhN2KEOVaX3m1Gk,4791
|
|
772
772
|
ansible_test/_internal/payload.py,sha256=1Pw05OEHvP3LMQnoLXch8631c94YMklWlpDn0CvQECw,8012
|
|
773
|
-
ansible_test/_internal/provisioning.py,sha256=
|
|
773
|
+
ansible_test/_internal/provisioning.py,sha256=owGvyyBmMjtdAw0x41X9qYtOJnkvskpQRcpJ-pmLwlo,7409
|
|
774
774
|
ansible_test/_internal/pypi_proxy.py,sha256=g2FbZh1ZaLWgj_mfgGNSynMX76s4F4DUKMOYavsWIuA,6019
|
|
775
775
|
ansible_test/_internal/python_requirements.py,sha256=x9u_9ABfvRx-aeQ-oyoTmlF78mmJ483wBGjxT5PuJfA,15205
|
|
776
776
|
ansible_test/_internal/ssh.py,sha256=WeVvn3ReHmjg6Im5BdSBRl1YIj1lOmi71jO9T5fTkik,10781
|
|
@@ -778,12 +778,12 @@ ansible_test/_internal/target.py,sha256=Whtb_n0jn4zbiMmX7je5jewgzsRczfXRm_ndYtjT
|
|
|
778
778
|
ansible_test/_internal/test.py,sha256=znQmGjKACqDU8T0EAPqcv2qyy0J7M2w4OmyYhwHLqT0,14515
|
|
779
779
|
ansible_test/_internal/thread.py,sha256=WQoZ2q2ljmEkKHRDkIqwxW7eZbkCKDrG3YZfcaxHzHw,2596
|
|
780
780
|
ansible_test/_internal/timeout.py,sha256=X8LxoMSU85lw4MszfEljaG6V7Aff4Btveg3r89Fe25U,4052
|
|
781
|
-
ansible_test/_internal/util.py,sha256=
|
|
781
|
+
ansible_test/_internal/util.py,sha256=VLPU4b0sEvb7fZ8pimnJS--5_fjQnrVmckRvm88dEuA,38935
|
|
782
782
|
ansible_test/_internal/util_common.py,sha256=wSygjQRWlkFNpqQnPSSXnz_3Cr0pWrPvCP-6lMdfuAk,17490
|
|
783
783
|
ansible_test/_internal/venv.py,sha256=k7L9_Ocpsdwp4kQFLF59BVguymd2nqJ-bLHH1NlMET0,5521
|
|
784
|
-
ansible_test/_internal/ci/__init__.py,sha256=
|
|
785
|
-
ansible_test/_internal/ci/azp.py,sha256=
|
|
786
|
-
ansible_test/_internal/ci/local.py,sha256=
|
|
784
|
+
ansible_test/_internal/ci/__init__.py,sha256=wFAyQVsPJbHEI94EN3F3yLBP8aZ4Zlm4U1jka0U8Z8M,4683
|
|
785
|
+
ansible_test/_internal/ci/azp.py,sha256=voqqYVP9OEccLIsx_Y9RyUCR9pL8xz3uu91hwGDac8w,10103
|
|
786
|
+
ansible_test/_internal/ci/local.py,sha256=Yd2fqBs9KcbnF-TEx9PyEslCNF69WNUlM5Bm3-vzq9c,9905
|
|
787
787
|
ansible_test/_internal/classification/__init__.py,sha256=LVyApjvPsofXjdDaHrXnWl71Z61k5jFJYN3WrBeeFDs,34142
|
|
788
788
|
ansible_test/_internal/classification/common.py,sha256=jd5VLRegcOX-GNTZqN_7PBzwKF6akFQYsPEltfynGtU,894
|
|
789
789
|
ansible_test/_internal/classification/csharp.py,sha256=3QpVZjamTTG7h86oeVm7d4UMbyojPbBALHVqCpxS1ic,3241
|
|
@@ -985,8 +985,8 @@ ansible_test/config/cloud-config-vultr.ini.template,sha256=XLKHk3lg_8ReQMdWfZzhh
|
|
|
985
985
|
ansible_test/config/config.yml,sha256=1zdGucnIl6nIecZA7ISIANvqXiHWqq6Dthsk_6MUwNc,2642
|
|
986
986
|
ansible_test/config/inventory.networking.template,sha256=bFNSk8zNQOaZ_twaflrY0XZ9mLwUbRLuNT0BdIFwvn4,1335
|
|
987
987
|
ansible_test/config/inventory.winrm.template,sha256=1QU8W-GFLnYEw8yY9bVIvUAVvJYPM3hyoijf6-M7T00,1098
|
|
988
|
-
ansible_core-2.18.
|
|
989
|
-
ansible_core-2.18.
|
|
990
|
-
ansible_core-2.18.
|
|
991
|
-
ansible_core-2.18.
|
|
992
|
-
ansible_core-2.18.
|
|
988
|
+
ansible_core-2.18.9.dist-info/METADATA,sha256=or0oMRYNJRtAUc9Ub0ngf6X2I9R3SXKvDSQ1-g-4srU,7690
|
|
989
|
+
ansible_core-2.18.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
990
|
+
ansible_core-2.18.9.dist-info/entry_points.txt,sha256=S9yJij5Im6FgRQxzkqSCnPQokC7PcWrDW_NSygZczJU,451
|
|
991
|
+
ansible_core-2.18.9.dist-info/top_level.txt,sha256=IFbRLjAvih1DYzJWg3_F6t4sCzEMxRO7TOMNs6GkYHo,21
|
|
992
|
+
ansible_core-2.18.9.dist-info/RECORD,,
|
|
@@ -2,22 +2,13 @@
|
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
4
|
import abc
|
|
5
|
-
import
|
|
5
|
+
import dataclasses
|
|
6
|
+
import datetime
|
|
6
7
|
import json
|
|
7
|
-
import
|
|
8
|
+
import pathlib
|
|
8
9
|
import tempfile
|
|
9
10
|
import typing as t
|
|
10
11
|
|
|
11
|
-
from ..encoding import (
|
|
12
|
-
to_bytes,
|
|
13
|
-
to_text,
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
from ..io import (
|
|
17
|
-
read_text_file,
|
|
18
|
-
write_text_file,
|
|
19
|
-
)
|
|
20
|
-
|
|
21
12
|
from ..config import (
|
|
22
13
|
CommonConfig,
|
|
23
14
|
TestConfig,
|
|
@@ -33,6 +24,65 @@ from ..util import (
|
|
|
33
24
|
)
|
|
34
25
|
|
|
35
26
|
|
|
27
|
+
@dataclasses.dataclass(frozen=True, kw_only=True)
|
|
28
|
+
class AuthContext:
|
|
29
|
+
"""Information about the request to which authentication will be applied."""
|
|
30
|
+
|
|
31
|
+
stage: str
|
|
32
|
+
provider: str
|
|
33
|
+
request_id: str
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AuthHelper:
|
|
37
|
+
"""Authentication helper."""
|
|
38
|
+
|
|
39
|
+
NAMESPACE: t.ClassVar = 'ci@core.ansible.com'
|
|
40
|
+
|
|
41
|
+
def __init__(self, key_file: pathlib.Path) -> None:
|
|
42
|
+
self.private_key_file = pathlib.Path(str(key_file).removesuffix('.pub'))
|
|
43
|
+
self.public_key_file = pathlib.Path(f'{self.private_key_file}.pub')
|
|
44
|
+
|
|
45
|
+
def sign_request(self, request: dict[str, object], context: AuthContext) -> None:
|
|
46
|
+
"""Sign the given auth request using the provided context."""
|
|
47
|
+
request.update(
|
|
48
|
+
stage=context.stage,
|
|
49
|
+
provider=context.provider,
|
|
50
|
+
request_id=context.request_id,
|
|
51
|
+
timestamp=datetime.datetime.now(tz=datetime.timezone.utc).replace(microsecond=0).isoformat(),
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
55
|
+
payload_path = pathlib.Path(temp_dir) / 'auth.json'
|
|
56
|
+
payload_path.write_text(json.dumps(request, sort_keys=True))
|
|
57
|
+
|
|
58
|
+
cmd = ['ssh-keygen', '-q', '-Y', 'sign', '-f', str(self.private_key_file), '-n', self.NAMESPACE, str(payload_path)]
|
|
59
|
+
raw_command(cmd, capture=False, interactive=True)
|
|
60
|
+
|
|
61
|
+
signature_path = pathlib.Path(f'{payload_path}.sig')
|
|
62
|
+
signature = signature_path.read_text()
|
|
63
|
+
|
|
64
|
+
request.update(signature=signature)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class GeneratingAuthHelper(AuthHelper, metaclass=abc.ABCMeta):
|
|
68
|
+
"""Authentication helper which generates a key pair on demand."""
|
|
69
|
+
|
|
70
|
+
def __init__(self) -> None:
|
|
71
|
+
super().__init__(pathlib.Path('~/.ansible/test/ansible-core-ci').expanduser())
|
|
72
|
+
|
|
73
|
+
def sign_request(self, request: dict[str, object], context: AuthContext) -> None:
|
|
74
|
+
if not self.private_key_file.exists():
|
|
75
|
+
self.generate_key_pair()
|
|
76
|
+
|
|
77
|
+
super().sign_request(request, context)
|
|
78
|
+
|
|
79
|
+
def generate_key_pair(self) -> None:
|
|
80
|
+
"""Generate key pair."""
|
|
81
|
+
self.private_key_file.parent.mkdir(parents=True, exist_ok=True)
|
|
82
|
+
|
|
83
|
+
raw_command(['ssh-keygen', '-q', '-f', str(self.private_key_file), '-N', ''], capture=True)
|
|
84
|
+
|
|
85
|
+
|
|
36
86
|
class ChangeDetectionNotSupported(ApplicationError):
|
|
37
87
|
"""Exception for cases where change detection is not supported."""
|
|
38
88
|
|
|
@@ -74,8 +124,8 @@ class CIProvider(metaclass=abc.ABCMeta):
|
|
|
74
124
|
"""Return True if Ansible Core CI is supported."""
|
|
75
125
|
|
|
76
126
|
@abc.abstractmethod
|
|
77
|
-
def
|
|
78
|
-
"""
|
|
127
|
+
def prepare_core_ci_request(self, config: dict[str, object], context: AuthContext) -> dict[str, object]:
|
|
128
|
+
"""Prepare an Ansible Core CI request using the given config and context."""
|
|
79
129
|
|
|
80
130
|
@abc.abstractmethod
|
|
81
131
|
def get_git_details(self, args: CommonConfig) -> t.Optional[dict[str, t.Any]]:
|
|
@@ -100,119 +150,3 @@ def get_ci_provider() -> CIProvider:
|
|
|
100
150
|
display.info('Detected CI provider: %s' % provider.name)
|
|
101
151
|
|
|
102
152
|
return provider
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
class AuthHelper(metaclass=abc.ABCMeta):
|
|
106
|
-
"""Public key based authentication helper for Ansible Core CI."""
|
|
107
|
-
|
|
108
|
-
def sign_request(self, request: dict[str, t.Any]) -> None:
|
|
109
|
-
"""Sign the given auth request and make the public key available."""
|
|
110
|
-
payload_bytes = to_bytes(json.dumps(request, sort_keys=True))
|
|
111
|
-
signature_raw_bytes = self.sign_bytes(payload_bytes)
|
|
112
|
-
signature = to_text(base64.b64encode(signature_raw_bytes))
|
|
113
|
-
|
|
114
|
-
request.update(signature=signature)
|
|
115
|
-
|
|
116
|
-
def initialize_private_key(self) -> str:
|
|
117
|
-
"""
|
|
118
|
-
Initialize and publish a new key pair (if needed) and return the private key.
|
|
119
|
-
The private key is cached across ansible-test invocations, so it is only generated and published once per CI job.
|
|
120
|
-
"""
|
|
121
|
-
path = os.path.expanduser('~/.ansible-core-ci-private.key')
|
|
122
|
-
|
|
123
|
-
if os.path.exists(to_bytes(path)):
|
|
124
|
-
private_key_pem = read_text_file(path)
|
|
125
|
-
else:
|
|
126
|
-
private_key_pem = self.generate_private_key()
|
|
127
|
-
write_text_file(path, private_key_pem)
|
|
128
|
-
|
|
129
|
-
return private_key_pem
|
|
130
|
-
|
|
131
|
-
@abc.abstractmethod
|
|
132
|
-
def sign_bytes(self, payload_bytes: bytes) -> bytes:
|
|
133
|
-
"""Sign the given payload and return the signature, initializing a new key pair if required."""
|
|
134
|
-
|
|
135
|
-
@abc.abstractmethod
|
|
136
|
-
def publish_public_key(self, public_key_pem: str) -> None:
|
|
137
|
-
"""Publish the given public key."""
|
|
138
|
-
|
|
139
|
-
@abc.abstractmethod
|
|
140
|
-
def generate_private_key(self) -> str:
|
|
141
|
-
"""Generate a new key pair, publishing the public key and returning the private key."""
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
class CryptographyAuthHelper(AuthHelper, metaclass=abc.ABCMeta):
|
|
145
|
-
"""Cryptography based public key based authentication helper for Ansible Core CI."""
|
|
146
|
-
|
|
147
|
-
def sign_bytes(self, payload_bytes: bytes) -> bytes:
|
|
148
|
-
"""Sign the given payload and return the signature, initializing a new key pair if required."""
|
|
149
|
-
# import cryptography here to avoid overhead and failures in environments which do not use/provide it
|
|
150
|
-
from cryptography.hazmat.backends import default_backend
|
|
151
|
-
from cryptography.hazmat.primitives import hashes
|
|
152
|
-
from cryptography.hazmat.primitives.asymmetric import ec
|
|
153
|
-
from cryptography.hazmat.primitives.serialization import load_pem_private_key
|
|
154
|
-
|
|
155
|
-
private_key_pem = self.initialize_private_key()
|
|
156
|
-
private_key = load_pem_private_key(to_bytes(private_key_pem), None, default_backend())
|
|
157
|
-
|
|
158
|
-
assert isinstance(private_key, ec.EllipticCurvePrivateKey)
|
|
159
|
-
|
|
160
|
-
signature_raw_bytes = private_key.sign(payload_bytes, ec.ECDSA(hashes.SHA256()))
|
|
161
|
-
|
|
162
|
-
return signature_raw_bytes
|
|
163
|
-
|
|
164
|
-
def generate_private_key(self) -> str:
|
|
165
|
-
"""Generate a new key pair, publishing the public key and returning the private key."""
|
|
166
|
-
# import cryptography here to avoid overhead and failures in environments which do not use/provide it
|
|
167
|
-
from cryptography.hazmat.backends import default_backend
|
|
168
|
-
from cryptography.hazmat.primitives import serialization
|
|
169
|
-
from cryptography.hazmat.primitives.asymmetric import ec
|
|
170
|
-
|
|
171
|
-
private_key = ec.generate_private_key(ec.SECP384R1(), default_backend())
|
|
172
|
-
public_key = private_key.public_key()
|
|
173
|
-
|
|
174
|
-
private_key_pem = to_text(private_key.private_bytes( # type: ignore[attr-defined] # documented method, but missing from type stubs
|
|
175
|
-
encoding=serialization.Encoding.PEM,
|
|
176
|
-
format=serialization.PrivateFormat.PKCS8,
|
|
177
|
-
encryption_algorithm=serialization.NoEncryption(),
|
|
178
|
-
))
|
|
179
|
-
|
|
180
|
-
public_key_pem = to_text(public_key.public_bytes(
|
|
181
|
-
encoding=serialization.Encoding.PEM,
|
|
182
|
-
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
|
183
|
-
))
|
|
184
|
-
|
|
185
|
-
self.publish_public_key(public_key_pem)
|
|
186
|
-
|
|
187
|
-
return private_key_pem
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
class OpenSSLAuthHelper(AuthHelper, metaclass=abc.ABCMeta):
|
|
191
|
-
"""OpenSSL based public key based authentication helper for Ansible Core CI."""
|
|
192
|
-
|
|
193
|
-
def sign_bytes(self, payload_bytes: bytes) -> bytes:
|
|
194
|
-
"""Sign the given payload and return the signature, initializing a new key pair if required."""
|
|
195
|
-
private_key_pem = self.initialize_private_key()
|
|
196
|
-
|
|
197
|
-
with tempfile.NamedTemporaryFile() as private_key_file:
|
|
198
|
-
private_key_file.write(to_bytes(private_key_pem))
|
|
199
|
-
private_key_file.flush()
|
|
200
|
-
|
|
201
|
-
with tempfile.NamedTemporaryFile() as payload_file:
|
|
202
|
-
payload_file.write(payload_bytes)
|
|
203
|
-
payload_file.flush()
|
|
204
|
-
|
|
205
|
-
with tempfile.NamedTemporaryFile() as signature_file:
|
|
206
|
-
raw_command(['openssl', 'dgst', '-sha256', '-sign', private_key_file.name, '-out', signature_file.name, payload_file.name], capture=True)
|
|
207
|
-
signature_raw_bytes = signature_file.read()
|
|
208
|
-
|
|
209
|
-
return signature_raw_bytes
|
|
210
|
-
|
|
211
|
-
def generate_private_key(self) -> str:
|
|
212
|
-
"""Generate a new key pair, publishing the public key and returning the private key."""
|
|
213
|
-
private_key_pem = raw_command(['openssl', 'ecparam', '-genkey', '-name', 'secp384r1', '-noout'], capture=True)[0]
|
|
214
|
-
public_key_pem = raw_command(['openssl', 'ec', '-pubout'], data=private_key_pem, capture=True)[0]
|
|
215
|
-
|
|
216
|
-
self.publish_public_key(public_key_pem)
|
|
217
|
-
|
|
218
|
-
return private_key_pem
|
ansible_test/_internal/ci/azp.py
CHANGED
|
@@ -30,9 +30,10 @@ from ..util import (
|
|
|
30
30
|
)
|
|
31
31
|
|
|
32
32
|
from . import (
|
|
33
|
+
AuthContext,
|
|
33
34
|
ChangeDetectionNotSupported,
|
|
34
35
|
CIProvider,
|
|
35
|
-
|
|
36
|
+
GeneratingAuthHelper,
|
|
36
37
|
)
|
|
37
38
|
|
|
38
39
|
CODE = 'azp'
|
|
@@ -111,10 +112,11 @@ class AzurePipelines(CIProvider):
|
|
|
111
112
|
"""Return True if Ansible Core CI is supported."""
|
|
112
113
|
return True
|
|
113
114
|
|
|
114
|
-
def
|
|
115
|
-
"""Return authentication details for Ansible Core CI."""
|
|
115
|
+
def prepare_core_ci_request(self, config: dict[str, object], context: AuthContext) -> dict[str, object]:
|
|
116
116
|
try:
|
|
117
|
-
request = dict(
|
|
117
|
+
request: dict[str, object] = dict(
|
|
118
|
+
type="azp:ssh",
|
|
119
|
+
config=config,
|
|
118
120
|
org_name=os.environ['SYSTEM_COLLECTIONURI'].strip('/').split('/')[-1],
|
|
119
121
|
project_name=os.environ['SYSTEM_TEAMPROJECT'],
|
|
120
122
|
build_id=int(os.environ['BUILD_BUILDID']),
|
|
@@ -123,13 +125,9 @@ class AzurePipelines(CIProvider):
|
|
|
123
125
|
except KeyError as ex:
|
|
124
126
|
raise MissingEnvironmentVariable(name=ex.args[0]) from None
|
|
125
127
|
|
|
126
|
-
self.auth.sign_request(request)
|
|
128
|
+
self.auth.sign_request(request, context)
|
|
127
129
|
|
|
128
|
-
|
|
129
|
-
azp=request,
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
return auth
|
|
130
|
+
return request
|
|
133
131
|
|
|
134
132
|
def get_git_details(self, args: CommonConfig) -> t.Optional[dict[str, t.Any]]:
|
|
135
133
|
"""Return details about git in the current environment."""
|
|
@@ -143,14 +141,14 @@ class AzurePipelines(CIProvider):
|
|
|
143
141
|
return details
|
|
144
142
|
|
|
145
143
|
|
|
146
|
-
class AzurePipelinesAuthHelper(
|
|
147
|
-
"""
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
144
|
+
class AzurePipelinesAuthHelper(GeneratingAuthHelper):
|
|
145
|
+
"""Authentication helper for Azure Pipelines."""
|
|
146
|
+
|
|
147
|
+
def generate_key_pair(self) -> None:
|
|
148
|
+
super().generate_key_pair()
|
|
149
|
+
|
|
150
|
+
public_key_pem = self.public_key_file.read_text()
|
|
151
151
|
|
|
152
|
-
def publish_public_key(self, public_key_pem: str) -> None:
|
|
153
|
-
"""Publish the given public key."""
|
|
154
152
|
try:
|
|
155
153
|
agent_temp_directory = os.environ['AGENT_TEMPDIRECTORY']
|
|
156
154
|
except KeyError as ex:
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
"""Support code for working without a supported CI provider."""
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
-
import
|
|
4
|
+
import abc
|
|
5
|
+
import inspect
|
|
5
6
|
import platform
|
|
6
7
|
import random
|
|
7
8
|
import re
|
|
9
|
+
import pathlib
|
|
8
10
|
import typing as t
|
|
9
11
|
|
|
10
12
|
from ..config import (
|
|
@@ -23,11 +25,14 @@ from ..git import (
|
|
|
23
25
|
from ..util import (
|
|
24
26
|
ApplicationError,
|
|
25
27
|
display,
|
|
28
|
+
get_subclasses,
|
|
26
29
|
is_binary_file,
|
|
27
30
|
SubprocessError,
|
|
28
31
|
)
|
|
29
32
|
|
|
30
33
|
from . import (
|
|
34
|
+
AuthContext,
|
|
35
|
+
AuthHelper,
|
|
31
36
|
CIProvider,
|
|
32
37
|
)
|
|
33
38
|
|
|
@@ -119,34 +124,20 @@ class Local(CIProvider):
|
|
|
119
124
|
|
|
120
125
|
def supports_core_ci_auth(self) -> bool:
|
|
121
126
|
"""Return True if Ansible Core CI is supported."""
|
|
122
|
-
|
|
123
|
-
return os.path.exists(path)
|
|
127
|
+
return Authenticator.available()
|
|
124
128
|
|
|
125
|
-
def
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
auth_key = read_text_file(path).strip()
|
|
129
|
+
def prepare_core_ci_request(self, config: dict[str, object], context: AuthContext) -> dict[str, object]:
|
|
130
|
+
if not (authenticator := Authenticator.load()):
|
|
131
|
+
raise ApplicationError('Ansible Core CI authentication has not been configured.')
|
|
129
132
|
|
|
130
|
-
|
|
131
|
-
key=auth_key,
|
|
132
|
-
nonce=None,
|
|
133
|
-
)
|
|
133
|
+
display.info(f'Using {authenticator} for Ansible Core CI.', verbosity=1)
|
|
134
134
|
|
|
135
|
-
|
|
136
|
-
remote=request,
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
return auth
|
|
135
|
+
return authenticator.prepare_auth_request(config, context)
|
|
140
136
|
|
|
141
137
|
def get_git_details(self, args: CommonConfig) -> t.Optional[dict[str, t.Any]]:
|
|
142
138
|
"""Return details about git in the current environment."""
|
|
143
139
|
return None # not yet implemented for local
|
|
144
140
|
|
|
145
|
-
@staticmethod
|
|
146
|
-
def _get_aci_key_path() -> str:
|
|
147
|
-
path = os.path.expanduser('~/.ansible-core-ci.key')
|
|
148
|
-
return path
|
|
149
|
-
|
|
150
141
|
|
|
151
142
|
class InvalidBranch(ApplicationError):
|
|
152
143
|
"""Exception for invalid branch specification."""
|
|
@@ -213,3 +204,108 @@ class LocalChanges:
|
|
|
213
204
|
return True
|
|
214
205
|
|
|
215
206
|
return False
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class Authenticator(metaclass=abc.ABCMeta):
|
|
210
|
+
"""Base class for authenticators."""
|
|
211
|
+
|
|
212
|
+
@staticmethod
|
|
213
|
+
def list() -> list[type[Authenticator]]:
|
|
214
|
+
"""List all authenticators in priority order."""
|
|
215
|
+
return sorted((sc for sc in get_subclasses(Authenticator) if not inspect.isabstract(sc)), key=lambda obj: obj.priority())
|
|
216
|
+
|
|
217
|
+
@staticmethod
|
|
218
|
+
def load() -> Authenticator | None:
|
|
219
|
+
"""Load an authenticator instance, returning None if not configured."""
|
|
220
|
+
for implementation in Authenticator.list():
|
|
221
|
+
if implementation.config_file().exists():
|
|
222
|
+
return implementation()
|
|
223
|
+
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
@staticmethod
|
|
227
|
+
def available() -> bool:
|
|
228
|
+
"""Return True if an authenticator is available, otherwise False."""
|
|
229
|
+
return bool(Authenticator.load())
|
|
230
|
+
|
|
231
|
+
@classmethod
|
|
232
|
+
@abc.abstractmethod
|
|
233
|
+
def priority(cls) -> int:
|
|
234
|
+
"""Priority used to determine which authenticator is tried first, from lowest to highest."""
|
|
235
|
+
|
|
236
|
+
@classmethod
|
|
237
|
+
@abc.abstractmethod
|
|
238
|
+
def config_file(cls) -> pathlib.Path:
|
|
239
|
+
"""Path to the config file for this authenticator."""
|
|
240
|
+
|
|
241
|
+
@abc.abstractmethod
|
|
242
|
+
def prepare_auth_request(self, config: dict[str, object], context: AuthContext) -> dict[str, object]:
|
|
243
|
+
"""Prepare an authenticated Ansible Core CI request using the given config and context."""
|
|
244
|
+
|
|
245
|
+
def __str__(self) -> str:
|
|
246
|
+
return self.__class__.__name__
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class PasswordAuthenticator(Authenticator):
|
|
250
|
+
"""Authenticate using a password."""
|
|
251
|
+
|
|
252
|
+
@classmethod
|
|
253
|
+
def priority(cls) -> int:
|
|
254
|
+
return 200
|
|
255
|
+
|
|
256
|
+
@classmethod
|
|
257
|
+
def config_file(cls) -> pathlib.Path:
|
|
258
|
+
return pathlib.Path('~/.ansible-core-ci.key').expanduser()
|
|
259
|
+
|
|
260
|
+
def prepare_auth_request(self, config: dict[str, object], context: AuthContext) -> dict[str, object]:
|
|
261
|
+
parts = self.config_file().read_text().strip().split(maxsplit=1)
|
|
262
|
+
|
|
263
|
+
if len(parts) == 1: # temporary backward compatibility for legacy API keys
|
|
264
|
+
request = dict(
|
|
265
|
+
config=config,
|
|
266
|
+
auth=dict(
|
|
267
|
+
remote=dict(
|
|
268
|
+
key=parts[0],
|
|
269
|
+
),
|
|
270
|
+
),
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
return request
|
|
274
|
+
|
|
275
|
+
username, password = parts
|
|
276
|
+
|
|
277
|
+
request = dict(
|
|
278
|
+
type="remote:password",
|
|
279
|
+
config=config,
|
|
280
|
+
username=username,
|
|
281
|
+
password=password,
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
return request
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class SshAuthenticator(Authenticator):
|
|
288
|
+
"""Authenticate using an SSH key."""
|
|
289
|
+
|
|
290
|
+
@classmethod
|
|
291
|
+
def priority(cls) -> int:
|
|
292
|
+
return 100
|
|
293
|
+
|
|
294
|
+
@classmethod
|
|
295
|
+
def config_file(cls) -> pathlib.Path:
|
|
296
|
+
return pathlib.Path('~/.ansible-core-ci.auth').expanduser()
|
|
297
|
+
|
|
298
|
+
def prepare_auth_request(self, config: dict[str, object], context: AuthContext) -> dict[str, object]:
|
|
299
|
+
parts = self.config_file().read_text().strip().split(maxsplit=1)
|
|
300
|
+
username, key_file = parts
|
|
301
|
+
|
|
302
|
+
request: dict[str, object] = dict(
|
|
303
|
+
type="remote:ssh",
|
|
304
|
+
config=config,
|
|
305
|
+
username=username,
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
auth_helper = AuthHelper(pathlib.Path(key_file).expanduser())
|
|
309
|
+
auth_helper.sign_request(request, context)
|
|
310
|
+
|
|
311
|
+
return request
|
|
@@ -41,6 +41,7 @@ from .config import (
|
|
|
41
41
|
)
|
|
42
42
|
|
|
43
43
|
from .ci import (
|
|
44
|
+
AuthContext,
|
|
44
45
|
get_ci_provider,
|
|
45
46
|
)
|
|
46
47
|
|
|
@@ -67,6 +68,10 @@ class Resource(metaclass=abc.ABCMeta):
|
|
|
67
68
|
def persist(self) -> bool:
|
|
68
69
|
"""True if the resource is persistent, otherwise false."""
|
|
69
70
|
|
|
71
|
+
@abc.abstractmethod
|
|
72
|
+
def get_config(self, core_ci: AnsibleCoreCI) -> dict[str, object]:
|
|
73
|
+
"""Return the configuration for this resource."""
|
|
74
|
+
|
|
70
75
|
|
|
71
76
|
@dataclasses.dataclass(frozen=True)
|
|
72
77
|
class VmResource(Resource):
|
|
@@ -91,6 +96,16 @@ class VmResource(Resource):
|
|
|
91
96
|
"""True if the resource is persistent, otherwise false."""
|
|
92
97
|
return True
|
|
93
98
|
|
|
99
|
+
def get_config(self, core_ci: AnsibleCoreCI) -> dict[str, object]:
|
|
100
|
+
"""Return the configuration for this resource."""
|
|
101
|
+
return dict(
|
|
102
|
+
type="vm",
|
|
103
|
+
platform=self.platform,
|
|
104
|
+
version=self.version,
|
|
105
|
+
architecture=self.architecture,
|
|
106
|
+
public_key=core_ci.ssh_key.pub_contents,
|
|
107
|
+
)
|
|
108
|
+
|
|
94
109
|
|
|
95
110
|
@dataclasses.dataclass(frozen=True)
|
|
96
111
|
class CloudResource(Resource):
|
|
@@ -111,6 +126,12 @@ class CloudResource(Resource):
|
|
|
111
126
|
"""True if the resource is persistent, otherwise false."""
|
|
112
127
|
return False
|
|
113
128
|
|
|
129
|
+
def get_config(self, core_ci: AnsibleCoreCI) -> dict[str, object]:
|
|
130
|
+
"""Return the configuration for this resource."""
|
|
131
|
+
return dict(
|
|
132
|
+
type="cloud",
|
|
133
|
+
)
|
|
134
|
+
|
|
114
135
|
|
|
115
136
|
class AnsibleCoreCI:
|
|
116
137
|
"""Client for Ansible Core CI services."""
|
|
@@ -188,7 +209,7 @@ class AnsibleCoreCI:
|
|
|
188
209
|
display.info(f'Skipping started {self.label} instance.', verbosity=1)
|
|
189
210
|
return None
|
|
190
211
|
|
|
191
|
-
return self._start(
|
|
212
|
+
return self._start()
|
|
192
213
|
|
|
193
214
|
def stop(self) -> None:
|
|
194
215
|
"""Stop instance."""
|
|
@@ -287,26 +308,25 @@ class AnsibleCoreCI:
|
|
|
287
308
|
def _uri(self) -> str:
|
|
288
309
|
return f'{self.endpoint}/{self.stage}/{self.provider}/{self.instance_id}'
|
|
289
310
|
|
|
290
|
-
def _start(self
|
|
311
|
+
def _start(self) -> dict[str, t.Any]:
|
|
291
312
|
"""Start instance."""
|
|
292
313
|
display.info(f'Initializing new {self.label} instance using: {self._uri}', verbosity=1)
|
|
293
314
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
)
|
|
315
|
+
config = self.resource.get_config(self)
|
|
316
|
+
|
|
317
|
+
context = AuthContext(
|
|
318
|
+
request_id=self.instance_id,
|
|
319
|
+
stage=self.stage,
|
|
320
|
+
provider=self.provider,
|
|
301
321
|
)
|
|
302
322
|
|
|
303
|
-
|
|
323
|
+
request = self.ci_provider.prepare_core_ci_request(config, context)
|
|
304
324
|
|
|
305
325
|
headers = {
|
|
306
326
|
'Content-Type': 'application/json',
|
|
307
327
|
}
|
|
308
328
|
|
|
309
|
-
response = self._start_endpoint(
|
|
329
|
+
response = self._start_endpoint(request, headers)
|
|
310
330
|
|
|
311
331
|
self.started = True
|
|
312
332
|
self._save()
|
|
@@ -246,6 +246,9 @@ class HostProfile(t.Generic[THostConfig], metaclass=abc.ABCMeta):
|
|
|
246
246
|
self.cache: dict[str, t.Any] = {}
|
|
247
247
|
"""Cache that must not be persisted across delegation."""
|
|
248
248
|
|
|
249
|
+
def pre_provision(self) -> None:
|
|
250
|
+
"""Pre-provision the host profile."""
|
|
251
|
+
|
|
249
252
|
def provision(self) -> None:
|
|
250
253
|
"""Provision the host before delegation."""
|
|
251
254
|
|
|
@@ -329,8 +332,8 @@ class RemoteProfile(SshTargetHostProfile[TRemoteConfig], metaclass=abc.ABCMeta):
|
|
|
329
332
|
"""The saved Ansible Core CI state."""
|
|
330
333
|
self.state['core_ci'] = value
|
|
331
334
|
|
|
332
|
-
def
|
|
333
|
-
"""
|
|
335
|
+
def pre_provision(self) -> None:
|
|
336
|
+
"""Pre-provision the host before delegation."""
|
|
334
337
|
self.core_ci = self.create_core_ci(load=True)
|
|
335
338
|
self.core_ci.start()
|
|
336
339
|
|
|
@@ -129,6 +129,9 @@ def prepare_profiles(
|
|
|
129
129
|
|
|
130
130
|
ExitHandler.register(functools.partial(cleanup_profiles, host_state))
|
|
131
131
|
|
|
132
|
+
for pre_profile in host_state.profiles:
|
|
133
|
+
pre_profile.pre_provision()
|
|
134
|
+
|
|
132
135
|
def provision(profile: HostProfile) -> None:
|
|
133
136
|
"""Provision the given profile."""
|
|
134
137
|
profile.provision()
|
ansible_test/_internal/util.py
CHANGED
|
@@ -678,6 +678,7 @@ def common_environment() -> dict[str, str]:
|
|
|
678
678
|
optional = (
|
|
679
679
|
'LD_LIBRARY_PATH',
|
|
680
680
|
'SSH_AUTH_SOCK',
|
|
681
|
+
'SSH_SK_PROVIDER',
|
|
681
682
|
# MacOS High Sierra Compatibility
|
|
682
683
|
# http://sealiesoftware.com/blog/archive/2017/6/5/Objective-C_and_fork_in_macOS_1013.html
|
|
683
684
|
# Example configuration for macOS:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ansible_core-2.18.8.dist-info → ansible_core-2.18.9.dist-info}/licenses/licenses/Apache-License.txt
RENAMED
|
File without changes
|
{ansible_core-2.18.8.dist-info → ansible_core-2.18.9.dist-info}/licenses/licenses/MIT-license.txt
RENAMED
|
File without changes
|
{ansible_core-2.18.8.dist-info → ansible_core-2.18.9.dist-info}/licenses/licenses/PSF-license.txt
RENAMED
|
File without changes
|
{ansible_core-2.18.8.dist-info → ansible_core-2.18.9.dist-info}/licenses/licenses/simplified_bsd.txt
RENAMED
|
File without changes
|
|
File without changes
|