Skip to content

Commit b060f3c

Browse files
authored
Merge pull request #1 from ISISComputingGroup/Ticket8056_Automated_check_of_hotfix_status
Ticket8056_automated_check_of_hotfix status
2 parents 8649c54 + edbb3e3 commit b060f3c

File tree

8 files changed

+493
-1
lines changed

8 files changed

+493
-1
lines changed

Jenkinsfile

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#!groovy
2+
3+
pipeline {
4+
// using same agnert as ConfigCheck job
5+
agent {
6+
label {
7+
label 'ConfigCheck'
8+
}
9+
}
10+
11+
triggers {
12+
cron('H 8 * * *')
13+
}
14+
15+
environment {
16+
SSH_CREDENTIALS = credentials('SSH')
17+
TEST_INSTRUMENT_LIST = "${TEST_INSTRUMENT_LIST}"
18+
USE_TEST_INSTRUMENT_LIST = "${USE_TEST_INSTRUMENT_LIST}"
19+
DEBUG_MODE = "${DEBUG_MODE}"
20+
}
21+
22+
stages {
23+
stage('Checkout') {
24+
steps {
25+
timeout(time: 2, unit: 'HOURS') {
26+
retry(5) {
27+
checkout scm
28+
}
29+
}
30+
}
31+
}
32+
33+
stage('Check Instrument has any Hotfixes and then any uncommitteed changes') {
34+
steps {
35+
echo 'Check Instrument has any Hotfixes and then any uncommitteed changes'
36+
timeout(time: 1, unit: 'HOURS') {
37+
bat '''
38+
call hotfix_checker.bat
39+
'''
40+
}
41+
}
42+
}
43+
}
44+
45+
post {
46+
always {
47+
logParser([
48+
projectRulePath: 'parse_rules',
49+
parsingRulesPath: '',
50+
showGraphs: true,
51+
unstableOnWarning: true,
52+
useProjectRule: true,
53+
])
54+
}
55+
}
56+
}

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
# HotfixStatusChecker
1+
# HotfixStatusChecker Jenkins Pipeline
2+
This is a Jenkins pipeline to check the status of the EPICS repositories on instrument machines.
3+
It fails when there are uncommitted changes or an instrument is unreachable either via branch or hotfix.

get_python.bat

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
CALL \\isis\Shares\ISIS_Experiment_Controls_Public\ibex_utils\installation_and_upgrade\define_latest_genie_python.bat 3
2+
3+
if exist "C:\HotfixStatusChecker\Python3" rd /s /q C:\HotfixStatusChecker\Python3
4+
call %LATEST_PYTHON_DIR%..\genie_python_install.bat C:\HotfixStatusChecker\Python3
5+
6+
set "LATEST_PYTHON3=C:\HotfixStatusChecker\Python3\python3.exe"
7+
%LATEST_PYTHON3% -m pip install python-ssh

hotfix_checker.bat

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
setlocal
2+
REM Requires Python3 and python-ssh to be installed in C:\HotfixStatusChecker\Python3 on Jenkins agent
3+
REM to setup run get_python.bat on the Jenkins agent
4+
5+
set "LATEST_PYTHON3=C:\HotfixStatusChecker\Python3\python3.exe"
6+
%LATEST_PYTHON3% -u hotfix_checker.py
7+
if %errorlevel% neq 0 exit /b %errorlevel%

