augint-github 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: augint-github
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: GitHub tools for Augmenting Integrations
|
|
5
|
+
License: AGPL-3.0-only
|
|
6
|
+
Author: Samuel Vange
|
|
7
|
+
Author-email: 7166607+svange@users.noreply.github.com
|
|
8
|
+
Requires-Python: >=3.12,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: GNU Affero General Public License v3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Requires-Dist: click (>=8.1.8,<9.0.0)
|
|
14
|
+
Requires-Dist: loguru (>=0.7.3,<0.8.0)
|
|
15
|
+
Requires-Dist: pygithub (>=2.5.0,<3.0.0)
|
|
16
|
+
Requires-Dist: python-dotenv (>=1.0.1,<2.0.0)
|
|
17
|
+
Requires-Dist: rich (>=13.9.4,<14.0.0)
|
|
18
|
+
Project-URL: Homepage, https://github.com/svange/augint-github
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# Augmenting Integrations GitHub Tools
|
|
22
|
+

|
|
23
|
+
|
|
24
|
+

|
|
25
|
+
[](https://www.gnu.org/licenses/agpl-3.0)[](https://github.com/astral-sh/ruff)
|
|
26
|
+
<a href="https://github.com/psf/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
|
|
27
|
+
[](https://conventionalcommits.org)
|
|
28
|
+
[](https://github.com/pre-commit/pre-commit)
|
|
29
|
+
[](https://github.com/features/actions "Go to GitHub Actions homepage")
|
|
30
|
+
[](https://github.com/semantic-release/semantic-release)
|
|
31
|
+
|
|
32
|
+
# Variables and Secrets
|
|
33
|
+
Push variables and secrets from .env to GH Actions.
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
Here is a draft for a `README.md` section that provides a general overview of the `LeadmoApiV1` class, its capabilities, and usage examples:
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Augmenting Integrations GitHub Tools
|
|
2
|
+

|
|
3
|
+
|
|
4
|
+

|
|
5
|
+
[](https://www.gnu.org/licenses/agpl-3.0)[](https://github.com/astral-sh/ruff)
|
|
6
|
+
<a href="https://github.com/psf/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
|
|
7
|
+
[](https://conventionalcommits.org)
|
|
8
|
+
[](https://github.com/pre-commit/pre-commit)
|
|
9
|
+
[](https://github.com/features/actions "Go to GitHub Actions homepage")
|
|
10
|
+
[](https://github.com/semantic-release/semantic-release)
|
|
11
|
+
|
|
12
|
+
# Variables and Secrets
|
|
13
|
+
Push variables and secrets from .env to GH Actions.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
Here is a draft for a `README.md` section that provides a general overview of the `LeadmoApiV1` class, its capabilities, and usage examples:
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "augint-github"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "GitHub tools for Augmenting Integrations"
|
|
5
|
+
authors = ["Samuel Vange <7166607+svange@users.noreply.github.com>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
license = "AGPL-3.0-only"
|
|
8
|
+
homepage = "https://github.com/svange/augint-github"
|
|
9
|
+
packages = [
|
|
10
|
+
{ include = "*", from = "src" },
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
[tool.poetry.scripts]
|
|
14
|
+
ai-gh-push = "gh_secrets_and_vars_async:cli"
|
|
15
|
+
|
|
16
|
+
[tool.poetry.plugins."commitizen.plugin"] # completely untested
|
|
17
|
+
cz_conventional_commits = "commitizen.cz.conventional_commits:ConventionalCommitsCz"
|
|
18
|
+
#cz_jira = "commitizen.cz.jira:JiraSmartCz"
|
|
19
|
+
cz_customize = "commitizen.cz.customize:CustomizeCommitsCz"
|
|
20
|
+
cargo = "commitizen.providers:CargoProvider"
|
|
21
|
+
commitizen = "commitizen.providers:CommitizenProvider"
|
|
22
|
+
composer = "commitizen.providers:ComposerProvider"
|
|
23
|
+
npm = "commitizen.providers:NpmProvider"
|
|
24
|
+
pep621 = "commitizen.providers:Pep621Provider"
|
|
25
|
+
poetry = "commitizen.providers:PoetryProvider"
|
|
26
|
+
scm = "commitizen.providers:ScmProvider"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
[tool.poetry.group.dev.dependencies]
|
|
30
|
+
pytest = "^8.3.4"
|
|
31
|
+
pytest-html = "^4.1.1"
|
|
32
|
+
pytest-cov = "^6.0.0"
|
|
33
|
+
|
|
34
|
+
[tool.semantic_release]
|
|
35
|
+
major_on_zero = false
|
|
36
|
+
commit_message = "chore(release): release {version}"
|
|
37
|
+
|
|
38
|
+
[tool.semantic_release.branches.main]
|
|
39
|
+
match = "main"
|
|
40
|
+
#commit_message = "chore(release): release {version}"
|
|
41
|
+
prerelease = false
|
|
42
|
+
|
|
43
|
+
[tool.semantic_release.branches.dev]
|
|
44
|
+
match = "dev"
|
|
45
|
+
#commit_message = "chore(pre-release): release {version}"
|
|
46
|
+
prerelease_token = "dev"
|
|
47
|
+
prerelease = true
|
|
48
|
+
|
|
49
|
+
version_toml = [
|
|
50
|
+
"pyproject.toml:tool.poetry.version",
|
|
51
|
+
]
|
|
52
|
+
version_variable = [
|
|
53
|
+
"__init__.py:__version__",
|
|
54
|
+
]
|
|
55
|
+
build_command = "pip install poetry && poetry build"
|
|
56
|
+
#logging_use_named_masks = true
|
|
57
|
+
#major_on_zero = true
|
|
58
|
+
tag_format = "v{version}"
|
|
59
|
+
#exclude_commit_patterns = [
|
|
60
|
+
# 'skip: ',
|
|
61
|
+
#]
|
|
62
|
+
|
|
63
|
+
[tool.semantic_release.publish]
|
|
64
|
+
dist_glob_patterns = ["dist/*"]
|
|
65
|
+
upload_to_vcs_release = true
|
|
66
|
+
type = "github"
|
|
67
|
+
|
|
68
|
+
[tool.semantic_release.remote.token]
|
|
69
|
+
env = "GITHUB_TOKEN"
|
|
70
|
+
fallback_env = "GH_TOKEN"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
[tool.commitizen]
|
|
75
|
+
name = "cz_conventional_commits"
|
|
76
|
+
tag_format = "v$version"
|
|
77
|
+
version_scheme = "SemVer"
|
|
78
|
+
version_provider = "poetry" #"scm"
|
|
79
|
+
update_changelog_on_bump = true
|
|
80
|
+
major_version_zero = true
|
|
81
|
+
#version = "3.9.1"
|
|
82
|
+
#version_files = [
|
|
83
|
+
# "pyproject.toml:version",
|
|
84
|
+
# "openbrain/__version__.py",
|
|
85
|
+
# ".pre-commit-config.yaml:rev:.+Openbrain",
|
|
86
|
+
#]
|
|
87
|
+
|
|
88
|
+
[tool.poetry.plugins."commitizen.scheme"]
|
|
89
|
+
pep440 = "commitizen.version_schemes:Pep440"
|
|
90
|
+
semver = "commitizen.version_schemes:SemVer"
|
|
91
|
+
|
|
92
|
+
[tool.poetry.dependencies]
|
|
93
|
+
python = "^3.12"
|
|
94
|
+
click = "^8.1.8"
|
|
95
|
+
python-dotenv = "^1.0.1"
|
|
96
|
+
rich = "^13.9.4"
|
|
97
|
+
loguru = "^0.7.3"
|
|
98
|
+
pygithub = "^2.5.0"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
[tool.coverage] # not tested
|
|
102
|
+
[tool.coverage.report]
|
|
103
|
+
show_missing = true
|
|
104
|
+
exclude_lines = [
|
|
105
|
+
# Have to re-enable the standard pragma
|
|
106
|
+
'pragma: no cover',
|
|
107
|
+
# Don't complain about missing debug-only code:
|
|
108
|
+
'def __repr__',
|
|
109
|
+
'if self\.debug',
|
|
110
|
+
# Don't complain if tests don't hit defensive assertion code:
|
|
111
|
+
'raise AssertionError',
|
|
112
|
+
'raise NotImplementedError',
|
|
113
|
+
# Don't complain if non-runnable code isn't run:
|
|
114
|
+
'if 0:',
|
|
115
|
+
'if __name__ == .__main__.:',
|
|
116
|
+
'if TYPE_CHECKING:',
|
|
117
|
+
]
|
|
118
|
+
omit = [
|
|
119
|
+
'env/*',
|
|
120
|
+
'venv/*',
|
|
121
|
+
'.venv/*',
|
|
122
|
+
'*/virtualenv/*',
|
|
123
|
+
'*/virtualenvs/*',
|
|
124
|
+
'*/tests/*',
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
[tool.pytest.ini_options]
|
|
128
|
+
addopts = '-m "not redundant and not skip_ci" --html=report.html --self-contained-html'
|
|
129
|
+
log_cli = true
|
|
130
|
+
log_cli_level = "INFO"
|
|
131
|
+
log_format = "%(asctime)s %(levelname)s %(message)s"
|
|
132
|
+
log_date_format = "%Y-%m-%d %H:%M:%S"
|
|
133
|
+
log_file = "pytest.log"
|
|
134
|
+
log_file_level = "INFO"
|
|
135
|
+
#environment_table_redact_list = ".*key.*"
|
|
136
|
+
#render_collapsed = "failed,error"
|
|
137
|
+
markers = [
|
|
138
|
+
"redudant: mark test as redundant",
|
|
139
|
+
"end_to_end: mark test as an end-to-end test, meant for running in CI/CD against a live environment",
|
|
140
|
+
"no_infra: mark test as not requiring infrastructure, intended for running in CI/CD against local web server (to the runner)",
|
|
141
|
+
"skip_ci: mark as not suitable for ci",
|
|
142
|
+
]
|
|
143
|
+
|
|
144
|
+
[tool.ruff]
|
|
145
|
+
line-length = 130
|
|
146
|
+
lint.ignore = [
|
|
147
|
+
"E501",
|
|
148
|
+
"D1",
|
|
149
|
+
"D415"
|
|
150
|
+
]
|
|
151
|
+
# Enable pycodestyle (`E`) and Pyflakes (`F`) codes by default.
|
|
152
|
+
lint.select = ["E", "F"]
|
|
153
|
+
|
|
154
|
+
[tool.lint.isort]
|
|
155
|
+
known-first-party = ["commitizen", "tests"]
|
|
156
|
+
|
|
157
|
+
[tool.lint.pydocstyle]
|
|
158
|
+
convention = "restructuredtext"
|
|
159
|
+
|
|
160
|
+
[tool.mypy]
|
|
161
|
+
files = "commitizen"
|
|
162
|
+
disallow_untyped_decorators = true
|
|
163
|
+
disallow_subclassing_any = true
|
|
164
|
+
warn_return_any = true
|
|
165
|
+
warn_redundant_casts = true
|
|
166
|
+
warn_unused_ignores = true
|
|
167
|
+
warn_unused_configs = true
|
|
168
|
+
|
|
169
|
+
[[tool.mypy.overrides]]
|
|
170
|
+
module = "py.*" # Legacy pytest dependencies
|
|
171
|
+
ignore_missing_imports = true
|
|
172
|
+
|
|
173
|
+
[tool.black]
|
|
174
|
+
line-length = 130
|
|
175
|
+
target-version = ['py312']
|
|
176
|
+
include = '\.pyi?$'
|
|
177
|
+
exclude = '''
|
|
178
|
+
/(
|
|
179
|
+
\.git
|
|
180
|
+
| \.hg
|
|
181
|
+
| \.mypy_cache
|
|
182
|
+
| \.tox
|
|
183
|
+
| \.venv
|
|
184
|
+
| _build
|
|
185
|
+
| buck-out
|
|
186
|
+
| build
|
|
187
|
+
)/
|
|
188
|
+
'''
|
|
189
|
+
|
|
190
|
+
[tool.flake8]
|
|
191
|
+
max-line-length = 130
|
|
192
|
+
extend-ignore = ["D203", "E203", "E251", "E266", "E302", "E305", "E401", "E402", "E501", "F401", "F403", "W503"]
|
|
193
|
+
exclude = [".git", "__pycache__", "dist"]
|
|
194
|
+
max-complexity = 10
|
|
195
|
+
|
|
196
|
+
[tool.isort]
|
|
197
|
+
atomic = true
|
|
198
|
+
profile = "black"
|
|
199
|
+
line_length = 130
|
|
200
|
+
skip_gitignore = true
|
|
201
|
+
|
|
202
|
+
# Allow autofix for all enabled rules (when `--fix`) is provided.
|
|
203
|
+
fixable = ["A", "B", "C", "D", "E", "F", "G", "I", "N", "Q", "S", "T", "W", "ANN", "ARG", "BLE", "COM", "DJ", "DTZ", "EM", "ERA", "EXE", "FBT", "ICN", "INP", "ISC", "NPY", "PD", "PGH", "PIE", "PL", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "SIM", "SLF", "TCH", "TID", "TRY", "UP", "YTT"]
|
|
204
|
+
unfixable = []
|
|
205
|
+
|
|
206
|
+
# Exclude a variety of commonly ignored directories.
|
|
207
|
+
exclude = [
|
|
208
|
+
".bzr",
|
|
209
|
+
".direnv",
|
|
210
|
+
".eggs",
|
|
211
|
+
".git",
|
|
212
|
+
".git-rewrite",
|
|
213
|
+
".hg",
|
|
214
|
+
".mypy_cache",
|
|
215
|
+
".nox",
|
|
216
|
+
".pants.d",
|
|
217
|
+
".pytype",
|
|
218
|
+
".ruff_cache",
|
|
219
|
+
".svn",
|
|
220
|
+
".tox",
|
|
221
|
+
".venv",
|
|
222
|
+
"__pypackages__",
|
|
223
|
+
"_build",
|
|
224
|
+
"buck-out",
|
|
225
|
+
"build",
|
|
226
|
+
"dist",
|
|
227
|
+
"node_modules",
|
|
228
|
+
"venv",
|
|
229
|
+
]
|
|
230
|
+
|
|
231
|
+
# Same as Black.
|
|
232
|
+
line-length = 130
|
|
233
|
+
|
|
234
|
+
# Allow unused variables when underscore-prefixed.
|
|
235
|
+
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
|
|
236
|
+
|
|
237
|
+
target-version = "py312"
|
|
File without changes
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
import github.GithubException
|
|
7
|
+
from dotenv import load_dotenv
|
|
8
|
+
from github import Github, Auth
|
|
9
|
+
from github.Repository import Repository
|
|
10
|
+
from github.GithubException import UnknownObjectException
|
|
11
|
+
from rich import print
|
|
12
|
+
from loguru import logger
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.command()
|
|
16
|
+
@click.option("--verbose", "-v", is_flag=True, help="Print all the output.")
|
|
17
|
+
@click.option("--dry-run", "-d", is_flag=True, help="Run through the process, but make no changes to GitHub.")
|
|
18
|
+
@click.argument("filename", type=click.Path(exists=True, readable=True), default=".env")
|
|
19
|
+
def cli(verbose: bool, dry_run: bool, filename: click.Path):
|
|
20
|
+
"""
|
|
21
|
+
Update GitHub repository secrets and environment variables from a .env file. Requires the values for `GH_REPO`, `GH_ACCOUNT`, and `GH_TOKEN` in your .env file. This script finds sensitive key/value pairs in FILENAME (by looking for substrins like KEY, TOKEN, SECRET, etc. in the key name) and updates the GitHub repository secrets with them. It then updates the GitHub repository environment variables with the remaining key/value pairs.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
if dry_run:
|
|
25
|
+
logger.info("Dry run mode enabled. No changes will be made to GitHub.")
|
|
26
|
+
results = asyncio.run(perform_update(filename, verbose, dry_run))
|
|
27
|
+
if verbose:
|
|
28
|
+
print(results)
|
|
29
|
+
total_secrets = len(results["SECRETS"])
|
|
30
|
+
total_vars = len(results["VARIABLES"])
|
|
31
|
+
print(f"Updated {total_secrets} secrets and {total_vars} variables.")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
async def perform_update(filename: click.Path, verbose: bool = False, dry_run: bool = False):
|
|
35
|
+
"""
|
|
36
|
+
Performs the update of the GitHub repository secrets and environment variables.
|
|
37
|
+
:param filename: file to use for secrets, variables, and github repo
|
|
38
|
+
:return:
|
|
39
|
+
"""
|
|
40
|
+
if not filename:
|
|
41
|
+
raise ValueError("No filename specified. Exiting to avoid an accident.")
|
|
42
|
+
|
|
43
|
+
load_dotenv(str(filename), override=True)
|
|
44
|
+
github_repo = os.environ.get("GH_REPO", None)
|
|
45
|
+
github_account = os.environ.get("GH_ACCOUNT", None)
|
|
46
|
+
|
|
47
|
+
file_path = Path(filename.__str__())
|
|
48
|
+
# Read the .env file and convert it to a JSON object
|
|
49
|
+
secrets = {}
|
|
50
|
+
not_secrets = {}
|
|
51
|
+
SECRETS_INDICATORS = ["secret", "key", "token", "bearer", "password", "pass", "pwd", "pword", "hash"]
|
|
52
|
+
logger.debug(f"Reading file {file_path}")
|
|
53
|
+
|
|
54
|
+
with file_path.open("r") as file:
|
|
55
|
+
for line in file:
|
|
56
|
+
line = line.strip()
|
|
57
|
+
if not line or line.startswith("#") or line.startswith(";") or "=" not in line:
|
|
58
|
+
continue
|
|
59
|
+
key, value = line.strip().split("=", 1)
|
|
60
|
+
|
|
61
|
+
match key:
|
|
62
|
+
case key if key.startswith("AWS_PROFILE"):
|
|
63
|
+
continue
|
|
64
|
+
case key if any(indicator in key.casefold() for indicator in SECRETS_INDICATORS):
|
|
65
|
+
secrets[key] = value
|
|
66
|
+
case _:
|
|
67
|
+
not_secrets[key] = value
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
repo = get_github_repo(github_account, github_repo)
|
|
71
|
+
except github.GithubException as e:
|
|
72
|
+
logger.critical(f"Repo {github_repo} not found. Ensure GH_REPO and GH_ACCOUNT are in your env file.")
|
|
73
|
+
exit(1)
|
|
74
|
+
|
|
75
|
+
secret_update_result = await create_or_update_github_secrets(repo=repo, env_data=secrets, dry_run=dry_run)
|
|
76
|
+
not_secret_update_result = await create_or_update_github_variables(repo=repo, env_data=not_secrets, dry_run=dry_run)
|
|
77
|
+
|
|
78
|
+
results = {"SECRETS": secret_update_result, "VARIABLES": not_secret_update_result}
|
|
79
|
+
|
|
80
|
+
return results
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def get_github_repo(github_account, github_repo_name) -> Repository:
|
|
84
|
+
"""
|
|
85
|
+
Get the GitHub repository object.
|
|
86
|
+
:param github_account: Name of your GitHub account (i.e. your username), or the name of the organization.
|
|
87
|
+
:param github_repo_name: Name of the repository.
|
|
88
|
+
:return: a GitHub repository object
|
|
89
|
+
"""
|
|
90
|
+
auth = Auth.Token(os.environ.get("GH_TOKEN", None))
|
|
91
|
+
g = Github(auth=auth)
|
|
92
|
+
try:
|
|
93
|
+
repo = g.get_user(github_account).get_repo(github_repo_name)
|
|
94
|
+
except UnknownObjectException as e:
|
|
95
|
+
logger.critical(e)
|
|
96
|
+
repo = g.get_organization(github_account).get_repo(github_repo_name)
|
|
97
|
+
logger.critical("You must add GH_USER to your env file.")
|
|
98
|
+
|
|
99
|
+
return repo
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
async def create_or_update_github_secrets(repo, env_data, dry_run: bool = False):
|
|
103
|
+
"""
|
|
104
|
+
Create or update GitHub repository secrets.
|
|
105
|
+
:param dry_run: run through the process, but make no changes to GitHub
|
|
106
|
+
:param repo: the repository on which to operate
|
|
107
|
+
:param env_data: the secrets and their values in a dictionary
|
|
108
|
+
:return: list of results
|
|
109
|
+
"""
|
|
110
|
+
secrets = await asyncio.to_thread(repo.get_secrets)
|
|
111
|
+
secret_names = [secret.name for secret in secrets]
|
|
112
|
+
dry_run_prefix = "[DRY RUN] " if dry_run else ""
|
|
113
|
+
|
|
114
|
+
tasks = []
|
|
115
|
+
for env_var_name, env_var_value in env_data.items():
|
|
116
|
+
# Create or update secrets
|
|
117
|
+
if env_var_name in secret_names:
|
|
118
|
+
logger.info(f"{dry_run_prefix}Updating secret {env_var_name}...")
|
|
119
|
+
tasks.append(asyncio.to_thread(repo.create_secret, env_var_name, env_var_value))
|
|
120
|
+
else:
|
|
121
|
+
logger.info(f"{dry_run_prefix}Creating secret {env_var_name}...")
|
|
122
|
+
tasks.append(asyncio.to_thread(repo.create_secret, env_var_name, env_var_value))
|
|
123
|
+
|
|
124
|
+
for secret_name in secret_names:
|
|
125
|
+
if secret_name not in env_data.keys():
|
|
126
|
+
logger.info(f"{dry_run_prefix}Deleting secret {secret_name}...")
|
|
127
|
+
tasks.append(asyncio.to_thread(repo.delete_secret, secret_name))
|
|
128
|
+
|
|
129
|
+
if dry_run:
|
|
130
|
+
results = [secret for secret in secrets]
|
|
131
|
+
else:
|
|
132
|
+
results = await asyncio.gather(*tasks)
|
|
133
|
+
return results
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
async def create_or_update_github_variables(repo, env_data, dry_run: bool = False):
|
|
137
|
+
"""
|
|
138
|
+
Create or update GitHub repository environment variables.
|
|
139
|
+
:param repo: the repository on which to operate
|
|
140
|
+
:param dry_run: run through the process, but make no changes to GitHub
|
|
141
|
+
:param env_data: the secrets and their values in a dictionary
|
|
142
|
+
:return: list of results
|
|
143
|
+
"""
|
|
144
|
+
vars = await asyncio.to_thread(repo.get_variables)
|
|
145
|
+
var_names = [var.name for var in vars]
|
|
146
|
+
dry_run_prefix = "[DRY RUN] " if dry_run else ""
|
|
147
|
+
|
|
148
|
+
tasks = []
|
|
149
|
+
for env_var_name, env_var_value in env_data.items():
|
|
150
|
+
# Create or update secrets
|
|
151
|
+
if env_var_name in var_names:
|
|
152
|
+
logger.info(f"{dry_run_prefix}Updating variable {env_var_name}...")
|
|
153
|
+
|
|
154
|
+
def delete_then_create_variable(repo, env_var_name, env_var_value):
|
|
155
|
+
repo.delete_variable(env_var_name)
|
|
156
|
+
return repo.create_variable(env_var_name, env_var_value)
|
|
157
|
+
|
|
158
|
+
# tasks.append(asyncio.to_thread(repo.delete_variable, env_var_name))
|
|
159
|
+
tasks.append(asyncio.to_thread(delete_then_create_variable, repo, env_var_name, env_var_value))
|
|
160
|
+
else:
|
|
161
|
+
logger.info(f"{dry_run_prefix}Creating variable {env_var_name}...")
|
|
162
|
+
tasks.append(asyncio.to_thread(repo.create_variable, env_var_name, env_var_value))
|
|
163
|
+
|
|
164
|
+
for var_name in var_names:
|
|
165
|
+
if var_name not in env_data.keys():
|
|
166
|
+
logger.info(f"{dry_run_prefix}Deleting variable {var_name}...")
|
|
167
|
+
tasks.append(asyncio.to_thread(repo.delete_variable, var_name))
|
|
168
|
+
|
|
169
|
+
if dry_run:
|
|
170
|
+
results = [var for var in vars]
|
|
171
|
+
else:
|
|
172
|
+
results = await asyncio.gather(*tasks)
|
|
173
|
+
|
|
174
|
+
return results
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
if __name__ == "__main__":
|
|
178
|
+
cli()
|