Skip to content

Commit 7a168d2

Browse files
[repo_setup] Download rhos-release using kerberos
If the RPM name points to a URL we now use a custom plugin to fetch the content. If the endpoint challenges the plugins with SPNEGO authentication and a kerberos ticket is present the plugin will authenticate itself using the ticket.
1 parent 0484493 commit 7a168d2

File tree

6 files changed

+228
-1
lines changed

6 files changed

+228
-1
lines changed

docs/dictionary/en-custom.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
aaabbcc
22
abcdefghij
33
addr
4+
afuscoar
45
alertmanager
56
ansible
67
ansibleee
@@ -105,6 +106,7 @@ ctl
105106
ctlplane
106107
ctrl
107108
ctx
109+
cve
108110
customizations
109111
dashboard
110112
dataplane
@@ -165,6 +167,7 @@ fbqufbqkfbzxrja
165167
fci
166168
fedoraproject
167169
fil
170+
filesystem
168171
fips
169172
firewalld
170173
flbxutz
@@ -174,6 +177,7 @@ freefonts
174177
frmo
175178
fsid
176179
fultonj
180+
fusco
177181
fwcybtb
178182
gapped
179183
genericcloud
@@ -296,13 +300,15 @@ manpage
296300
mawxlihjizaogicbjyxbzig
297301
mawxlihjizcbwb
298302
maxdepth
303+
mcs
299304
mellanox
300305
metallb
301306
metalsmith
302307
mgmt
303308
mins
304309
minsizegigabytes
305310
mlnx
311+
mls
306312
modprobe
307313
mountpoints
308314
mtcylje
@@ -464,7 +470,11 @@ scansettingbinding
464470
scap
465471
scp
466472
sdk
473+
selevel
467474
selinux
475+
serole
476+
setype
477+
seuser
468478
sha
469479
shiftstack
470480
shiftstackclient
@@ -473,6 +483,7 @@ sizepercent
473483
skbg
474484
skiplist
475485
specificities
486+
spnego
476487
spxzvbhvtzxmsihbyb
477488
src
478489
sshkey

plugins/modules/url_request.py

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
#!/usr/bin/python
2+
3+
# Copyright: (c) 2025, Pablo Rodriguez <[email protected]>
4+
# Apache License Version 2.0 (see LICENSE)
5+
6+
__metaclass__ = type
7+
8+
DOCUMENTATION = r"""
9+
---
10+
module: url_request
11+
short_description: Downloads/fetches the content of a SPNEGO secured URL
12+
extends_documentation_fragment:
13+
- files
14+
15+
description:
16+
- Downloads/fetches the content of a SPNEGO secured URL
17+
- A kerberos ticket should be already issued
18+
19+
author:
20+
- Adrian Fusco (@afuscoar)
21+
- Pablo Rodriguez (@pablintino)
22+
23+
options:
24+
url:
25+
description:
26+
- The URL to retrieve the content from
27+
required: True
28+
type: str
29+
verify_ssl:
30+
description:
31+
- Enables/disables using TLS to reach the URL
32+
required: False
33+
type: bool
34+
default: true
35+
dest:
36+
description:
37+
- Path to the destination file/dir where the content should be downloaded
38+
- If not provided the content won't be written into disk
39+
required: False
40+
type: str
41+
42+
"""
43+
44+
EXAMPLES = r"""
45+
- name: Get some content
46+
url_request:
47+
url: "http://someurl.local/resource"
48+
dest: "{{ ansible_user_dir }}/content.raw"
49+
mode: "0644"
50+
register: _fetched_content
51+
52+
- name: Show base64 content
53+
debug:
54+
msg: "{{ _fetched_content.response_b64 }}"
55+
"""
56+
57+
RETURN = r"""
58+
status_code:
59+
description: HTTP response code
60+
type: int
61+
returned: returned request
62+
content_type:
63+
description: HTTP response Content-Type header content
64+
type: str
65+
returned: returned request
66+
headers:
67+
description: HTTP response headers
68+
type: dict
69+
returned: returned request
70+
response_b64:
71+
description: Returned content base64 encoded
72+
type: str
73+
returned: successful request
74+
response_json:
75+
description: Returned content as a dict
76+
type: str
77+
returned: successful request that returned application/json
78+
path:
79+
description: Written file path
80+
type: str
81+
returned: successful request
82+
"""
83+
84+
import base64
85+
import os.path
86+
import re
87+
88+
from ansible.module_utils.basic import AnsibleModule
89+
90+
91+
try:
92+
from requests import get
93+
94+
python_requests_installed = True
95+
except ImportError:
96+
python_requests_installed = False
97+
try:
98+
from requests_kerberos import HTTPKerberosAuth, OPTIONAL
99+
100+
python_requests_kerberos_installed = True
101+
except ImportError:
102+
python_requests_kerberos_installed = False
103+
104+
105+
def main():
106+
module_args = {
107+
"url": {"type": "str", "required": True},
108+
"verify_ssl": {"type": "bool", "default": True},
109+
"dest": {"type": "str", "required": False},
110+
}
111+
112+
result = {
113+
"changed": False,
114+
}
115+
116+
module = AnsibleModule(
117+
argument_spec=module_args, supports_check_mode=False, add_file_common_args=True
118+
)
119+
120+
if not python_requests_installed:
121+
module.fail_json(msg="requests required for this module.")
122+
123+
if not python_requests_kerberos_installed:
124+
module.fail_json(msg="requests_kerberos required for this module.")
125+
126+
url = module.params["url"]
127+
verify_ssl = module.params["verify_ssl"]
128+
129+
auth = HTTPKerberosAuth(mutual_authentication=OPTIONAL)
130+
try:
131+
response = get(url=url, auth=auth, verify=verify_ssl, allow_redirects=True)
132+
133+
result["status_code"] = response.status_code
134+
result["headers"] = dict(response.headers)
135+
result["content_type"] = response.headers.get("Content-Type", None)
136+
137+
if response.status_code < 200 or response.status_code >= 300:
138+
module.fail_json(
139+
msg=f"Error fetching the information {response.status_code}: {response.text}"
140+
)
141+
142+
result["response_b64"] = base64.b64encode(response.content)
143+
if "application/json" in result["content_type"]:
144+
try:
145+
result["response_json"] = response.json()
146+
except ValueError as e:
147+
module.fail_json(msg=f"Error with the JSON response: {str(e)}")
148+
149+
if "dest" in module.params:
150+
dest = module.params["dest"]
151+
if (
152+
os.path.exists(dest)
153+
and os.path.isdir(dest)
154+
and "content-disposition" in response.headers
155+
):
156+
# Destination is a directory but the filename is available in Content-Disposition
157+
filename = re.findall(
158+
"filename=(.+)", response.headers["content-disposition"]
159+
)
160+
dest = filename[0] if filename else None
161+
elif os.path.exists(dest) and os.path.isdir(dest):
162+
# Destination is a directory but we cannot guess the filename from Content-Disposition
163+
dest = None
164+
165+
if not dest:
166+
# Reached if dest points to a directory and:
167+
# - Content-Disposition not available
168+
# - Cannot extract the filename part from the Content-Disposition header
169+
module.fail_json(
170+
msg="Destination points to a directory and the filename cannot be retrieved from the response"
171+
)
172+
173+
exists = os.path.exists(dest)
174+
original_sha1 = module.sha1(dest) if exists else None
175+
with open(dest, mode="wb") as file:
176+
file.write(response.content)
177+
file_args = module.load_file_common_arguments(module.params, path=dest)
178+
result["changed"] = (
179+
(not exists)
180+
or (module.sha1(dest) != original_sha1)
181+
or module.set_fs_attributes_if_different(file_args, result["changed"])
182+
)
183+
result["path"] = dest
184+
185+
except Exception as e:
186+
module.fail_json(msg=f"Error fetching the information: {str(e)}")
187+
188+
module.exit_json(**result)
189+
190+
191+
if __name__ == "__main__":
192+
main()

