Merge lpbuildbot:use-wheels-to-pack into lpbuildbot:main
- Git
- lp:lpbuildbot
- use-wheels-to-pack
- Merge into main
Proposed by
Vaishnavi Asawale
| Status: | Work in progress |
|---|---|
| Proposed branch: | lpbuildbot:use-wheels-to-pack |
| Merge into: | lpbuildbot:main |
| Diff against target: |
851 lines (+772/-0) (has conflicts) 12 files modified
.gitignore (+12/-0) CONTRIBUTING.md (+34/-0) LICENSE (+202/-0) README.md (+36/-0) charmcraft.yaml (+95/-0) pyproject.toml (+46/-0) requirements.txt (+48/-0) src/charm.py (+137/-0) templates/buildbot@.service.j2 (+18/-0) tests/integration/test_charm.py (+34/-0) tests/unit/test_charm.py (+24/-0) tox.ini (+86/-0) Conflict in .gitignore |
| Related bugs: |
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| Canonical Launchpad Engineering | Pending | ||
|
Review via email:
|
|||
Commit message
WIP: Use pre-downloaded wheels while packing the charm
Description of the change
To post a comment you must log in.
Unmerged commits
- 42a163e... by Vaishnavi Asawale
-
Can pack charm from wheels
- 1a74ee0... by Vaishnavi Asawale
-
Run ruff format and ruff check
- 4a31e14... by Vaishnavi Asawale
-
Final working charm
- dfc5e83... by Vaishnavi Asawale
-
Working buildbot manager
- 0e69c8d... by Tushar Gupta
-
init
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
| 1 | diff --git a/.gitignore b/.gitignore |
| 2 | index ae56c78..8b9db6a 100644 |
| 3 | --- a/.gitignore |
| 4 | +++ b/.gitignore |
| 5 | @@ -1,3 +1,4 @@ |
| 6 | +<<<<<<< .gitignore |
| 7 | tags |
| 8 | Session.vim |
| 9 | TAGS |
| 10 | @@ -6,3 +7,14 @@ lp-db-devel-* |
| 11 | twistd.log* |
| 12 | twistd.pid |
| 13 | work |
| 14 | +======= |
| 15 | +venv/ |
| 16 | +build/ |
| 17 | +*.charm |
| 18 | +.tox/ |
| 19 | +.coverage |
| 20 | +__pycache__/ |
| 21 | +*.py[cod] |
| 22 | +.idea |
| 23 | +.vscode/ |
| 24 | +>>>>>>> .gitignore |
| 25 | diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md |
| 26 | new file mode 100644 |
| 27 | index 0000000..20e88bc |
| 28 | --- /dev/null |
| 29 | +++ b/CONTRIBUTING.md |
| 30 | @@ -0,0 +1,34 @@ |
| 31 | +# Contributing |
| 32 | + |
| 33 | +To make contributions to this charm, you'll need a working [development setup](https://juju.is/docs/sdk/dev-setup). |
| 34 | + |
| 35 | +You can create an environment for development with `tox`: |
| 36 | + |
| 37 | +```shell |
| 38 | +tox devenv -e integration |
| 39 | +source venv/bin/activate |
| 40 | +``` |
| 41 | + |
| 42 | +## Testing |
| 43 | + |
| 44 | +This project uses `tox` for managing test environments. There are some pre-configured environments |
| 45 | +that can be used for linting and formatting code when you're preparing contributions to the charm: |
| 46 | + |
| 47 | +```shell |
| 48 | +tox run -e format # update your code according to linting rules |
| 49 | +tox run -e lint # code style |
| 50 | +tox run -e static # static type checking |
| 51 | +tox run -e unit # unit tests |
| 52 | +tox run -e integration # integration tests |
| 53 | +tox # runs 'format', 'lint', 'static', and 'unit' environments |
| 54 | +``` |
| 55 | + |
| 56 | +## Build the charm |
| 57 | + |
| 58 | +Build the charm in this git repository using: |
| 59 | + |
| 60 | +```shell |
| 61 | +charmcraft pack |
| 62 | +``` |
| 63 | + |
| 64 | +<!-- You may want to include any contribution/style guidelines in this document> |
| 65 | diff --git a/LICENSE b/LICENSE |
| 66 | new file mode 100644 |
| 67 | index 0000000..bc0f920 |
| 68 | --- /dev/null |
| 69 | +++ b/LICENSE |
| 70 | @@ -0,0 +1,202 @@ |
| 71 | + |
| 72 | + Apache License |
| 73 | + Version 2.0, January 2004 |
| 74 | + http://www.apache.org/licenses/ |
| 75 | + |
| 76 | + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
| 77 | + |
| 78 | + 1. Definitions. |
| 79 | + |
| 80 | + "License" shall mean the terms and conditions for use, reproduction, |
| 81 | + and distribution as defined by Sections 1 through 9 of this document. |
| 82 | + |
| 83 | + "Licensor" shall mean the copyright owner or entity authorized by |
| 84 | + the copyright owner that is granting the License. |
| 85 | + |
| 86 | + "Legal Entity" shall mean the union of the acting entity and all |
| 87 | + other entities that control, are controlled by, or are under common |
| 88 | + control with that entity. For the purposes of this definition, |
| 89 | + "control" means (i) the power, direct or indirect, to cause the |
| 90 | + direction or management of such entity, whether by contract or |
| 91 | + otherwise, or (ii) ownership of fifty percent (50%) or more of the |
| 92 | + outstanding shares, or (iii) beneficial ownership of such entity. |
| 93 | + |
| 94 | + "You" (or "Your") shall mean an individual or Legal Entity |
| 95 | + exercising permissions granted by this License. |
| 96 | + |
| 97 | + "Source" form shall mean the preferred form for making modifications, |
| 98 | + including but not limited to software source code, documentation |
| 99 | + source, and configuration files. |
| 100 | + |
| 101 | + "Object" form shall mean any form resulting from mechanical |
| 102 | + transformation or translation of a Source form, including but |
| 103 | + not limited to compiled object code, generated documentation, |
| 104 | + and conversions to other media types. |
| 105 | + |
| 106 | + "Work" shall mean the work of authorship, whether in Source or |
| 107 | + Object form, made available under the License, as indicated by a |
| 108 | + copyright notice that is included in or attached to the work |
| 109 | + (an example is provided in the Appendix below). |
| 110 | + |
| 111 | + "Derivative Works" shall mean any work, whether in Source or Object |
| 112 | + form, that is based on (or derived from) the Work and for which the |
| 113 | + editorial revisions, annotations, elaborations, or other modifications |
| 114 | + represent, as a whole, an original work of authorship. For the purposes |
| 115 | + of this License, Derivative Works shall not include works that remain |
| 116 | + separable from, or merely link (or bind by name) to the interfaces of, |
| 117 | + the Work and Derivative Works thereof. |
| 118 | + |
| 119 | + "Contribution" shall mean any work of authorship, including |
| 120 | + the original version of the Work and any modifications or additions |
| 121 | + to that Work or Derivative Works thereof, that is intentionally |
| 122 | + submitted to Licensor for inclusion in the Work by the copyright owner |
| 123 | + or by an individual or Legal Entity authorized to submit on behalf of |
| 124 | + the copyright owner. For the purposes of this definition, "submitted" |
| 125 | + means any form of electronic, verbal, or written communication sent |
| 126 | + to the Licensor or its representatives, including but not limited to |
| 127 | + communication on electronic mailing lists, source code control systems, |
| 128 | + and issue tracking systems that are managed by, or on behalf of, the |
| 129 | + Licensor for the purpose of discussing and improving the Work, but |
| 130 | + excluding communication that is conspicuously marked or otherwise |
| 131 | + designated in writing by the copyright owner as "Not a Contribution." |
| 132 | + |
| 133 | + "Contributor" shall mean Licensor and any individual or Legal Entity |
| 134 | + on behalf of whom a Contribution has been received by Licensor and |
| 135 | + subsequently incorporated within the Work. |
| 136 | + |
| 137 | + 2. Grant of Copyright License. Subject to the terms and conditions of |
| 138 | + this License, each Contributor hereby grants to You a perpetual, |
| 139 | + worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
| 140 | + copyright license to reproduce, prepare Derivative Works of, |
| 141 | + publicly display, publicly perform, sublicense, and distribute the |
| 142 | + Work and such Derivative Works in Source or Object form. |
| 143 | + |
| 144 | + 3. Grant of Patent License. Subject to the terms and conditions of |
| 145 | + this License, each Contributor hereby grants to You a perpetual, |
| 146 | + worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
| 147 | + (except as stated in this section) patent license to make, have made, |
| 148 | + use, offer to sell, sell, import, and otherwise transfer the Work, |
| 149 | + where such license applies only to those patent claims licensable |
| 150 | + by such Contributor that are necessarily infringed by their |
| 151 | + Contribution(s) alone or by combination of their Contribution(s) |
| 152 | + with the Work to which such Contribution(s) was submitted. If You |
| 153 | + institute patent litigation against any entity (including a |
| 154 | + cross-claim or counterclaim in a lawsuit) alleging that the Work |
| 155 | + or a Contribution incorporated within the Work constitutes direct |
| 156 | + or contributory patent infringement, then any patent licenses |
| 157 | + granted to You under this License for that Work shall terminate |
| 158 | + as of the date such litigation is filed. |
| 159 | + |
| 160 | + 4. Redistribution. You may reproduce and distribute copies of the |
| 161 | + Work or Derivative Works thereof in any medium, with or without |
| 162 | + modifications, and in Source or Object form, provided that You |
| 163 | + meet the following conditions: |
| 164 | + |
| 165 | + (a) You must give any other recipients of the Work or |
| 166 | + Derivative Works a copy of this License; and |
| 167 | + |
| 168 | + (b) You must cause any modified files to carry prominent notices |
| 169 | + stating that You changed the files; and |
| 170 | + |
| 171 | + (c) You must retain, in the Source form of any Derivative Works |
| 172 | + that You distribute, all copyright, patent, trademark, and |
| 173 | + attribution notices from the Source form of the Work, |
| 174 | + excluding those notices that do not pertain to any part of |
| 175 | + the Derivative Works; and |
| 176 | + |
| 177 | + (d) If the Work includes a "NOTICE" text file as part of its |
| 178 | + distribution, then any Derivative Works that You distribute must |
| 179 | + include a readable copy of the attribution notices contained |
| 180 | + within such NOTICE file, excluding those notices that do not |
| 181 | + pertain to any part of the Derivative Works, in at least one |
| 182 | + of the following places: within a NOTICE text file distributed |
| 183 | + as part of the Derivative Works; within the Source form or |
| 184 | + documentation, if provided along with the Derivative Works; or, |
| 185 | + within a display generated by the Derivative Works, if and |
| 186 | + wherever such third-party notices normally appear. The contents |
| 187 | + of the NOTICE file are for informational purposes only and |
| 188 | + do not modify the License. You may add Your own attribution |
| 189 | + notices within Derivative Works that You distribute, alongside |
| 190 | + or as an addendum to the NOTICE text from the Work, provided |
| 191 | + that such additional attribution notices cannot be construed |
| 192 | + as modifying the License. |
| 193 | + |
| 194 | + You may add Your own copyright statement to Your modifications and |
| 195 | + may provide additional or different license terms and conditions |
| 196 | + for use, reproduction, or distribution of Your modifications, or |
| 197 | + for any such Derivative Works as a whole, provided Your use, |
| 198 | + reproduction, and distribution of the Work otherwise complies with |
| 199 | + the conditions stated in this License. |
| 200 | + |
| 201 | + 5. Submission of Contributions. Unless You explicitly state otherwise, |
| 202 | + any Contribution intentionally submitted for inclusion in the Work |
| 203 | + by You to the Licensor shall be under the terms and conditions of |
| 204 | + this License, without any additional terms or conditions. |
| 205 | + Notwithstanding the above, nothing herein shall supersede or modify |
| 206 | + the terms of any separate license agreement you may have executed |
| 207 | + with Licensor regarding such Contributions. |
| 208 | + |
| 209 | + 6. Trademarks. This License does not grant permission to use the trade |
| 210 | + names, trademarks, service marks, or product names of the Licensor, |
| 211 | + except as required for reasonable and customary use in describing the |
| 212 | + origin of the Work and reproducing the content of the NOTICE file. |
| 213 | + |
| 214 | + 7. Disclaimer of Warranty. Unless required by applicable law or |
| 215 | + agreed to in writing, Licensor provides the Work (and each |
| 216 | + Contributor provides its Contributions) on an "AS IS" BASIS, |
| 217 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
| 218 | + implied, including, without limitation, any warranties or conditions |
| 219 | + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
| 220 | + PARTICULAR PURPOSE. You are solely responsible for determining the |
| 221 | + appropriateness of using or redistributing the Work and assume any |
| 222 | + risks associated with Your exercise of permissions under this License. |
| 223 | + |
| 224 | + 8. Limitation of Liability. In no event and under no legal theory, |
| 225 | + whether in tort (including negligence), contract, or otherwise, |
| 226 | + unless required by applicable law (such as deliberate and grossly |
| 227 | + negligent acts) or agreed to in writing, shall any Contributor be |
| 228 | + liable to You for damages, including any direct, indirect, special, |
| 229 | + incidental, or consequential damages of any character arising as a |
| 230 | + result of this License or out of the use or inability to use the |
| 231 | + Work (including but not limited to damages for loss of goodwill, |
| 232 | + work stoppage, computer failure or malfunction, or any and all |
| 233 | + other commercial damages or losses), even if such Contributor |
| 234 | + has been advised of the possibility of such damages. |
| 235 | + |
| 236 | + 9. Accepting Warranty or Additional Liability. While redistributing |
| 237 | + the Work or Derivative Works thereof, You may choose to offer, |
| 238 | + and charge a fee for, acceptance of support, warranty, indemnity, |
| 239 | + or other liability obligations and/or rights consistent with this |
| 240 | + License. However, in accepting such obligations, You may act only |
| 241 | + on Your own behalf and on Your sole responsibility, not on behalf |
| 242 | + of any other Contributor, and only if You agree to indemnify, |
| 243 | + defend, and hold each Contributor harmless for any liability |
| 244 | + incurred by, or claims asserted against, such Contributor by reason |
| 245 | + of your accepting any such warranty or additional liability. |
| 246 | + |
| 247 | + END OF TERMS AND CONDITIONS |
| 248 | + |
| 249 | + APPENDIX: How to apply the Apache License to your work. |
| 250 | + |
| 251 | + To apply the Apache License to your work, attach the following |
| 252 | + boilerplate notice, with the fields enclosed by brackets "[]" |
| 253 | + replaced with your own identifying information. (Don't include |
| 254 | + the brackets!) The text should be enclosed in the appropriate |
| 255 | + comment syntax for the file format. We also recommend that a |
| 256 | + file or class name and description of purpose be included on the |
| 257 | + same "printed page" as the copyright notice for easier |
| 258 | + identification within third-party archives. |
| 259 | + |
| 260 | + Copyright 2025 Tushar Gupta |
| 261 | + |
| 262 | + Licensed under the Apache License, Version 2.0 (the "License"); |
| 263 | + you may not use this file except in compliance with the License. |
| 264 | + You may obtain a copy of the License at |
| 265 | + |
| 266 | + http://www.apache.org/licenses/LICENSE-2.0 |
| 267 | + |
| 268 | + Unless required by applicable law or agreed to in writing, software |
| 269 | + distributed under the License is distributed on an "AS IS" BASIS, |
| 270 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 271 | + See the License for the specific language governing permissions and |
| 272 | + limitations under the License. |
| 273 | diff --git a/README.md b/README.md |
| 274 | new file mode 100644 |
| 275 | index 0000000..c53bbc8 |
| 276 | --- /dev/null |
| 277 | +++ b/README.md |
| 278 | @@ -0,0 +1,36 @@ |
| 279 | +<!-- |
| 280 | +Avoid using this README file for information that is maintained or published elsewhere, e.g.: |
| 281 | + |
| 282 | +* metadata.yaml > published on Charmhub |
| 283 | +* documentation > published on (or linked to from) Charmhub |
| 284 | +* detailed contribution guide > documentation or CONTRIBUTING.md |
| 285 | + |
| 286 | +Use links instead. |
| 287 | +--> |
| 288 | + |
| 289 | +# buildbot-manager |
| 290 | + |
| 291 | +Charmhub package name: operator-template |
| 292 | +More information: https://charmhub.io/buildbot-manager |
| 293 | + |
| 294 | +Describe your charm in one or two sentences. |
| 295 | + |
| 296 | +## Other resources |
| 297 | + |
| 298 | +<!-- If your charm is documented somewhere else other than Charmhub, provide a link separately. --> |
| 299 | + |
| 300 | +- [Read more](https://example.com) |
| 301 | + |
| 302 | +- [Contributing](CONTRIBUTING.md) <!-- or link to other contribution documentation --> |
| 303 | + |
| 304 | +- See the [Juju SDK documentation](https://juju.is/docs/sdk) for more information about developing and improving charms. |
| 305 | + |
| 306 | +# Setup Instructions |
| 307 | + |
| 308 | +To set up the development environment: |
| 309 | + |
| 310 | +```bash |
| 311 | +python3 -m venv .venv |
| 312 | +source .venv/bin/activate |
| 313 | +pip install -r requirements.txt |
| 314 | +``` |
| 315 | \ No newline at end of file |
| 316 | diff --git a/charmcraft.yaml b/charmcraft.yaml |
| 317 | new file mode 100644 |
| 318 | index 0000000..72c32d0 |
| 319 | --- /dev/null |
| 320 | +++ b/charmcraft.yaml |
| 321 | @@ -0,0 +1,95 @@ |
| 322 | +# This file configures Charmcraft. |
| 323 | +# See https://juju.is/docs/sdk/charmcraft-config for guidance. |
| 324 | + |
| 325 | + |
| 326 | + |
| 327 | +# (Required) |
| 328 | +name: buildbot-manager |
| 329 | + |
| 330 | + |
| 331 | +# (Required) |
| 332 | +type: charm |
| 333 | + |
| 334 | + |
| 335 | +# (Recommended) |
| 336 | +title: Buildbot Manager |
| 337 | + |
| 338 | + |
| 339 | +# (Required) |
| 340 | +summary: A very short one-line summary of the charm. |
| 341 | + |
| 342 | + |
| 343 | +# (Required) |
| 344 | +description: | |
| 345 | + A single sentence that says what the charm is, concisely and memorably. |
| 346 | + |
| 347 | + A paragraph of one to three short sentences, that describe what the charm does. |
| 348 | + |
| 349 | + A third paragraph that explains what need the charm meets. |
| 350 | + |
| 351 | + Finally, a paragraph that describes whom the charm is useful for. |
| 352 | + |
| 353 | + |
| 354 | +# (Required for 'charm' type) |
| 355 | +# bases: |
| 356 | +# - build-on: |
| 357 | +# - name: ubuntu |
| 358 | +# channel: "22.04" |
| 359 | +# run-on: |
| 360 | +# - name: ubuntu |
| 361 | +# channel: "22.04" |
| 362 | + |
| 363 | +base: ubuntu@24.04 |
| 364 | + |
| 365 | +platforms: |
| 366 | + amd64: |
| 367 | + |
| 368 | +parts: |
| 369 | + charm-wheels: |
| 370 | + plugin: dump |
| 371 | + source: https://git.launchpad.net/~vaishnavi-asawale/+git/wheels-buildbot-manager-charm |
| 372 | + source-commit: 1d1b984acbe3ee1fd523f25990887637e32fbeb6 |
| 373 | + source-type: git |
| 374 | + organize: |
| 375 | + "*": wheels/ |
| 376 | + prime: |
| 377 | + - "-wheels" |
| 378 | + |
| 379 | + charm: |
| 380 | + source: . |
| 381 | + after: |
| 382 | + - charm-wheels |
| 383 | + build-packages: |
| 384 | + - build-essential |
| 385 | + - python3-dev |
| 386 | + - libssl-dev |
| 387 | + - libffi-dev |
| 388 | + - python3.12-venv |
| 389 | + stage: |
| 390 | + - wheels/* |
| 391 | + build-environment: |
| 392 | + - PIP_NO_INDEX: "true" |
| 393 | + - PIP_FIND_LINKS: "$CRAFT_STAGE/wheels" |
| 394 | + override-build: | |
| 395 | + INSTALL_PATH="/srv/buildbot/managers" |
| 396 | + VENV_NAME="sandbox" |
| 397 | + VENV_PATH="$INSTALL_PATH/$VENV_NAME" |
| 398 | + mkdir -p "$INSTALL_PATH" |
| 399 | + python3 -m venv --clear "$VENV_PATH" |
| 400 | + "$VENV_PATH/bin/pip" install --no-index --find-links="$CRAFT_STAGE/wheels" --requirement requirements.txt |
| 401 | + |
| 402 | +# (Optional) Configuration options for the charm |
| 403 | +# This config section defines charm config options, and populates the Configure |
| 404 | +# tab on Charmhub. |
| 405 | +# More information on this section at https://juju.is/docs/sdk/charmcraft-yaml#heading--config |
| 406 | +# General configuration documentation: https://juju.is/docs/sdk/config |
| 407 | +config: |
| 408 | + options: |
| 409 | + # An example config option to customise the log level of the workload |
| 410 | + log-level: |
| 411 | + description: | |
| 412 | + Configures the log level of gunicorn. |
| 413 | + |
| 414 | + Acceptable values are: "info", "debug", "warning", "error" and "critical" |
| 415 | + default: "info" |
| 416 | + type: string |
| 417 | diff --git a/pyproject.toml b/pyproject.toml |
| 418 | new file mode 100644 |
| 419 | index 0000000..e10531c |
| 420 | --- /dev/null |
| 421 | +++ b/pyproject.toml |
| 422 | @@ -0,0 +1,46 @@ |
| 423 | +# Testing tools configuration |
| 424 | +[tool.coverage.run] |
| 425 | +branch = true |
| 426 | + |
| 427 | +[tool.coverage.report] |
| 428 | +show_missing = true |
| 429 | + |
| 430 | +[tool.pytest.ini_options] |
| 431 | +minversion = "6.0" |
| 432 | +log_cli_level = "INFO" |
| 433 | + |
| 434 | +# Formatting tools configuration |
| 435 | +[tool.black] |
| 436 | +line-length = 99 |
| 437 | +target-version = ["py38"] |
| 438 | + |
| 439 | +# Linting tools configuration |
| 440 | +[tool.ruff] |
| 441 | +line-length = 99 |
| 442 | +select = ["E", "W", "F", "C", "N", "D", "I001"] |
| 443 | +extend-ignore = [ |
| 444 | + "D203", |
| 445 | + "D204", |
| 446 | + "D213", |
| 447 | + "D215", |
| 448 | + "D400", |
| 449 | + "D404", |
| 450 | + "D406", |
| 451 | + "D407", |
| 452 | + "D408", |
| 453 | + "D409", |
| 454 | + "D413", |
| 455 | +] |
| 456 | +ignore = ["E501", "D107"] |
| 457 | +extend-exclude = ["__pycache__", "*.egg_info"] |
| 458 | +per-file-ignores = {"tests/*" = ["D100","D101","D102","D103","D104"]} |
| 459 | + |
| 460 | +[tool.ruff.mccabe] |
| 461 | +max-complexity = 10 |
| 462 | + |
| 463 | +[tool.codespell] |
| 464 | +skip = "build,lib,venv,icon.svg,.tox,.git,.mypy_cache,.ruff_cache,.coverage" |
| 465 | + |
| 466 | +[tool.pyright] |
| 467 | +include = ["src/**.py"] |
| 468 | + |
| 469 | diff --git a/requirements.txt b/requirements.txt |
| 470 | new file mode 100644 |
| 471 | index 0000000..e81e315 |
| 472 | --- /dev/null |
| 473 | +++ b/requirements.txt |
| 474 | @@ -0,0 +1,48 @@ |
| 475 | +ops ~= 2.5 |
| 476 | +charmhelpers ~= 1.2 |
| 477 | +alembic==1.16.5 |
| 478 | +attrs==25.3.0 |
| 479 | +autobahn==24.4.2 |
| 480 | +Automat==25.4.16 |
| 481 | +buildbot==4.3.0 |
| 482 | +buildbot-console-view==4.3.0 |
| 483 | +buildbot-grid-view==4.3.0 |
| 484 | +buildbot-waterfall-view==4.3.0 |
| 485 | +buildbot-worker==4.3.0 |
| 486 | +buildbot-www==4.3.0 |
| 487 | +certifi==2025.8.3 |
| 488 | +cffi==1.17.1 |
| 489 | +charset-normalizer==3.4.3 |
| 490 | +constantly==23.10.4 |
| 491 | +croniter==6.0.0 |
| 492 | +cryptography==45.0.7 |
| 493 | +greenlet==3.2.4 |
| 494 | +hyperlink==21.0.0 |
| 495 | +idna==3.10 |
| 496 | +incremental==24.7.2 |
| 497 | +Jinja2==3.1.6 |
| 498 | +Mako==1.3.10 |
| 499 | +MarkupSafe==3.0.2 |
| 500 | +msgpack==1.1.1 |
| 501 | +multipart==1.3.0 |
| 502 | +packaging==25.0 |
| 503 | +pyasn1==0.6.1 |
| 504 | +pyasn1_modules==0.4.2 |
| 505 | +pycparser==2.22 |
| 506 | +PyJWT==2.10.1 |
| 507 | +pyOpenSSL==25.1.0 |
| 508 | +python-dateutil==2.9.0.post0 |
| 509 | +pytz==2025.2 |
| 510 | +PyYAML==6.0.2 |
| 511 | +requests==2.32.5 |
| 512 | +service-identity==24.2.0 |
| 513 | +setuptools==80.9.0 |
| 514 | +six==1.17.0 |
| 515 | +SQLAlchemy==2.0.43 |
| 516 | +treq==25.5.0 |
| 517 | +Twisted==25.5.0 |
| 518 | +txaio==25.6.1 |
| 519 | +typing_extensions==4.15.0 |
| 520 | +unidiff==0.7.5 |
| 521 | +urllib3==2.5.0 |
| 522 | +zope.interface==7.2 |
| 523 | diff --git a/src/charm.py b/src/charm.py |
| 524 | new file mode 100755 |
| 525 | index 0000000..be5d93e |
| 526 | --- /dev/null |
| 527 | +++ b/src/charm.py |
| 528 | @@ -0,0 +1,137 @@ |
| 529 | +#!/usr/bin/env python3 |
| 530 | +# Copyright 2025 Tushar Gupta |
| 531 | +# See LICENSE file for licensing details. |
| 532 | + |
| 533 | +"""Charm the application.""" |
| 534 | + |
| 535 | +import getpass |
| 536 | +import logging |
| 537 | +import os |
| 538 | +import subprocess |
| 539 | + |
| 540 | +import ops |
| 541 | +from charmhelpers.core import templating |
| 542 | + |
| 543 | +logger = logging.getLogger(__name__) |
| 544 | + |
| 545 | +class BuildbotManagerCharm(ops.CharmBase): |
| 546 | + """Charm the application.""" |
| 547 | + |
| 548 | + def __init__(self, framework: ops.Framework): |
| 549 | + super().__init__(framework) |
| 550 | + framework.observe(self.on.install, self._on_install) |
| 551 | + framework.observe(self.on.config_changed, self._on_config_changed) |
| 552 | + # working directory for the buildbot manager |
| 553 | + self._venv_name = "sandbox" |
| 554 | + self._install_path = "/srv/buildbot/managers" |
| 555 | + self._venv_path = os.path.join(self._install_path, self._venv_name) |
| 556 | + self._buildbot_bin_path = os.path.join(self._venv_path, "bin", "buildbot") |
| 557 | + self._pip_bin_path = os.path.join(self._venv_path, "bin", "pip") |
| 558 | + |
| 559 | + def _run_command(self, command: list[str], message, cwd=None, within_venv=False): |
| 560 | + # See comment from Dima (element) |
| 561 | + # TODO: REmove all venv land ogic, unset PYTHON_PATH and LD_LIBRAR before calling |
| 562 | + # commands from inside the virtualenv and reset them.Y_PATH"""Utility to run shell commands with error handling.""" |
| 563 | + shell_command = "" |
| 564 | + try: |
| 565 | + self.unit.status = ops.MaintenanceStatus(message) |
| 566 | + if within_venv: |
| 567 | + command = [ |
| 568 | + "source", |
| 569 | + os.path.join(self._venv_path, "bin", "activate"), |
| 570 | + "&&", |
| 571 | + ] + command |
| 572 | + shell_command = " ".join(command) |
| 573 | + |
| 574 | + # Log current user, path, and PYTHONPATH |
| 575 | + logger.debug("Current user: %s", getpass.getuser()) |
| 576 | + logger.debug("Current working directory: %s", os.getcwd()) |
| 577 | + logger.debug("PYTHONPATH: %s", os.environ.get("PYTHONPATH", "")) |
| 578 | + |
| 579 | + logger.debug("Trying to run '%s'", shell_command) |
| 580 | + |
| 581 | + # nit: shell=True can lead to security implications, |
| 582 | + res = subprocess.run( |
| 583 | + shell_command, |
| 584 | + cwd=cwd, |
| 585 | + executable="/bin/bash", |
| 586 | + stdout=subprocess.PIPE, |
| 587 | + stderr=subprocess.PIPE, |
| 588 | + text=True if within_venv else False, |
| 589 | + shell=True, |
| 590 | + check=True, |
| 591 | + ) |
| 592 | + |
| 593 | + logger.debug("Command stdout:\n%s", res.stdout.strip()) |
| 594 | + logger.debug("Command stderr:\n%s", res.stderr.strip()) |
| 595 | + logger.debug( |
| 596 | + "Successfully ran '%s' with return code %s", shell_command, res.returncode |
| 597 | + ) |
| 598 | + |
| 599 | + except subprocess.CalledProcessError as e: |
| 600 | + logger.error("Failed to run '%s'", shell_command) |
| 601 | + logger.error("Return code: %s", e.returncode) |
| 602 | + logger.error("stdout:\n%s", e.stdout) |
| 603 | + logger.error("stderr:\n%s", e.stderr) |
| 604 | + self.unit.status = ops.BlockedStatus(f"Failure running: {message}") |
| 605 | + raise RuntimeError(f"Command failed: {shell_command}") from e |
| 606 | + |
| 607 | + def _on_install(self, event: ops.InstallEvent): |
| 608 | + """Handle install event: set up Buildbot manager environment.""" |
| 609 | + # nit: use pathlib |
| 610 | + self._run_command(["mkdir", "-p", self._install_path], "Creating manager root directory") |
| 611 | + |
| 612 | + self._run_command( |
| 613 | + ["python3", "-m", "venv", self._venv_path], |
| 614 | + "Creating virtualenv", |
| 615 | + cwd=self._install_path, |
| 616 | + ) |
| 617 | + |
| 618 | + # check if using cwd helps |
| 619 | + self._run_command( |
| 620 | + ["pip", "install", "--upgrade", "pip"], |
| 621 | + "Upgrading pip", |
| 622 | + cwd=self._install_path, |
| 623 | + within_venv=True, |
| 624 | + ) |
| 625 | + |
| 626 | + templating.render("buildbot@.service.j2", "/lib/systemd/system/buildbot@.service", {}) |
| 627 | + |
| 628 | + self._run_command(["systemctl", "daemon-reload"], message="Reload systemd") |
| 629 | + |
| 630 | + self._run_command( |
| 631 | + ["adduser", "--system", "--home", "/srv/buildbot", "--group", "buildbot"], |
| 632 | + "Creating buildbot user", |
| 633 | + ) |
| 634 | + |
| 635 | + def _on_config_changed(self, event: ops.ConfigChangedEvent): |
| 636 | + service_name = "lp-manager" |
| 637 | + self._run_command( |
| 638 | + ["buildbot", "create-master", service_name], |
| 639 | + "Creating Buildbot manager", |
| 640 | + cwd=self._install_path, |
| 641 | + within_venv=True, |
| 642 | + ) |
| 643 | + cfg_path = os.path.join(self._install_path, service_name) |
| 644 | + # think about what configurations this charm will need |
| 645 | + self._run_command( |
| 646 | + ["chown", "-R", "buildbot:buildbot", cfg_path], |
| 647 | + "Set ownership of Buildbot manager directory", |
| 648 | + ) |
| 649 | + |
| 650 | + self._run_command( |
| 651 | + ["mv", "master.cfg.sample", "master.cfg"], |
| 652 | + "Configuring master", |
| 653 | + cwd=cfg_path, |
| 654 | + ) |
| 655 | + |
| 656 | + self._run_command( |
| 657 | + ["systemctl", "enable", "--now", f"buildbot@{service_name}.service"], |
| 658 | + message="Enabling and starting buildbot manager", |
| 659 | + ) |
| 660 | + |
| 661 | + self.unit.status = ops.ActiveStatus("Buildbot master is running") |
| 662 | + |
| 663 | + |
| 664 | +if __name__ == "__main__": # pragma: nocover |
| 665 | + ops.main(BuildbotManagerCharm) # type: ignore |
| 666 | diff --git a/templates/buildbot@.service.j2 b/templates/buildbot@.service.j2 |
| 667 | new file mode 100644 |
| 668 | index 0000000..a5e9103 |
| 669 | --- /dev/null |
| 670 | +++ b/templates/buildbot@.service.j2 |
| 671 | @@ -0,0 +1,18 @@ |
| 672 | +[Unit] |
| 673 | +Description=Buildbot master %I |
| 674 | +Documentation=https://docs.buildbot.net/ |
| 675 | +ConditionDirectoryNotEmpty=/srv/buildbot/managers/%i |
| 676 | +ConditionFileNotEmpty=/srv/buildbot/managers/%i/buildbot.tac |
| 677 | +ConditionFileNotEmpty=/srv/buildbot/managers/%i/master.cfg |
| 678 | + |
| 679 | +[Service] |
| 680 | +Type=simple |
| 681 | +WorkingDirectory=/srv/buildbot/managers/%i |
| 682 | +PrivateTmp=true |
| 683 | +User=buildbot |
| 684 | +Group=buildbot |
| 685 | +ExecStart=/srv/buildbot/managers/sandbox/bin/buildbot start --nodaemon /srv/buildbot/managers/%i |
| 686 | +ExecReload=/bin/kill -HUP $MAINPID |
| 687 | + |
| 688 | +[Install] |
| 689 | +WantedBy=multi-user.target |
| 690 | diff --git a/tests/integration/test_charm.py b/tests/integration/test_charm.py |
| 691 | new file mode 100644 |
| 692 | index 0000000..c9da586 |
| 693 | --- /dev/null |
| 694 | +++ b/tests/integration/test_charm.py |
| 695 | @@ -0,0 +1,34 @@ |
| 696 | +#!/usr/bin/env python3 |
| 697 | +# Copyright 2025 Tushar Gupta |
| 698 | +# See LICENSE file for licensing details. |
| 699 | + |
| 700 | +import asyncio |
| 701 | +import logging |
| 702 | +from pathlib import Path |
| 703 | + |
| 704 | +import pytest |
| 705 | +import yaml |
| 706 | +from pytest_operator.plugin import OpsTest |
| 707 | + |
| 708 | +logger = logging.getLogger(__name__) |
| 709 | + |
| 710 | +METADATA = yaml.safe_load(Path("./metadata.yaml").read_text()) |
| 711 | +APP_NAME = METADATA["name"] |
| 712 | + |
| 713 | + |
| 714 | +@pytest.mark.abort_on_fail |
| 715 | +async def test_build_and_deploy(ops_test: OpsTest): |
| 716 | + """Build the charm-under-test and deploy it together with related charms. |
| 717 | + |
| 718 | + Assert on the unit status before any relations/configurations take place. |
| 719 | + """ |
| 720 | + # Build and deploy charm from local source folder |
| 721 | + charm = await ops_test.build_charm(".") |
| 722 | + |
| 723 | + # Deploy the charm and wait for active/idle status |
| 724 | + await asyncio.gather( |
| 725 | + ops_test.model.deploy(charm, application_name=APP_NAME), |
| 726 | + ops_test.model.wait_for_idle( |
| 727 | + apps=[APP_NAME], status="active", raise_on_blocked=True, timeout=1000 |
| 728 | + ), |
| 729 | + ) |
| 730 | diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py |
| 731 | new file mode 100644 |
| 732 | index 0000000..f16045e |
| 733 | --- /dev/null |
| 734 | +++ b/tests/unit/test_charm.py |
| 735 | @@ -0,0 +1,24 @@ |
| 736 | +# Copyright 2025 Tushar Gupta |
| 737 | +# See LICENSE file for licensing details. |
| 738 | +# |
| 739 | +# Learn more about testing at: https://juju.is/docs/sdk/testing |
| 740 | + |
| 741 | +import unittest |
| 742 | + |
| 743 | +import ops |
| 744 | +import ops.testing |
| 745 | + |
| 746 | +from charm import BuildbotManagerCharm |
| 747 | + |
| 748 | + |
| 749 | +class TestCharm(unittest.TestCase): |
| 750 | + def setUp(self): |
| 751 | + self.harness = ops.testing.Harness(BuildbotManagerCharm) |
| 752 | + self.addCleanup(self.harness.cleanup) |
| 753 | + |
| 754 | + def test_start(self): |
| 755 | + # Simulate the charm starting |
| 756 | + self.harness.begin_with_initial_hooks() |
| 757 | + |
| 758 | + # Ensure we set an ActiveStatus with no message |
| 759 | + self.assertEqual(self.harness.model.unit.status, ops.ActiveStatus()) |
| 760 | diff --git a/tox.ini b/tox.ini |
| 761 | new file mode 100644 |
| 762 | index 0000000..db6bb52 |
| 763 | --- /dev/null |
| 764 | +++ b/tox.ini |
| 765 | @@ -0,0 +1,86 @@ |
| 766 | +# Copyright 2025 Tushar Gupta |
| 767 | +# See LICENSE file for licensing details. |
| 768 | + |
| 769 | +[tox] |
| 770 | +no_package = True |
| 771 | +skip_missing_interpreters = True |
| 772 | +env_list = format, lint, static, unit |
| 773 | +min_version = 4.0.0 |
| 774 | + |
| 775 | +[vars] |
| 776 | +src_path = {tox_root}/src |
| 777 | +tests_path = {tox_root}/tests |
| 778 | +;lib_path = {tox_root}/lib/charms/operator_name_with_underscores |
| 779 | +all_path = {[vars]src_path} {[vars]tests_path} |
| 780 | + |
| 781 | +[testenv] |
| 782 | +set_env = |
| 783 | + PYTHONPATH = {tox_root}/lib:{[vars]src_path} |
| 784 | + PYTHONBREAKPOINT=pdb.set_trace |
| 785 | + PY_COLORS=1 |
| 786 | +pass_env = |
| 787 | + PYTHONPATH |
| 788 | + CHARM_BUILD_DIR |
| 789 | + MODEL_SETTINGS |
| 790 | + |
| 791 | +[testenv:format] |
| 792 | +description = Apply coding style standards to code |
| 793 | +deps = |
| 794 | + black |
| 795 | + ruff |
| 796 | +commands = |
| 797 | + black {[vars]all_path} |
| 798 | + ruff check --fix {[vars]all_path} |
| 799 | + |
| 800 | +[testenv:lint] |
| 801 | +description = Check code against coding style standards |
| 802 | +deps = |
| 803 | + black |
| 804 | + ruff |
| 805 | + codespell |
| 806 | +commands = |
| 807 | + # if this charm owns a lib, uncomment "lib_path" variable |
| 808 | + # and uncomment the following line |
| 809 | + # codespell {[vars]lib_path} |
| 810 | + codespell {tox_root} |
| 811 | + ruff check {[vars]all_path} |
| 812 | + black --check --diff {[vars]all_path} |
| 813 | + |
| 814 | +[testenv:unit] |
| 815 | +description = Run unit tests |
| 816 | +deps = |
| 817 | + pytest |
| 818 | + coverage[toml] |
| 819 | + -r {tox_root}/requirements.txt |
| 820 | +commands = |
| 821 | + coverage run --source={[vars]src_path} \ |
| 822 | + -m pytest \ |
| 823 | + --tb native \ |
| 824 | + -v \ |
| 825 | + -s \ |
| 826 | + {posargs} \ |
| 827 | + {[vars]tests_path}/unit |
| 828 | + coverage report |
| 829 | + |
| 830 | +[testenv:static] |
| 831 | +description = Run static type checks |
| 832 | +deps = |
| 833 | + pyright |
| 834 | + -r {tox_root}/requirements.txt |
| 835 | +commands = |
| 836 | + pyright {posargs} |
| 837 | + |
| 838 | +[testenv:integration] |
| 839 | +description = Run integration tests |
| 840 | +deps = |
| 841 | + pytest |
| 842 | + juju |
| 843 | + pytest-operator |
| 844 | + -r {tox_root}/requirements.txt |
| 845 | +commands = |
| 846 | + pytest -v \ |
| 847 | + -s \ |
| 848 | + --tb native \ |
| 849 | + --log-cli-level=INFO \ |
| 850 | + {posargs} \ |
| 851 | + {[vars]tests_path}/integration |