hotfix_checker.py

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
from enum import Enum
2+
import os
3+
import sys
4+
from util.channel_access import ChannelAccessUtils
5+
from util.ssh_access import SSHAccessUtils
6+
import requests
7+
8+
EPICS_DIR = "C:\\Instrument\\Apps\\EPICS\\"
9+
SSH_USERNAME = os.environ["SSH_CREDENTIALS_USR"]
10+
SSH_PASSWORD = os.environ["SSH_CREDENTIALS_PSW"]
11+
12+
USE_TEST_INSTRUMENT_LIST = os.environ["USE_TEST_INSTRUMENT_LIST"] == "true"
13+
TEST_INSTRUMENT_LIST = os.environ["TEST_INSTRUMENT_LIST"]
14+
INST_CONFIG_VERSION_TXT_RAW_DATA_URL = "https://control-svcs.isis.cclrc.ac.uk/git/?p=instconfigs/inst.git;a=blob_plain;f=configurations/config_version.txt;hb=refs/heads/"
15+
16+
DEBUG_MODE = os.environ["DEBUG_MODE"] == "true"
17+
18+
19+
class CHECK(Enum):
20+
UNDETERMINABLE = 0
21+
TRUE = 1
22+
FALSE = 2
23+
24+
25+
def get_insts_on_latest_ibex_via_inst_congif():
26+
""" Get a list of instruments that are on the latest version of IBEX via the inst_config file.
27+
28+
Returns:
29+
list: A list of instruments that are on the latest version of IBEX.
30+
"""
31+
instrument_list = ChannelAccessUtils().get_inst_list()
32+
result_list = []
33+
for instrument in instrument_list:
34+
if not instrument['seci']:
35+
version = requests.get(
36+
INST_CONFIG_VERSION_TXT_RAW_DATA_URL + instrument['hostName']).text
37+
version_first_number = int(version.strip().split(".")[0])
38+
if DEBUG_MODE:
39+
print(
40+
f"DEBUG: Found instrument {instrument['name']} on IBEX version {version_first_number}")
41+
if version_first_number is not None and version_first_number != "None" and version_first_number != "":
42+
result_list.append(
43+
{'hostname': instrument['hostName'], 'version': version_first_number})
44+
45+
# Get the latest version of IBEX
46+
latest_version = max([inst["version"]
47+
for inst in result_list])
48+
49+
# filter out the instruments that are not on the latest version
50+
insts_on_latest_ibex = [inst["hostname"] for inst in result_list if
51+
inst["version"] == latest_version]
52+
53+
return insts_on_latest_ibex
54+
55+
56+
def check_for_uncommitted_changes(hostname):
57+
""" Check if there are any uncommitted changes on the instrument via SSH.
58+
59+
Args:
60+
hostname (str): The hostname to connect to.
61+
62+
Returns:
63+
CHECK: The result of the check.
64+
"""
65+
command = f"cd {EPICS_DIR} && git status --porcelain"
66+
ssh_process = SSHAccessUtils.runSSHCommand(
67+
hostname, SSH_USERNAME, SSH_PASSWORD, command)
68+
69+
if ssh_process['success']:
70+
if ssh_process['output'].strip() != "":
71+
return CHECK.TRUE
72+
else:
73+
74+
return CHECK.FALSE
75+
else:
76+
return CHECK.UNDETERMINABLE
77+
78+
79+
def get_parent_branch(hostname):
80+
""" Get the parent branch of the instrument branch.
81+
82+
Args:
83+
hostname (str): The hostname to connect to.
84+
85+
Returns:
86+
str: The name of the parent branch.
87+
"""
88+
command = f"cd {EPICS_DIR} && git log"
89+
ssh_process = SSHAccessUtils.runSSHCommand(
90+
hostname, SSH_USERNAME, SSH_PASSWORD, command)
91+
if ssh_process['success']:
92+
if "galil-old" in ssh_process['output']:
93+
return "origin/galil-old"
94+
else:
95+
return "origin/main"
96+
else:
97+
return False
98+
99+
100+
def git_log_analyszer(hostname, changes_on=None, subtracted_against=None, prefix=None):
101+
""" Get the commit messages between two branches on the instrument.
102+
103+
Args:
104+
hostname (str): The hostname to connect to.
105+
changes_on (str): The branch to start from.
106+
subtracted_against (str): The branch to end at.
107+
prefix (str): The prefix to check for in commit messages.
108+
109+
Returns:
110+
CHECK: The result of the check.
111+
dict: A dictionary with the commit messages and their hashes.
112+
"""
113+
branch_details = None
114+
if changes_on and subtracted_against:
115+
branch_details = f"{subtracted_against}..{changes_on}"
116+
else:
117+
branch_details = ""
118+
119+
# fetch latest changes from the remote
120+
fetch_command = f"cd {EPICS_DIR} && git fetch origin"
121+
ssh_process_fetch = SSHAccessUtils.runSSHCommand(
122+
hostname, SSH_USERNAME, SSH_PASSWORD, fetch_command)
123+
if not ssh_process_fetch['success']:
124+
return CHECK.UNDETERMINABLE, None
125+
126+
command = f"cd {EPICS_DIR} && git log --format=\"%h %s\" {branch_details}"
127+
128+
ssh_process = SSHAccessUtils.runSSHCommand(
129+
hostname, SSH_USERNAME, SSH_PASSWORD, command)
130+
131+
commit_dict = {}
132+
133+
if ssh_process['success']:
134+
output = ssh_process['output']
135+
commit_lines = output.split("\n")
136+
commit_lines = [line for line in commit_lines if line.strip() != ""]
137+
for line in commit_lines:
138+
split_line = line.split(" ", 1)
139+
hash = split_line[0]
140+
message = split_line[1]
141+
if prefix == None or message.startswith(prefix):
142+
commit_dict[hash] = message
143+
else:
144+
return CHECK.UNDETERMINABLE, None
145+
146+
if len(commit_dict) > 0:
147+
return CHECK.TRUE, commit_dict
148+
else:
149+
return CHECK.FALSE, None
150+
151+
152+
def check_instrument(hostname):
153+
""" Check if there are any hotfixes or uncommitted changes on AN instrument.
154+
155+
Args:
156+
hostname (str): The hostname to connect to.
157+
158+
Returns:
159+
dict: A dictionary with the result of the checks.
160+
"""
161+
# Examples of how to use the git_log_analyszer function decided to not be used in this iteration of the check
162+
# Check if any hotfixes run on the instrument with the prefix "Hotfix:"
163+
# hotfix_commits_enum, hotfix_commits_messages = git_log_analyszer(
164+
# hostname, prefix="Hotfix:")
165+
166+
# Check if any upstream commits are not on the instrument, default to the parent origin branch, either main or galil-old
167+
# commits_pending_pulling_enum = git_log_analyszer(
168+
# hostname, changes_on=get_parent_branch(hostname), subtracted_against=hostname, prefix=None)
169+
170+
# Check if any unpushed commits run on the instrument
171+
unpushed_commits_enum, unpushed_commit_messages = git_log_analyszer(
172+
hostname, changes_on="HEAD", subtracted_against="origin/" + hostname, prefix=None)
173+
174+
# Check if any uncommitted changes run on the instrument
175+
uncommitted_changes_enum = check_for_uncommitted_changes(hostname)
176+
177+
# return the result of the checks
178+
instrument_status = {"commits_not_pushed_messages": unpushed_commit_messages, "commits_not_pushed": unpushed_commits_enum,
179+
"uncommitted_changes": uncommitted_changes_enum}
180+
181+
return instrument_status
182+
183+
184+
def check_instruments():
185+
""" Run checks on all instruments to find hotfix/changes and log the results.
186+
187+
Returns:
188+
None
189+
"""
190+
if USE_TEST_INSTRUMENT_LIST:
191+
instrument_list = TEST_INSTRUMENT_LIST.split(",")
192+
instrument_list = [instrument.strip()
193+
for instrument in instrument_list]
194+
if "" in instrument_list:
195+
instrument_list.remove("")
196+
else:
197+
instrument_list = get_insts_on_latest_ibex_via_inst_congif()
198+
199+
instrument_status_lists = {"uncommitted_changes": [], "unreachable_at_some_point": [
200+
], "unpushed_commits": []}
201+
202+
for instrument in instrument_list:
203+
try:
204+
print(f"INFO: Checking {instrument}")
205+
instrument_status = check_instrument(instrument)
206+
207+
if DEBUG_MODE:
208+
print("DEBUG: " + str(instrument_status))
209+
210+
if instrument_status['commits_not_pushed'] == CHECK.TRUE:
211+
instrument_status_lists["unpushed_commits"].append(instrument + " " + str(
212+
instrument_status['commits_not_pushed_messages']))
213+
elif instrument_status['commits_not_pushed'] == CHECK.UNDETERMINABLE and instrument not in instrument_status_lists["unreachable_at_some_point"]:
214+
instrument_status_lists["unreachable_at_some_point"].append(
215+
instrument)
216+
217+
if instrument_status['uncommitted_changes'] == CHECK.TRUE:
218+
instrument_status_lists["uncommitted_changes"].append(
219+
instrument)
220+
elif instrument_status['uncommitted_changes'] == CHECK.UNDETERMINABLE and instrument not in instrument_status_lists["unreachable_at_some_point"]:
221+
instrument_status_lists["unreachable_at_some_point"].append(
222+
instrument)
223+
except Exception as e:
224+
print(f"ERROR: Could not connect to {instrument} ({str(e)})")
225+
if instrument not in instrument_status_lists["unreachable_at_some_point"]:
226+
instrument_status_lists["unreachable_at_some_point"].append(
227+
instrument)
228+
229+
print("INFO: Summary of results")
230+
if len(instrument_status_lists['uncommitted_changes']) > 0:
231+
print(
232+
f"ERROR: Uncommitted changes: {instrument_status_lists['uncommitted_changes']}")
233+
else:
234+
print(
235+
f"Uncommitted changes: {instrument_status_lists['uncommitted_changes']}")
236+
if len(instrument_status_lists['unpushed_commits']) > 0:
237+
print(
238+
f"ERROR: Commits not pushed: {instrument_status_lists['unpushed_commits']}")
239+
else:
240+
print(
241+
f"Commits not pushed: {instrument_status_lists['unpushed_commits']}")
242+
243+
if len(instrument_status_lists['unreachable_at_some_point']) > 0:
244+
print(
245+
f"ERROR: Unreachable at some point: {instrument_status_lists['unreachable_at_some_point']}")
246+
else:
247+
print(
248+
f"Unreachable at some point: {instrument_status_lists['unreachable_at_some_point']}")
249+
250+
# Check if any instrument in hotfix_status_each_instrument has uncommitted changes or is unreachable
251+
if len(instrument_status_lists["uncommitted_changes"]) > 0:
252+
sys.exit(1)
253+
if len(instrument_status_lists["unreachable_at_some_point"]) > 0:
254+
sys.exit(1)
255+
if len(instrument_status_lists["unpushed_commits"]) > 0:
256+
sys.exit(1)
257+
258+
# If no instruments have uncommitted changes or are unreachable, exit with ok status
259+
sys.exit(0)
260+
261+
262+
if __name__ == '__main__':
263+
check_instruments()

parse_rules

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
error /(?i)error:?/
2+
warning /(?i)warning:? /
3+
info /(?i)info:? /
4+
debug /(?i)debug:? /

0 commit comments

Comments
 (0)