Skip to content

Commit e9cccfd

Browse files
committed
disable_autosync: use ArgoCD API
this contains only python part for now
1 parent f6d5fc9 commit e9cccfd

File tree

3 files changed

+152
-1
lines changed

3 files changed

+152
-1
lines changed

python/understack-workflows/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ enroll-server = "understack_workflows.main.enroll_server:main"
5252
bmc-password = "understack_workflows.main.print_bmc_password:main"
5353
bmc-kube-password = "understack_workflows.main.bmc_display_password:main"
5454
sync-network-segment-range = "understack_workflows.main.sync_ucvni_group_range:main"
55+
disable-autosync = "understack_workflows.main.disable_autosync:main"
5556

5657
[tool.pytest.ini_options]
5758
minversion = "6.0"

python/understack-workflows/understack_workflows/helpers.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import argparse
22
import logging
3+
import os
34
import pathlib
45
from functools import partial
56
from urllib.parse import urlparse
@@ -56,6 +57,8 @@ def parser_nautobot_args(parser: argparse.ArgumentParser) -> argparse.ArgumentPa
5657

5758

5859
def credential(subpath, item):
59-
ref = pathlib.Path("/etc").joinpath(subpath).joinpath(item)
60+
ref = (
61+
pathlib.Path(os.getenv("SECRETS_DIR", "/etc")).joinpath(subpath).joinpath(item)
62+
)
6063
with ref.open() as f:
6164
return f.read().strip()
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import argparse
2+
import json
3+
import os
4+
import sys
5+
6+
import requests
7+
from helpers import credential
8+
9+
from understack_workflows.helpers import setup_logger
10+
11+
logger = setup_logger(__name__)
12+
13+
14+
def argument_parser():
15+
parser = argparse.ArgumentParser(
16+
prog=os.path.basename(__file__),
17+
description="Switch auto-sync status on an Application",
18+
)
19+
parser.add_argument(
20+
"--automated-sync",
21+
type=bool,
22+
required=True,
23+
help="Requested state of automated sync",
24+
)
25+
parser.add_argument(
26+
"--app-name", type=str, required=True, help="Name of the Application"
27+
)
28+
29+
return parser
30+
31+
32+
APP_NAME = "understack"
33+
REQUEST_TIMEOUT_LOGIN = 30
34+
REQUEST_TIMEOUT_PATCH = 10
35+
# TODO: we may need to change this to True and provide appropriate CA certificate
36+
VERIFY_SSL = False
37+
if not VERIFY_SSL:
38+
import urllib3
39+
40+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
41+
42+
43+
def argocd_credentials():
44+
"""Reads ArgoCD server, username, and password from mounted secret files."""
45+
argocd_server_host = credential(APP_NAME, "server_host")
46+
argocd_user = credential(APP_NAME, "user")
47+
argocd_pass = credential(APP_NAME, "password")
48+
49+
if not all([argocd_server_host, argocd_user, argocd_pass]):
50+
logger.error(
51+
"One or more ArgoCD credentials are empty after reading from secret."
52+
)
53+
sys.exit(1)
54+
return argocd_server_host, argocd_user, argocd_pass
55+
56+
57+
def get_argocd_token(session, api_base_url, username, password):
58+
"""Logs into ArgoCD and returns an authentication token."""
59+
logger.info("Logging into ArgoCD...")
60+
session_url = f"{api_base_url}/session"
61+
login_data = {"username": username, "password": password}
62+
63+
try:
64+
response = session.post(
65+
session_url, json=login_data, timeout=REQUEST_TIMEOUT_LOGIN
66+
)
67+
response.raise_for_status()
68+
token_data = response.json()
69+
token = token_data.get("token")
70+
if not token:
71+
logger.error("Failed to retrieve ArgoCD token from login response.")
72+
sys.exit(1)
73+
logger.debug("Successfully obtained ArgoCD token.")
74+
return token
75+
except requests.exceptions.RequestException as e:
76+
logger.error("ArgoCD login failed: %s", e)
77+
sys.exit(1)
78+
79+
80+
def patch_argocd_application(session, api_base_url, app_name, token, action):
81+
"""Patches the specified ArgoCD application."""
82+
app_patch_url = f"{api_base_url}/applications/{app_name}"
83+
headers = {
84+
"Authorization": f"Bearer {token}",
85+
"Content-Type": "application/json",
86+
}
87+
payload = {
88+
"patchType": "application/merge-patch+json",
89+
"patch": '{"spec": {"syncPolicy": {"automated": {"selfHeal": action}}}}',
90+
}
91+
logger.debug(
92+
"Patching Application '%(app_name)s' to '%(action)s'.Payload: %(payload)s",
93+
extra=dict(app_name=app_name, action=action, payload=json.dumps(payload)),
94+
)
95+
96+
try:
97+
response = session.patch(
98+
app_patch_url, json=payload, headers=headers, timeout=REQUEST_TIMEOUT_PATCH
99+
)
100+
response.raise_for_status()
101+
logger.debug(
102+
"Successfully patched Application '%(app_name)s'. "
103+
"Action: '%(action)s'. Status: %(code)s",
104+
extra=dict(action=action, app_name=app_name, code=response.status_code),
105+
)
106+
return True
107+
except requests.exceptions.RequestException as e:
108+
logger.error("Failed to patch ArgoCD Application '%s': %s", app_name, e)
109+
if hasattr(e, "response") and e.response is not None:
110+
logger.error("Error Response: %s", e.response.text)
111+
return False
112+
113+
114+
def main():
115+
"""Switch auto-sync status on an Application.
116+
117+
This updates an Application syncPolicy to a requested state.
118+
"""
119+
args = argument_parser().parse_args()
120+
121+
action = "enable" if args.automated_sync else "disable"
122+
logger.info(
123+
"changing syncPolicy to '%s' for ArgoCD Application: %s", action, args.app_name
124+
)
125+
126+
argocd_server_host, argocd_user, argocd_pass = argocd_credentials()
127+
api_base_url = f"https://{argocd_server_host}/api/v1"
128+
logger.info("ArgoCD API URL: %s, User: %s", api_base_url, argocd_user)
129+
130+
with requests.Session() as http_session:
131+
http_session.verify = VERIFY_SSL
132+
token = get_argocd_token(http_session, api_base_url, argocd_user, argocd_pass)
133+
134+
if not patch_argocd_application(
135+
http_session, api_base_url, args.app_name, token, args.automated_sync
136+
):
137+
sys.exit(1)
138+
139+
logger.info(
140+
"Action '%s' completed successfully for Application '%s'.",
141+
action,
142+
args.app_name,
143+
)
144+
145+
146+
if __name__ == "__main__":
147+
main()

0 commit comments

Comments
 (0)