Skip to content

Commit c1dfecd

Browse files
publish retrieve changes via SSH (#269)
* publish retrieve changes * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * changes to _clone function * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix pre commit * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix pre commit * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix pre commit * fix pre commit * added changelog * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * added ssh integration tcs * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix CI * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix file * fix pattern.json * fix ansible lint * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix ci * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix ci * fix ci * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix ci * delete * fix ci * fix ci * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * ci fix * fix pre commit * fix pre commit * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * update changelog * changed playbook for ci * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * updated doc * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix docs * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix docs * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix ssh for AAP * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * change readme * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * remove patterns directory * remove backup --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 247927b commit c1dfecd

File tree

17 files changed

+376
-97
lines changed

17 files changed

+376
-97
lines changed

.github/workflows/check_label.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
name: "Check label"
2+
name: "Check PR label 👓"
33
concurrency:
44
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
55
cancel-in-progress: true

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
name: "Release collection"
2+
name: "Release collection 🚀"
33
on: # yamllint disable-line rule:truthy
44
release:
55
types: [published]

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: test
1+
name: "Collection Tests 🧪"
22

33
concurrency:
44
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
---
22
repos:
33
- repo: https://github.com/ansible-network/collection_prep
4-
rev: 1.1.1
4+
rev: da78082ea59b03ad16cd7dbee267f53e8ff50bb5
55
hooks:
66
- id: update-docs
77

88
- repo: https://github.com/pre-commit/pre-commit-hooks
9-
rev: v4.6.0
9+
rev: v6.0.0
1010
hooks:
1111
- id: check-merge-conflict
1212
- id: check-symlinks

.yamllint

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ rules:
1919
line-length: disable
2020
truthy:
2121
check-keys: false
22+
23+
octal-values:
24+
forbid-implicit-octal: true
25+
forbid-explicit-octal: true

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@ This repository contains the `ansible.scm` Ansible Collection that allows you to
55
<!--start requires_ansible-->
66
## Ansible version compatibility
77

8-
This collection has been tested against following Ansible versions: **>=2.15.0**.
8+
This collection has been tested against the following Ansible versions: **>=2.15.0**.
99

10-
For collections that support Ansible 2.9, please ensure you update your `network_os` to use the
11-
fully qualified collection name (for example, `cisco.ios.ios`).
1210
Plugins and modules within a collection may be tested with only specific Ansible versions.
1311
A collection may contain metadata that identifies these versions.
1412
PEP440 is the schema used to describe the versions of Ansible.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
---
2+
minor_changes:
3+
- Add ssh support for git_publish/git_retrieve and fix CI.

docs/ansible.scm.git_publish_module.rst

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,39 @@ Parameters
138138
<div>Remove the local copy of the repository if the push is successful</div>
139139
</td>
140140
</tr>
141+
<tr>
142+
<td colspan="2">
143+
<div class="ansibleOptionAnchor" id="parameter-"></div>
144+
<b>ssh_key_content</b>
145+
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
146+
<div style="font-size: small">
147+
<span style="color: purple">string</span>
148+
</div>
149+
</td>
150+
<td>
151+
</td>
152+
<td>
153+
<div>The content of the SSH private key for authentication with git.</div>
154+
<div>Ideal for use with Ansible Vault or other secret management systems.</div>
155+
<div>Used only for SSH-based repository URLs.</div>
156+
</td>
157+
</tr>
158+
<tr>
159+
<td colspan="2">
160+
<div class="ansibleOptionAnchor" id="parameter-"></div>
161+
<b>ssh_key_file</b>
162+
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
163+
<div style="font-size: small">
164+
<span style="color: purple">string</span>
165+
</div>
166+
</td>
167+
<td>
168+
</td>
169+
<td>
170+
<div>Path to the SSH private key file to use for authentication with git.</div>
171+
<div>Used only for SSH-based repository URLs (e.g., [email protected]:...).</div>
172+
</td>
173+
</tr>
141174
<tr>
142175
<td colspan="2">
143176
<div class="ansibleOptionAnchor" id="parameter-"></div>

docs/ansible.scm.git_retrieve_module.rst

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,41 @@ Parameters
133133
</tr>
134134
<tr>
135135
<td class="elbow-placeholder"></td>
136+
<td colspan="1">
137+
<div class="ansibleOptionAnchor" id="parameter-"></div>
138+
<b>ssh_key_content</b>
139+
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
140+
<div style="font-size: small">
141+
<span style="color: purple">string</span>
142+
</div>
143+
</td>
144+
<td>
145+
</td>
146+
<td>
147+
<div>The content of the SSH private key for authentication with the origin repository.</div>
148+
<div>Ideal for use with Ansible Vault or other secret management systems.</div>
149+
<div>Used only for SSH-based repository URLs.</div>
150+
</td>
151+
</tr>
152+
<tr>
153+
<td class="elbow-placeholder"></td>
154+
<td colspan="1">
155+
<div class="ansibleOptionAnchor" id="parameter-"></div>
156+
<b>ssh_key_file</b>
157+
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
158+
<div style="font-size: small">
159+
<span style="color: purple">string</span>
160+
</div>
161+
</td>
162+
<td>
163+
</td>
164+
<td>
165+
<div>Path to the SSH private key file to use for authentication with the origin repository.</div>
166+
<div>Used only for SSH-based repository URLs (e.g., [email protected]:...).</div>
167+
</td>
168+
</tr>
169+
<tr>
170+
<td class="elbow-placeholder"></td>
136171
<td colspan="1">
137172
<div class="ansibleOptionAnchor" id="parameter-"></div>
138173
<b>tag</b>

plugins/action/git_publish.py

Lines changed: 70 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,14 @@
55

66
from __future__ import absolute_import, division, print_function
77

8+
import os
89
import shutil
10+
import tempfile
911
import webbrowser
1012

1113
from contextlib import suppress
1214
from dataclasses import asdict, dataclass
15+
from pathlib import Path
1316
from typing import Dict, List, Optional, TypeVar, Union
1417

1518
from ansible.errors import AnsibleActionFail
@@ -50,6 +53,7 @@ class Result(ResultBase):
5053
T = TypeVar("T", bound="ActionModule") # pylint: disable=invalid-name, useless-suppression
5154

5255

56+
# pylint: disable=too-many-instance-attributes
5357
class ActionModule(GitBase):
5458
"""The retrieve action plugin."""
5559

@@ -90,6 +94,8 @@ def __init__( # noqa: PLR0913
9094
self._play_name: str = ""
9195
self._supports_async = True
9296
self._result: Result = Result()
97+
self._env: Optional[Dict[str, str]] = None
98+
self._temp_ssh_key_path: Optional[str] = None
9399

94100
def _check_argspec(self: T) -> None:
95101
"""Check the argspec for the action plugin.
@@ -107,6 +113,34 @@ def _check_argspec(self: T) -> None:
107113
if self._task.args.get("token") == "":
108114
err = "Token can not be an empty string"
109115
raise AnsibleActionFail(err)
116+
if self._task.args.get("ssh_key_file") and self._task.args.get("ssh_key_content"):
117+
msg = "Parameters `ssh_key_file` and `ssh_key_content` are mutually exclusive."
118+
raise AnsibleActionFail(msg)
119+
120+
def _prepare_ssh_environment(self: T) -> None:
121+
"""Prepare the environment for SSH key authentication."""
122+
key_content = self._task.args.get("ssh_key_content")
123+
key_file = self._task.args.get("ssh_key_file")
124+
125+
if not key_content and not key_file:
126+
return
127+
128+
if key_content:
129+
fd, self._temp_ssh_key_path = tempfile.mkstemp(prefix="ansible-git-key-")
130+
os.write(fd, key_content.encode("utf-8"))
131+
os.close(fd)
132+
Path(self._temp_ssh_key_path).chmod(0o600)
133+
key_path = self._temp_ssh_key_path
134+
else:
135+
key_path = key_file
136+
137+
ssh_command = f"ssh -i {key_path} -o IdentitiesOnly=yes -o StrictHostKeyChecking=no"
138+
self._env = {"GIT_SSH_COMMAND": ssh_command}
139+
140+
def _cleanup_ssh_key(self: T) -> None:
141+
"""Remove the temporary SSH key file if it was created."""
142+
if self._temp_ssh_key_path:
143+
Path(self._temp_ssh_key_path).unlink()
110144

111145
def _configure_git_user_name(self: T) -> None:
112146
"""Configure the git user name."""
@@ -115,6 +149,7 @@ def _configure_git_user_name(self: T) -> None:
115149
command = Command(
116150
command_parts=command_parts,
117151
fail_msg="Failed to get current user name for git.",
152+
env=self._env,
118153
)
119154
self._run_command(command=command, ignore_errors=True)
120155
if command.stdout:
@@ -127,6 +162,7 @@ def _configure_git_user_name(self: T) -> None:
127162
command = Command(
128163
command_parts=command_parts,
129164
fail_msg="Failed to configure git user name",
165+
env=self._env,
130166
)
131167
self._run_command(command=command)
132168
self._result.user_name = name
@@ -138,6 +174,7 @@ def _configure_git_user_email(self: T) -> None:
138174
command = Command(
139175
command_parts=command_parts,
140176
fail_msg="Failed to get current user email for git.",
177+
env=self._env,
141178
)
142179
self._run_command(command=command, ignore_errors=True)
143180
if command.stdout:
@@ -150,6 +187,7 @@ def _configure_git_user_email(self: T) -> None:
150187
command = Command(
151188
command_parts=command_parts,
152189
fail_msg="Failed to configure git user email",
190+
env=self._env,
153191
)
154192
self._run_command(command=command)
155193
self._result.user_email = email
@@ -162,6 +200,7 @@ def _add(self: T) -> None:
162200
command = Command(
163201
command_parts=command_parts,
164202
fail_msg=f"Failed to add the file to the pending commit: {files}",
203+
env=self._env,
165204
)
166205
self._run_command(command=command)
167206

@@ -174,6 +213,7 @@ def _commit(self: T) -> None:
174213
command = Command(
175214
command_parts=command_parts,
176215
fail_msg=f"Failed to perform the commit: {message}",
216+
env=self._env,
177217
)
178218
self._run_command(command=command)
179219

@@ -189,6 +229,7 @@ def _tag(self: T) -> None:
189229
command = Command(
190230
command_parts=command_parts,
191231
fail_msg=f"Failed to perform tagging: {message}",
232+
env=self._env,
192233
)
193234
self._run_command(command=command)
194235

@@ -199,6 +240,7 @@ def _push(self: T) -> None:
199240
command = Command(
200241
command_parts=command_parts,
201242
fail_msg="Failed to get remote",
243+
env=self._env,
202244
)
203245

204246
self._run_command(command=command)
@@ -228,6 +270,7 @@ def _push(self: T) -> None:
228270
command_parts=command_parts,
229271
fail_msg="Failed to perform the push",
230272
no_log=no_log,
273+
env=self._env,
231274
)
232275
self._run_command(command=command)
233276
with suppress(StopIteration):
@@ -262,32 +305,38 @@ def run(
262305
self._task.diff = False
263306
super().run(task_vars=task_vars)
264307

265-
self._check_argspec()
266-
if self._result.failed:
267-
return asdict(self._result)
308+
try:
309+
self._check_argspec()
310+
if self._result.failed:
311+
return asdict(self._result)
268312

269-
self._path_to_repo = self._task.args["path"]
270-
self._base_command = ("git", "-C", self._path_to_repo)
271-
self._timeout = self._task.args["timeout"]
313+
self._prepare_ssh_environment()
272314

273-
steps = [
274-
self._configure_git_user_name,
275-
self._configure_git_user_email,
276-
self._add,
277-
self._commit,
278-
]
279-
if self._task.args.get("tag"):
280-
steps.append(self._tag)
315+
self._path_to_repo = self._task.args["path"]
316+
self._base_command = ("git", "-C", self._path_to_repo)
317+
self._timeout = self._task.args["timeout"]
281318

282-
steps.extend([self._push, self._remove_repo])
319+
steps = [
320+
self._configure_git_user_name,
321+
self._configure_git_user_email,
322+
self._add,
323+
self._commit,
324+
]
325+
if self._task.args.get("tag"):
326+
steps.append(self._tag)
283327

284-
for step in steps:
285-
step()
286-
if self._result.failed:
287-
return asdict(self._result)
328+
steps.extend([self._push, self._remove_repo])
329+
330+
for step in steps:
331+
step()
332+
if self._result.failed:
333+
return asdict(self._result)
334+
335+
if self._result.pr_url and self._task.args["open_browser"]:
336+
webbrowser.open(self._result.pr_url, new=2)
288337

289-
if self._result.pr_url and self._task.args["open_browser"]:
290-
webbrowser.open(self._result.pr_url, new=2)
338+
finally:
339+
self._cleanup_ssh_key()
291340

292341
self._result.msg = f"Successfully published local changes from: {self._path_to_repo}"
293342
return asdict(self._result)

0 commit comments

Comments
 (0)