roles/repo_setup/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ using `cifmw_repo_setup_src` role default var.
3030
* `cifmw_repo_setup_rhos_release_rpm`: (String) URL to rhos-release RPM.
3131
* `cifmw_repo_setup_rhos_release_args`: (String) Parameters to pass down to `rhos-release`.
3232
* `cifmw_repo_setup_rhos_release_gpg_check`: (Bool) Skips the gpg check during rhos-release rpm installation. Defaults to `True`.
33+
* `cifmw_repo_setup_rhos_release_path`: (String) The path where the rhos-release rpm is downloaded. Defaults to `{{ cifmw_repo_setup_basedir }}/rhos-release`.
3334

3435
## Notes
3536

roles/repo_setup/defaults/main.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,4 @@ cifmw_repo_setup_component_promotion_tag: component-ci-testing
4040
# cifmw_repo_setup_rhos_release_args: <arguments for rhos-release utility>
4141
cifmw_repo_setup_enable_rhos_release: false
4242
cifmw_repo_setup_rhos_release_gpg_check: true
43+
cifmw_repo_setup_rhos_release_path: "{{ cifmw_repo_setup_basedir }}/rhos-release"

roles/repo_setup/tasks/rhos_release.yml

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,29 @@
11
---
2+
- name: Download RHOS Release if the given rpm is a URL
3+
when: cifmw_repo_setup_rhos_release_rpm is url
4+
block:
5+
- name: Create download directory
6+
ansible.builtin.file:
7+
path: "{{ cifmw_repo_setup_rhos_release_path }}"
8+
state: directory
9+
mode: "0755"
10+
11+
- name: Download the RPM
12+
cifmw.general.url_request:
13+
url: "{{ cifmw_repo_setup_rhos_release_rpm }}"
14+
dest: "{{ cifmw_repo_setup_rhos_release_path }}/rhos-release.rpm"
15+
mode: "0644"
16+
register: _cifmw_repo_setup_url_get
17+
218
- name: Install RHOS Release tool
319
become: true
420
ansible.builtin.package:
5-
name: "{{ cifmw_repo_setup_rhos_release_rpm }}"
21+
name: >-
22+
{{
23+
cifmw_repo_setup_rhos_release_rpm
24+
if cifmw_repo_setup_rhos_release_rpm is not url
25+
else _cifmw_repo_setup_url_get.path
26+
}}
627
state: present
728
disable_gpg_check: "{{ cifmw_repo_setup_rhos_release_gpg_check | bool }}"
829

tests/sanity/ignore.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ plugins/modules/generate_make_tasks.py validate-modules:missing-gplv3-license #
33
plugins/modules/tempest_list_allowed.py validate-modules:missing-gplv3-license # ignore license check
44
plugins/modules/tempest_list_skipped.py validate-modules:missing-gplv3-license # ignore license check
55
plugins/modules/cephx_key.py validate-modules:missing-gplv3-license # ignore license check
6+
plugins/modules/url_request.py validate-modules:missing-gplv3-license # ignore license check

0 commit comments

Comments
 (0)