Skip to content

Commit bdcfee6

Browse files
authored
Merge pull request #134 from chmeliik/azure-secrets-python
azure: reimplement set-vars script in Python
2 parents e52889f + 21a2d64 commit bdcfee6

File tree

3 files changed

+185
-132
lines changed

3 files changed

+185
-132
lines changed

hack/_azure-set-vars.sh

Lines changed: 0 additions & 100 deletions
This file was deleted.

hack/azure-set-vars.sh

Lines changed: 0 additions & 32 deletions
This file was deleted.

hack/azure_set_vars.py

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
#!/usr/bin/env python
2+
3+
"""Set variables for Azure DevOps Pipelines.
4+
5+
Create a variable group called $AZURE_VARIABLE_GROUP_NAME (if it doesn't exist)
6+
and set all the variables needed for the RHTAP pipeline.
7+
8+
Before running this script, source your .env or .envrc file.
9+
10+
Requires python>=3.9 (just the standard library).
11+
"""
12+
13+
import io
14+
import json
15+
import logging
16+
import os
17+
import urllib.error
18+
import urllib.parse
19+
import urllib.request
20+
from typing import Any, NamedTuple, Optional
21+
22+
logging.basicConfig(
23+
level=logging.DEBUG, format="%(asctime)s [%(levelname)s] %(message)s"
24+
)
25+
log = logging.getLogger(__name__)
26+
27+
28+
class AzureAPI:
29+
def __init__(self, token: str, org_name: str, project_name: str) -> None:
30+
self._token = token
31+
self._org_name = org_name
32+
self._project_name = project_name
33+
34+
def get_variable_group(self, name: str) -> Optional[dict[str, Any]]:
35+
# https://learn.microsoft.com/en-us/rest/api/azure/devops/distributedtask/variablegroups/get-variable-groups?view=azure-devops-rest-7.1
36+
resp = self._do_request(
37+
"GET",
38+
f"{self._project_name}/_apis/distributedtask/variablegroups",
39+
query={"groupName": name},
40+
)
41+
if not (groups := resp["value"]):
42+
return None
43+
44+
return groups[0]
45+
46+
def add_variable_group(self, variable_group: dict[str, Any]) -> dict[str, Any]:
47+
# https://learn.microsoft.com/en-us/rest/api/azure/devops/distributedtask/variablegroups/add?view=azure-devops-rest-7.1
48+
return self._do_request(
49+
"POST",
50+
"_apis/distributedtask/variablegroups",
51+
data=self._add_project_reference(variable_group),
52+
)
53+
54+
def update_variable_group(
55+
self, id: int, variable_group: dict[str, Any]
56+
) -> dict[str, Any]:
57+
# https://learn.microsoft.com/en-us/rest/api/azure/devops/distributedtask/variablegroups/update?view=azure-devops-rest-7.1
58+
return self._do_request(
59+
"PUT",
60+
f"_apis/distributedtask/variablegroups/{id}",
61+
data=self._add_project_reference(variable_group),
62+
)
63+
64+
def _do_request(
65+
self,
66+
method: str,
67+
path: str,
68+
query: Optional[dict[str, str]] = None,
69+
data: Optional[dict[str, Any]] = None,
70+
) -> dict[str, Any]:
71+
if query is not None:
72+
query = query.copy()
73+
else:
74+
query = {}
75+
76+
query.setdefault("api-version", "7.1")
77+
78+
if data:
79+
data_buffer = io.BytesIO(json.dumps(data).encode())
80+
else:
81+
data_buffer = None
82+
83+
query_str = urllib.parse.urlencode(query)
84+
url = f"https://dev.azure.com/{self._org_name}/{path}?{query_str}"
85+
86+
request = urllib.request.Request(url, method=method)
87+
request.add_header("Authorization", f"Bearer {self._token}")
88+
request.add_header("Content-Type", "application/json")
89+
90+
log.debug("%s %s", method, url)
91+
92+
try:
93+
with urllib.request.urlopen(request, data=data_buffer) as response:
94+
log.debug("Response status: %d", response.status)
95+
return json.load(response)
96+
except urllib.error.HTTPError as e:
97+
msg = e.read().decode()
98+
log.error("Error status: %d, message: %s", e.status, msg or "<empty>")
99+
raise
100+
101+
def _add_project_reference(self, variable_group: dict[str, Any]) -> dict[str, Any]:
102+
variable_group = variable_group.copy()
103+
if not variable_group.get("variableGroupProjectReferences"):
104+
variable_group["variableGroupProjectReferences"] = []
105+
106+
for ref in variable_group["variableGroupProjectReferences"]:
107+
if ref["projectReference"].get("name") == self._project_name:
108+
# already has the project reference
109+
return variable_group
110+
111+
variable_group["variableGroupProjectReferences"].append(
112+
{
113+
"name": variable_group["name"],
114+
"projectReference": {"name": self._project_name},
115+
}
116+
)
117+
return variable_group
118+
119+
120+
class VarFromEnv(NamedTuple):
121+
name: str
122+
is_secret: bool = True
123+
124+
def get_value(self) -> str:
125+
return os.environ.get(self.name, "")
126+
127+
128+
def get_required_env(name: str) -> str:
129+
value = os.getenv(name)
130+
if not value:
131+
raise ValueError(f"Environment variable missing or empty: {name}")
132+
return value
133+
134+
135+
def main() -> None:
136+
token = get_required_env("AZURE_DEVOPS_EXT_PAT")
137+
org_name = get_required_env("AZURE_ORGANIZATION")
138+
project_name = get_required_env("AZURE_PROJECT")
139+
vargroup_name = get_required_env("AZURE_VARIABLE_GROUP_NAME")
140+
141+
client = AzureAPI(token=token, org_name=org_name, project_name=project_name)
142+
143+
variables_to_set = [
144+
VarFromEnv("ROX_CENTRAL_ENDPOINT", is_secret=False),
145+
VarFromEnv("ROX_API_TOKEN"),
146+
VarFromEnv("GITOPS_AUTH_PASSWORD"),
147+
VarFromEnv("QUAY_IO_CREDS_USR", is_secret=False),
148+
VarFromEnv("QUAY_IO_CREDS_PSW"),
149+
VarFromEnv("COSIGN_SECRET_PASSWORD"),
150+
VarFromEnv("COSIGN_SECRET_KEY"),
151+
VarFromEnv("COSIGN_PUBLIC_KEY", is_secret=False),
152+
VarFromEnv("TRUSTIFICATION_BOMBASTIC_API_URL", is_secret=False),
153+
VarFromEnv("TRUSTIFICATION_OIDC_ISSUER_URL", is_secret=False),
154+
VarFromEnv("TRUSTIFICATION_OIDC_CLIENT_ID", is_secret=False),
155+
VarFromEnv("TRUSTIFICATION_OIDC_CLIENT_SECRET"),
156+
VarFromEnv("TRUSTIFICATION_SUPPORTED_CYCLONEDX_VERSION", is_secret=False),
157+
]
158+
159+
variables = {}
160+
161+
for var in variables_to_set:
162+
value = var.get_value()
163+
variables[var.name] = {"value": value, "isSecret": var.is_secret}
164+
if value and var.is_secret:
165+
log.info("Will set %s=<redacted>", var.name)
166+
else:
167+
log.info("Will set %s=%s", var.name, value)
168+
169+
log.info("Searching for '%s' variable group", vargroup_name)
170+
var_group = client.get_variable_group(vargroup_name)
171+
if not var_group:
172+
log.info("Creating a new variable group")
173+
var_group = client.add_variable_group(
174+
{"type": "Vsts", "name": vargroup_name, "variables": variables}
175+
)
176+
else:
177+
log.info("Updating existing variable group (id %d)", var_group["id"])
178+
var_group["variables"].update(variables)
179+
var_group = client.update_variable_group(var_group["id"], var_group)
180+
181+
print(json.dumps(var_group, indent=2))
182+
183+
184+
if __name__ == "__main__":
185+
main()

0 commit comments

Comments
 (0)