From b728cde6963de2a8ac6621a1d6b9a48fb4b49447 Mon Sep 17 00:00:00 2001 From: MoritzWeber Date: Mon, 1 Jul 2024 21:14:14 +0200 Subject: [PATCH] build: Use buildpacks for better caching --- .gitignore | 1 - .pre-commit-config.yaml | 2 +- LICENSES/BSD-3-Clause.txt | 24 ++ LICENSES/EPL-2.0.txt | 80 ++++ Makefile | 270 ++++---------- base/Dockerfile | 75 ---- builder/Dockerfile | 20 - builder/build_capella_from_source.sh | 69 ---- builder/inject_architecture_into_pom.py | 116 ------ buildpacks/builder/build.Dockerfile | 38 ++ buildpacks/builder/builder.toml | 68 ++++ buildpacks/builder/run.Dockerfile | 12 + .../buildpacks/capella-dropins/bin/build | 24 ++ .../buildpacks/capella-dropins/bin/detect | 9 + .../buildpacks/capella-dropins/buildpack.toml | 11 + .../capella-dropins}/install_dropins.py | 11 +- buildpacks/buildpacks/capella/autostart | 17 + buildpacks/buildpacks/capella/bin/build | 81 +++++ buildpacks/buildpacks/capella/bin/detect | 47 +++ buildpacks/buildpacks/capella/buildpack.toml | 11 + .../buildpacks/capella}/download_archive.py | 2 +- .../buildpacks/capella/entrypoint.sh | 6 +- .../capella/exec.d}/provisioning.py | 6 +- .../capella/exec.d/set_autostart.sh | 8 + .../capella/exec.d}/setup_workspace.py | 2 + buildpacks/buildpacks/jupyter/bin/build | 24 ++ buildpacks/buildpacks/jupyter/bin/detect | 9 + buildpacks/buildpacks/jupyter/buildpack.toml | 11 + buildpacks/buildpacks/pre-commit/bin/build | 21 ++ buildpacks/buildpacks/pre-commit/bin/detect | 5 + .../buildpacks/pre-commit/buildpack.toml | 11 + .../pre-commit}/hooks/+pre-commit-and-lfs.sh | 0 .../pre-commit}/hooks/+pre-commit-only.sh | 0 .../pre-commit}/hooks/+pre-commit.sh | 3 + buildpacks/buildpacks/supervisord/bin/build | 28 ++ buildpacks/buildpacks/supervisord/bin/detect | 5 + .../buildpacks/supervisord/buildpack.toml | 12 + buildpacks/buildpacks/t4c/bin/build | 63 ++++ buildpacks/buildpacks/t4c/bin/detect | 18 + buildpacks/buildpacks/t4c/buildpack.toml | 11 + .../buildpacks/t4c/entrypoint.sh | 6 +- .../t4c/exec.d/inject_in_capella_ini.sh | 11 + .../t4c/exec.d/reconstruct_layer.sh | 7 + .../t4c/exec.d}/setup_workspace_t4c.py | 3 +- buildpacks/buildpacks/xidletime/bin/build | 25 ++ buildpacks/buildpacks/xidletime/bin/detect | 5 + .../buildpacks/xidletime/buildpack.toml | 12 + .../buildpacks/xidletime}/metrics.py | 0 buildpacks/buildpacks/xpra/bin/build | 18 + buildpacks/buildpacks/xpra/bin/detect | 5 + buildpacks/buildpacks/xpra/buildpack.toml | 12 + .../buildpacks/xpra}/error.html | 0 .../xpra/exec.d/add_supervisord_config.sh | 8 +- .../xpra/exec.d/replace_nginx_vars.sh | 8 + .../buildpacks/xpra}/nginx.conf | 2 +- buildpacks/buildpacks/xrdp/bin/build | 18 + buildpacks/buildpacks/xrdp/bin/detect | 5 + buildpacks/buildpacks/xrdp/buildpack.toml | 12 + .../xrdp/exec.d/add_supervisord_config.sh | 7 + .../xrdp/exec.d/copy_openbox_config.sh | 9 + .../buildpacks/xrdp/exec.d/set_password.sh | 20 + .../buildpacks/xrdp}/menu.xml | 0 {remote => buildpacks/buildpacks/xrdp}/rc.xml | 0 buildpacks/extensions/base/bin/detect | 5 + buildpacks/extensions/base/bin/generate | 48 +++ buildpacks/extensions/base/extension.toml | 9 + buildpacks/extensions/capella-deps/bin/detect | 5 + .../extensions/capella-deps/bin/generate | 36 ++ .../extensions/capella-deps/extension.toml | 9 + .../extensions/xidletime-deps/bin/detect | 5 + .../extensions/xidletime-deps/bin/generate | 21 ++ .../extensions/xidletime-deps/extension.toml | 9 + buildpacks/extensions/xpra-deps/bin/detect | 5 + buildpacks/extensions/xpra-deps/bin/generate | 38 ++ .../extensions/xpra-deps/extension.toml | 9 + buildpacks/extensions/xrdp-deps/bin/detect | 5 + buildpacks/extensions/xrdp-deps/bin/generate | 32 ++ .../extensions/xrdp-deps/extension.toml | 9 + capella/.dockerignore.template | 7 - capella/.dockerignore.template.license | 2 - capella/Dockerfile | 174 --------- capella/autostart | 22 -- capella/libs/.gitkeep | 0 capella/patch.sh | 6 +- capella/setup/README.md | 9 - capella/setup/replace_env_variables.sh | 7 - capella/startup.sh | 8 - capella_loop.sh | 32 -- ci-templates/gitlab/diagram-cache.yml | 22 +- ci-templates/gitlab/image-builder.yml | 290 +++++---------- ci-templates/gitlab/model-validation.yml | 30 +- docs/docs/base.md | 69 ---- docs/docs/base/capella/index.md | 278 ++++++++++++++ docs/docs/base/capella/logo.png | Bin 0 -> 71882 bytes docs/docs/base/capella/logo.png.license | 2 + .../base.md => base/eclipse/index.md} | 6 +- docs/docs/base/eclipse/logo.png | Bin 0 -> 10747 bytes docs/docs/base/eclipse/logo.png.license | 2 + docs/docs/{ => base}/jupyter/index.md | 10 +- docs/docs/base/jupyter/logo.png | Bin 0 -> 81419 bytes docs/docs/base/jupyter/logo.png.license | 2 + .../base.md => base/papyrus/index.md} | 16 +- docs/docs/base/papyrus/logo.png | Bin 0 -> 25059 bytes docs/docs/base/papyrus/logo.png.license | 2 + docs/docs/capella/base.md | 273 -------------- docs/docs/capella/build-from-source.md | 25 -- docs/docs/capella/introduction.md | 64 ---- docs/docs/capella/provisioning.md | 62 ---- docs/docs/capella/t4c/base.md | 124 ------- docs/docs/capella/t4c/exporter.md | 96 ----- docs/docs/capella/t4c/importer.md | 139 ------- docs/docs/capella/t4c/introduction.md | 10 - .../docs/ci-templates/gitlab/image-builder.md | 209 ++++++----- .../docs/ci-templates/gitlab/release-train.md | 16 +- docs/docs/development/buildpacks.md | 61 ++++ docs/docs/{ => extensions}/pure-variants.md | 28 +- docs/docs/{ => extensions}/remote.md | 63 +--- docs/docs/extensions/t4c.md | 342 ++++++++++++++++++ .../git-hooks/egit-failed-git-hook.png | Bin .../egit-failed-git-hook.png.license | 0 .../git-hooks.md => hints/git-hooks/index.md} | 0 .../docs/{eclipse => hints}/memory-options.md | 4 + docs/docs/index.md | 151 ++++---- docs/docs/migration.md | 22 ++ docs/docs/stylesheets/commercial.css | 19 + docs/mkdocs.yml | 47 ++- eclipse/set_memory_flags.py | 6 +- remote/Dockerfile | 90 ----- remote/startup.sh | 55 --- remote/supervisord.conf | 13 - remote/tests/test_metrics.py | 68 ---- remote/wallpaper.png | 0 t4c/.dockerignore.template | 7 - t4c/.dockerignore.template.license | 2 - t4c/Dockerfile | 70 ---- t4c/t4c_cli/util/capella.py | 2 +- 136 files changed, 2278 insertions(+), 2455 deletions(-) create mode 100644 LICENSES/BSD-3-Clause.txt create mode 100644 LICENSES/EPL-2.0.txt delete mode 100644 base/Dockerfile delete mode 100644 builder/Dockerfile delete mode 100755 builder/build_capella_from_source.sh delete mode 100644 builder/inject_architecture_into_pom.py create mode 100644 buildpacks/builder/build.Dockerfile create mode 100644 buildpacks/builder/builder.toml create mode 100644 buildpacks/builder/run.Dockerfile create mode 100755 buildpacks/buildpacks/capella-dropins/bin/build create mode 100755 buildpacks/buildpacks/capella-dropins/bin/detect create mode 100644 buildpacks/buildpacks/capella-dropins/buildpack.toml rename {capella => buildpacks/buildpacks/capella-dropins}/install_dropins.py (84%) create mode 100755 buildpacks/buildpacks/capella/autostart create mode 100755 buildpacks/buildpacks/capella/bin/build create mode 100755 buildpacks/buildpacks/capella/bin/detect create mode 100644 buildpacks/buildpacks/capella/buildpack.toml rename {capella => buildpacks/buildpacks/capella}/download_archive.py (96%) rename remote/bg-saved.cfg => buildpacks/buildpacks/capella/entrypoint.sh (65%) mode change 100644 => 100755 rename {capella/setup => buildpacks/buildpacks/capella/exec.d}/provisioning.py (98%) mode change 100644 => 100755 create mode 100755 buildpacks/buildpacks/capella/exec.d/set_autostart.sh rename {capella/setup => buildpacks/buildpacks/capella/exec.d}/setup_workspace.py (99%) mode change 100644 => 100755 create mode 100755 buildpacks/buildpacks/jupyter/bin/build create mode 100755 buildpacks/buildpacks/jupyter/bin/detect create mode 100644 buildpacks/buildpacks/jupyter/buildpack.toml create mode 100755 buildpacks/buildpacks/pre-commit/bin/build create mode 100755 buildpacks/buildpacks/pre-commit/bin/detect create mode 100644 buildpacks/buildpacks/pre-commit/buildpack.toml rename {base => buildpacks/buildpacks/pre-commit}/hooks/+pre-commit-and-lfs.sh (100%) rename {base => buildpacks/buildpacks/pre-commit}/hooks/+pre-commit-only.sh (100%) rename {base => buildpacks/buildpacks/pre-commit}/hooks/+pre-commit.sh (91%) create mode 100755 buildpacks/buildpacks/supervisord/bin/build create mode 100755 buildpacks/buildpacks/supervisord/bin/detect create mode 100644 buildpacks/buildpacks/supervisord/buildpack.toml create mode 100755 buildpacks/buildpacks/t4c/bin/build create mode 100755 buildpacks/buildpacks/t4c/bin/detect create mode 100644 buildpacks/buildpacks/t4c/buildpack.toml rename t4c/docker_entrypoint.sh => buildpacks/buildpacks/t4c/entrypoint.sh (69%) create mode 100755 buildpacks/buildpacks/t4c/exec.d/inject_in_capella_ini.sh create mode 100755 buildpacks/buildpacks/t4c/exec.d/reconstruct_layer.sh rename {t4c => buildpacks/buildpacks/t4c/exec.d}/setup_workspace_t4c.py (97%) mode change 100644 => 100755 create mode 100755 buildpacks/buildpacks/xidletime/bin/build create mode 100755 buildpacks/buildpacks/xidletime/bin/detect create mode 100644 buildpacks/buildpacks/xidletime/buildpack.toml rename {remote => buildpacks/buildpacks/xidletime}/metrics.py (100%) create mode 100755 buildpacks/buildpacks/xpra/bin/build create mode 100755 buildpacks/buildpacks/xpra/bin/detect create mode 100644 buildpacks/buildpacks/xpra/buildpack.toml rename {remote => buildpacks/buildpacks/xpra}/error.html (100%) rename remote/supervisord.xpra.conf => buildpacks/buildpacks/xpra/exec.d/add_supervisord_config.sh (72%) mode change 100644 => 100755 create mode 100755 buildpacks/buildpacks/xpra/exec.d/replace_nginx_vars.sh rename {remote => buildpacks/buildpacks/xpra}/nginx.conf (96%) create mode 100755 buildpacks/buildpacks/xrdp/bin/build create mode 100755 buildpacks/buildpacks/xrdp/bin/detect create mode 100644 buildpacks/buildpacks/xrdp/buildpack.toml rename remote/supervisord.xrdp.conf => buildpacks/buildpacks/xrdp/exec.d/add_supervisord_config.sh (66%) mode change 100644 => 100755 create mode 100755 buildpacks/buildpacks/xrdp/exec.d/copy_openbox_config.sh create mode 100755 buildpacks/buildpacks/xrdp/exec.d/set_password.sh rename {remote => buildpacks/buildpacks/xrdp}/menu.xml (100%) rename {remote => buildpacks/buildpacks/xrdp}/rc.xml (100%) create mode 100755 buildpacks/extensions/base/bin/detect create mode 100755 buildpacks/extensions/base/bin/generate create mode 100644 buildpacks/extensions/base/extension.toml create mode 100755 buildpacks/extensions/capella-deps/bin/detect create mode 100755 buildpacks/extensions/capella-deps/bin/generate create mode 100644 buildpacks/extensions/capella-deps/extension.toml create mode 100755 buildpacks/extensions/xidletime-deps/bin/detect create mode 100755 buildpacks/extensions/xidletime-deps/bin/generate create mode 100644 buildpacks/extensions/xidletime-deps/extension.toml create mode 100755 buildpacks/extensions/xpra-deps/bin/detect create mode 100755 buildpacks/extensions/xpra-deps/bin/generate create mode 100644 buildpacks/extensions/xpra-deps/extension.toml create mode 100755 buildpacks/extensions/xrdp-deps/bin/detect create mode 100755 buildpacks/extensions/xrdp-deps/bin/generate create mode 100644 buildpacks/extensions/xrdp-deps/extension.toml delete mode 100644 capella/.dockerignore.template delete mode 100644 capella/.dockerignore.template.license delete mode 100644 capella/Dockerfile delete mode 100755 capella/autostart delete mode 100644 capella/libs/.gitkeep delete mode 100644 capella/setup/README.md delete mode 100755 capella/setup/replace_env_variables.sh delete mode 100755 capella/startup.sh delete mode 100755 capella_loop.sh delete mode 100644 docs/docs/base.md create mode 100644 docs/docs/base/capella/index.md create mode 100644 docs/docs/base/capella/logo.png create mode 100644 docs/docs/base/capella/logo.png.license rename docs/docs/{eclipse/base.md => base/eclipse/index.md} (96%) create mode 100644 docs/docs/base/eclipse/logo.png create mode 100644 docs/docs/base/eclipse/logo.png.license rename docs/docs/{ => base}/jupyter/index.md (82%) create mode 100644 docs/docs/base/jupyter/logo.png create mode 100644 docs/docs/base/jupyter/logo.png.license rename docs/docs/{papyrus/base.md => base/papyrus/index.md} (87%) create mode 100644 docs/docs/base/papyrus/logo.png create mode 100644 docs/docs/base/papyrus/logo.png.license delete mode 100644 docs/docs/capella/base.md delete mode 100644 docs/docs/capella/build-from-source.md delete mode 100644 docs/docs/capella/introduction.md delete mode 100644 docs/docs/capella/provisioning.md delete mode 100644 docs/docs/capella/t4c/base.md delete mode 100644 docs/docs/capella/t4c/exporter.md delete mode 100644 docs/docs/capella/t4c/importer.md delete mode 100644 docs/docs/capella/t4c/introduction.md create mode 100644 docs/docs/development/buildpacks.md rename docs/docs/{ => extensions}/pure-variants.md (70%) rename docs/docs/{ => extensions}/remote.md (60%) create mode 100644 docs/docs/extensions/t4c.md rename docs/docs/{ => hints}/git-hooks/egit-failed-git-hook.png (100%) rename docs/docs/{ => hints}/git-hooks/egit-failed-git-hook.png.license (100%) rename docs/docs/{git-hooks/git-hooks.md => hints/git-hooks/index.md} (100%) rename docs/docs/{eclipse => hints}/memory-options.md (93%) create mode 100644 docs/docs/migration.md create mode 100644 docs/docs/stylesheets/commercial.css mode change 100644 => 100755 eclipse/set_memory_flags.py delete mode 100644 remote/Dockerfile delete mode 100755 remote/startup.sh delete mode 100644 remote/supervisord.conf delete mode 100644 remote/tests/test_metrics.py delete mode 100644 remote/wallpaper.png delete mode 100644 t4c/.dockerignore.template delete mode 100644 t4c/.dockerignore.template.license delete mode 100644 t4c/Dockerfile diff --git a/.gitignore b/.gitignore index 86eab8d8..2e77caeb 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ # Capella capella/versions/*/*/capella.tar.gz -capella/versions/*/*/capella.zip capella/versions/*/dropins/* !capella/versions/*/dropins/.gitkeep capella/versions/*/patches/* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 866d8537..2f81183d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -73,7 +73,7 @@ repos: hooks: - id: insert-license name: Insert license headers (shell-style comments) - files: '(?:^|/)(?:.*\.(?:py|sh|toml|ya?ml|cfg|ini)|Dockerfile|Makefile|nginx.conf)$' + files: '(?:^|/)(?:.*\.(?:py|sh|toml|ya?ml|cfg|ini)|Dockerfile|Makefile|nginx.conf|build|detect|generate)$' exclude: '(?:^|/)\..+|^docs/Makefile$|^base/hooks/\+pre-commit.sh$' args: - --detect-license-in-X-top-lines=15 diff --git a/LICENSES/BSD-3-Clause.txt b/LICENSES/BSD-3-Clause.txt new file mode 100644 index 00000000..37897e23 --- /dev/null +++ b/LICENSES/BSD-3-Clause.txt @@ -0,0 +1,24 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/LICENSES/EPL-2.0.txt b/LICENSES/EPL-2.0.txt new file mode 100644 index 00000000..78756b5e --- /dev/null +++ b/LICENSES/EPL-2.0.txt @@ -0,0 +1,80 @@ +Eclipse Public License - v 2.0 +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE (“AGREEMENT”). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS +“Contribution” means: + +a) in the case of the initial Contributor, the initial content Distributed under this Agreement, and +b) in the case of each subsequent Contributor: +i) changes to the Program, and +ii) additions to the Program; +where such changes and/or additions to the Program originate from and are Distributed by that particular Contributor. A Contribution “originates” from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include changes or additions to the Program that are not Modified Works. +“Contributor” means any person or entity that Distributes the Program. + +“Licensed Patents” mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. + +“Program” means the Contributions Distributed in accordance with this Agreement. + +“Recipient” means anyone who receives the Program under this Agreement or any Secondary License (as applicable), including Contributors. + +“Derivative Works” shall mean any work, whether in Source Code or other form, that is based on (or derived from) the Program and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. + +“Modified Works” shall mean any work in Source Code or other form that results from an addition to, deletion from, or modification of the contents of the Program, including, for purposes of clarity any new file in Source Code form that contains any contents of the Program. Modified Works shall not include works that contain only declarations, interfaces, types, classes, structures, or files of the Program solely in each case in order to link to, bind by name, or subclass the Program or Modified Works thereof. + +“Distribute” means the acts of a) distributing or b) making available in any manner that enables the transfer of a copy. + +“Source Code” means the form of a Program preferred for making modifications, including but not limited to software source code, documentation source, and configuration files. + +“Secondary License” means either the GNU General Public License, Version 2.0, or any later versions of that license, including any exceptions or additional permissions as identified by the initial Contributor. + +2. GRANT OF RIGHTS +a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, Distribute and sublicense the Contribution of such Contributor, if any, and such Derivative Works. +b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in Source Code or other form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. +c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to Distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. +d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. +e) Notwithstanding the terms of any Secondary License, no Contributor makes additional grants to any Recipient (other than those set forth in this Agreement) as a result of such Recipient's receipt of the Program under the terms of a Secondary License (if permitted under the terms of Section 3). +3. REQUIREMENTS +3.1 If a Contributor Distributes the Program in any form, then: + +a) the Program must also be made available as Source Code, in accordance with section 3.2, and the Contributor must accompany the Program with a statement that the Source Code for the Program is available under this Agreement, and informs Recipients how to obtain it in a reasonable manner on or through a medium customarily used for software exchange; and +b) the Contributor may Distribute the Program under a license different than this Agreement, provided that such license: +i) effectively disclaims on behalf of all other Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; +ii) effectively excludes on behalf of all other Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; +iii) does not attempt to limit or alter the recipients' rights in the Source Code under section 3.2; and +iv) requires any subsequent distribution of the Program by any party to be under a license that satisfies the requirements of this section 3. +3.2 When the Program is Distributed as Source Code: + +a) it must be made available under this Agreement, or if the Program (i) is combined with other material in a separate file or files made available under a Secondary License, and (ii) the initial Contributor attached to the Source Code the notice described in Exhibit A of this Agreement, then the Program may be made available under the terms of such Secondary Licenses, and +b) a copy of this Agreement must be included with each copy of the Program. +3.3 Contributors may not remove or alter any copyright, patent, trademark, attribution notices, disclaimers of warranty, or limitations of liability (‘notices’) contained within the Program from any copy of the Program which they Distribute, provided that Contributors may add their own appropriate notices. + +4. COMMERCIAL DISTRIBUTION +Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor (“Commercial Contributor”) hereby agrees to defend and indemnify every other Contributor (“Indemnified Contributor”) against any losses, damages and costs (collectively “Losses”) arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. + +5. NO WARRANTY +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL +If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be Distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to Distribute the Program (including its Contributions) under the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. Nothing in this Agreement is intended to be enforceable by any entity that is not a Contributor or Recipient. No third-party beneficiary rights are created under this Agreement. + +Exhibit A – Form of Secondary Licenses Notice +“This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), version(s), and exceptions or additional permissions here}.” + +Simply including a copy of this Agreement, including this Exhibit A is not sufficient to license the Source Code under Secondary Licenses. + +If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. diff --git a/Makefile b/Makefile index f9f4ab30..b92fdef5 100644 --- a/Makefile +++ b/Makefile @@ -71,9 +71,6 @@ GIT_SERVER_PORT ?= 10001 # Preferred metrics port on your host system METRICS_PORT ?= 9118 -# Capella version used for builds and tests -export CAPELLA_VERSIONS ?= 5.0.0 5.2.0 6.0.0 6.1.0 - # Capella version used to run containers export CAPELLA_VERSION ?= 6.1.0 @@ -86,8 +83,7 @@ AUTOSTART_CAPELLA ?= 1 # See available options in documentation: https://dsd-dbs.github.io/capella-dockerimages/capella/base/#optional-customisation-of-the-capella-client CAPELLA_DROPINS ?= ModelsImporter,CapellaXHTMLDocGen,DiagramStyler,PVMT,Filtering,Requirements,SubsystemTransition,TextualEditor -# Only use when "capella_loop.sh" is NOT used -export DOCKER_TAG_SCHEMA ?= $$CAPELLA_VERSION-$$CAPELLA_DOCKERIMAGES_REVISION +export DOCKER_TAG ?= $(CAPELLA_VERSION)-$(CAPELLA_DOCKERIMAGES_REVISION) PAPYRUS_VERSION ?= 6.4.0 @@ -112,15 +108,14 @@ INSTALL_OLD_GTK_VERSION ?= true PURE_VARIANTS_LICENSE_SERVER ?= http://localhost:8080 PURE_VARIANTS_KNOWN_SERVERS ?= '[{"name": "test", "url": "http://example.localhost"}]' -# Inject libraries from the capella/libs directory -INJECT_LIBS_CAPELLA ?= false - -# Build architecture: amd64 or arm64 +# Target build architecture: amd64 or arm64 BUILD_ARCHITECTURE ?= amd64 DOCKER_BUILD_FLAGS ?= --platform linux/$(BUILD_ARCHITECTURE) DOCKER_RUN_FLAGS ?= --add-host=host.docker.internal:host-gateway --rm -it -DOCKER_DEBUG_FLAGS ?= -it --entrypoint="bash" -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=$$DISPLAY +DOCKER_DEBUG_FLAGS ?= -it --entrypoint="bash" + +PACK_BUILD_FLAGS ?= --path ./buildpacks --pull-policy always --volume $$PWD:/app:ro # If set to 1, we will push the images to the specified registry PUSH_IMAGES ?= 0 @@ -160,134 +155,65 @@ export MAKE_CURRENT_TARGET=$@ SHELL=/bin/bash all: \ - base \ - jupyter-notebook \ - capella/base \ - capella/remote \ - t4c/client/base \ - t4c/client/remote \ - t4c/client/remote/pure-variants \ - capella/remote/pure-variants - -base: SHELL=/bin/bash -base: - docker build $(DOCKER_BUILD_FLAGS) \ - --build-arg UID=$(TECHUSER_UID) \ - -t $(DOCKER_PREFIX)$@:$(CAPELLA_DOCKERIMAGES_REVISION) \ - base - $(MAKE) PUSH_IMAGES=$(PUSH_IMAGES) DOCKER_TAG=$(CAPELLA_DOCKERIMAGES_REVISION) IMAGENAME=$@ .push - -jupyter-notebook: DOCKER_TAG=$(JUPYTER_NOTEBOOK_REVISION) -jupyter-notebook: base - docker build $(DOCKER_BUILD_FLAGS) -t $(DOCKER_PREFIX)$@:$(DOCKER_TAG) jupyter-notebook - $(MAKE) PUSH_IMAGES=$(PUSH_IMAGES) DOCKER_TAG=$(DOCKER_TAG) IMAGENAME=$@ .push - -capella/base: SHELL=./capella_loop.sh -capella/base: base - envsubst < capella/.dockerignore.template > capella/.dockerignore - cp eclipse/set_memory_flags.py capella/setup/set_memory_flags.py - docker build $(DOCKER_BUILD_FLAGS) \ - -t $(DOCKER_PREFIX)$@:$$DOCKER_TAG \ - --build-arg BUILD_ARCHITECTURE=$(BUILD_ARCHITECTURE) \ - --build-arg BASE_IMAGE=$(DOCKER_PREFIX)$<:$(CAPELLA_DOCKERIMAGES_REVISION) \ - --build-arg BUILD_TYPE=$(CAPELLA_BUILD_TYPE) \ - --build-arg CAPELLA_VERSION=$$CAPELLA_VERSION \ - --build-arg "CAPELLA_DROPINS=$(CAPELLA_DROPINS)" \ - --build-arg "INJECT_PACKAGES=$(INJECT_LIBS_CAPELLA)" \ - --build-arg INSTALL_OLD_GTK_VERSION=$(INSTALL_OLD_GTK_VERSION) \ - capella - rm capella/.dockerignore - rm capella/setup/set_memory_flags.py - $(MAKE) PUSH_IMAGES=$(PUSH_IMAGES) IMAGENAME=$@ .push - -papyrus/base: DOCKER_TAG=$(PAPYRUS_VERSION)-$(CAPELLA_DOCKERIMAGES_REVISION) -papyrus/base: DOCKER_BUILD_FLAGS=--platform linux/amd64 -papyrus/base: base + jupyter \ + capella \ + papyrus \ + eclipse + +builder: + docker build buildpacks/builder -f buildpacks/builder/build.Dockerfile \ + --build-arg CNB_USER_ID=$(TECHUSER_UID) \ + -t $(DOCKER_REGISTRY)/buildpacks-base + docker push $(DOCKER_REGISTRY)/buildpacks-base + docker build buildpacks/builder \ + -f buildpacks/builder/run.Dockerfile \ + --build-arg TECHUSER_USER_ID=$(TECHUSER_UID) \ + -t $(DOCKER_REGISTRY)/run-base + docker push $(DOCKER_REGISTRY)/run-base + pack builder create $(DOCKER_REGISTRY)/mbse-builder \ + --config buildpacks/builder/builder.toml \ + --publish + +jupyter: builder + docker build $(DOCKER_BUILD_FLAGS) -t $(DOCKER_PREFIX)$@:$(JUPYTER_NOTEBOOK_REVISION) jupyter-notebook + $(MAKE) PUSH_IMAGES=$(PUSH_IMAGES) DOCKER_TAG=$(JUPYTER_NOTEBOOK_REVISION) IMAGENAME=$@ .push + +capella: builder + pack build $(DOCKER_REGISTRY)/$(DOCKER_PREFIX)$@:$(DOCKER_TAG) \ + --env TOOL=capella \ + --env CAPELLA_BUILD_TYPE=$(CAPELLA_BUILD_TYPE) \ + --env CAPELLA_VERSION=$$CAPELLA_VERSION \ + --env CAPELLA_DROPINS="$(CAPELLA_DROPINS)" \ + --builder localhost:12345/mbse-builder \ + --uid $(TECHUSER_UID) \ + --network host \ + $(PACK_BUILD_FLAGS) + +papyrus: DOCKER_BUILD_FLAGS=--platform linux/amd64 +papyrus: base cp eclipse/set_memory_flags.py papyrus/set_memory_flags.py docker build $(DOCKER_BUILD_FLAGS) \ - -t $(DOCKER_PREFIX)$@:$(DOCKER_TAG) \ + -t $(DOCKER_PREFIX)$@:$(PAPYRUS_VERSION)-$(CAPELLA_DOCKERIMAGES_REVISION) \ --build-arg BASE_IMAGE=$(DOCKER_PREFIX)$<:$(CAPELLA_DOCKERIMAGES_REVISION) \ - --build-arg PAPYRUS_VERSION=$(PAPYRUS_VERSION) \ + --build-arg PAPYRUS_VERSION=$(PAPYRUS_VERSION)-$(CAPELLA_DOCKERIMAGES_REVISION) \ papyrus rm papyrus/set_memory_flags.py $(MAKE) PUSH_IMAGES=$(PUSH_IMAGES) DOCKER_TAG=$(DOCKER_TAG) IMAGENAME=$@ .push -eclipse/base: DOCKER_TAG=$(ECLIPSE_VERSION)-$(CAPELLA_DOCKERIMAGES_REVISION) -eclipse/base: base +eclipse: base docker build $(DOCKER_BUILD_FLAGS) \ - -t $(DOCKER_PREFIX)$@:$(DOCKER_TAG) \ + -t $(DOCKER_PREFIX)$@:$(ECLIPSE_VERSION)-$(CAPELLA_DOCKERIMAGES_REVISION) \ --build-arg BUILD_ARCHITECTURE=$(BUILD_ARCHITECTURE) \ --build-arg BASE_IMAGE=$(DOCKER_PREFIX)$<:$(CAPELLA_DOCKERIMAGES_REVISION) \ --build-arg ECLIPSE_VERSION=$(ECLIPSE_VERSION) \ eclipse - $(MAKE) PUSH_IMAGES=$(PUSH_IMAGES) DOCKER_TAG=$(DOCKER_TAG) IMAGENAME=$@ .push - -capella/remote: SHELL=./capella_loop.sh -capella/remote: capella/base - docker build $(DOCKER_BUILD_FLAGS) -t $(DOCKER_PREFIX)$@:$$DOCKER_TAG --build-arg BASE_IMAGE=$(DOCKER_PREFIX)$<:$$DOCKER_TAG remote - $(MAKE) PUSH_IMAGES=$(PUSH_IMAGES) IMAGENAME=$@ .push - -papyrus/remote: DOCKER_TAG=$(PAPYRUS_VERSION)-$(CAPELLA_DOCKERIMAGES_REVISION) -papyrus/remote: DOCKER_BUILD_FLAGS=--platform linux/amd64 -papyrus/remote: papyrus/base - docker build $(DOCKER_BUILD_FLAGS) \ - -t $(DOCKER_PREFIX)$@:$(DOCKER_TAG) \ - --build-arg BASE_IMAGE=$(DOCKER_PREFIX)$<:$(DOCKER_TAG) \ - remote - $(MAKE) PUSH_IMAGES=$(PUSH_IMAGES) DOCKER_TAG=$(DOCKER_TAG) IMAGENAME=$@ .push - -eclipse/remote: DOCKER_TAG=$(ECLIPSE_VERSION)-$(CAPELLA_DOCKERIMAGES_REVISION) -eclipse/remote: eclipse/base - docker build $(DOCKER_BUILD_FLAGS) \ - -t $(DOCKER_PREFIX)$@:$(DOCKER_TAG) \ - --build-arg BASE_IMAGE=$(DOCKER_PREFIX)$<:$(DOCKER_TAG) \ - remote - $(MAKE) PUSH_IMAGES=$(PUSH_IMAGES) DOCKER_TAG=$(DOCKER_TAG) IMAGENAME=$@ .push - -eclipse/remote/pure-variants: DOCKER_TAG=$(ECLIPSE_VERSION)-$(PURE_VARIANTS_VERSION)-$(CAPELLA_DOCKERIMAGES_REVISION) -eclipse/remote/pure-variants: eclipse/remote - docker build $(DOCKER_BUILD_FLAGS) \ - -t $(DOCKER_PREFIX)$@:$(DOCKER_TAG) \ - --build-arg BASE_IMAGE=$(DOCKER_PREFIX)$<:$(ECLIPSE_VERSION)-$(CAPELLA_DOCKERIMAGES_REVISION) \ - --build-arg PURE_VARIANTS_VERSION=$(PURE_VARIANTS_VERSION) \ - pure-variants - $(MAKE) PUSH_IMAGES=$(PUSH_IMAGES) DOCKER_TAG=$(DOCKER_TAG) IMAGENAME=$@ .push - -t4c/client/base: SHELL=./capella_loop.sh -t4c/client/base: capella/base - envsubst < t4c/.dockerignore.template > t4c/.dockerignore - docker build $(DOCKER_BUILD_FLAGS) -t $(DOCKER_PREFIX)$@:$$DOCKER_TAG --build-arg BASE_IMAGE=$(DOCKER_PREFIX)$<:$$DOCKER_TAG --build-arg CAPELLA_VERSION=$$CAPELLA_VERSION t4c - rm t4c/.dockerignore - $(MAKE) PUSH_IMAGES=$(PUSH_IMAGES) IMAGENAME=$@ .push - -t4c/client/remote: SHELL=./capella_loop.sh -t4c/client/remote: t4c/client/base - docker build $(DOCKER_BUILD_FLAGS) -t $(DOCKER_PREFIX)$@:$$DOCKER_TAG --build-arg BASE_IMAGE=$(DOCKER_PREFIX)$<:$$DOCKER_TAG remote - $(MAKE) PUSH_IMAGES=$(PUSH_IMAGES) IMAGENAME=$@ .push - -t4c/client/remote/pure-variants: SHELL=./capella_loop.sh -t4c/client/remote/pure-variants: t4c/client/remote - docker build $(DOCKER_BUILD_FLAGS) \ - -t $(DOCKER_PREFIX)$@:$$DOCKER_TAG \ - --build-arg BASE_IMAGE=$(DOCKER_PREFIX)$<:$$DOCKER_TAG \ - --build-arg PURE_VARIANTS_VERSION=$(PURE_VARIANTS_VERSION) \ - pure-variants - $(MAKE) PUSH_IMAGES=$(PUSH_IMAGES) IMAGENAME=$@ .push - -capella/remote/pure-variants: SHELL=./capella_loop.sh -capella/remote/pure-variants: capella/remote - docker build $(DOCKER_BUILD_FLAGS) -t $(DOCKER_PREFIX)$@:$$DOCKER_TAG --build-arg BASE_IMAGE=$(DOCKER_PREFIX)$<:$$DOCKER_TAG pure-variants - $(MAKE) PUSH_IMAGES=$(PUSH_IMAGES) IMAGENAME=$@ .push - -capella/builder: - docker build $(DOCKER_BUILD_FLAGS) -t $(DOCKER_PREFIX)$@:$(CAPELLA_DOCKERIMAGES_REVISION) builder - docker run -it -e CAPELLA_VERSION=$(CAPELLA_VERSION) -v $$(pwd)/builder/output/$(CAPELLA_VERSION):/output -v $$(pwd)/builder/m2_cache:/root/.m2/repository $(DOCKER_PREFIX)$@:$(CAPELLA_DOCKERIMAGES_REVISION) + $(MAKE) PUSH_IMAGES=$(PUSH_IMAGES) DOCKER_TAG=$(ECLIPSE_VERSION)-$(CAPELLA_DOCKERIMAGES_REVISION) IMAGENAME=$@ .push run-capella/base: capella/base docker run $(DOCKER_RUN_FLAGS) \ $(DOCKER_PREFIX)$<:$$(echo "$(DOCKER_TAG_SCHEMA)" | envsubst) -run-jupyter-notebook: jupyter-notebook +run-jupyter: jupyter docker run $(DOCKER_RUN_FLAGS) \ -p $(WEB_PORT):8888 \ -p $(METRICS_PORT):9118 \ @@ -296,26 +222,27 @@ run-jupyter-notebook: jupyter-notebook -e JUPYTER_BASE_URL=/subpath \ $(DOCKER_PREFIX)$<:$(JUPYTER_NOTEBOOK_REVISION) -run-capella/remote: capella/remote - FLAGS=""; - if [ -n "$(WORKSPACE_NAME)" ]; then \ - FLAGS="-v $$(pwd)/volumes/workspaces/$(WORKSPACE_NAME):/workspace"; \ - fi +run-capella/remote: capella docker run $(DOCKER_RUN_FLAGS) \ $$FLAGS \ + -v $$(pwd)/volumes/pure-variants:/inputs/pure-variants \ + -e T4C_LICENCE_SECRET=$(T4C_LICENCE_SECRET) \ + -e T4C_JSON=$(T4C_JSON) \ -e RMT_PASSWORD=$(RMT_PASSWORD) \ - -e CONNECTION_METHOD=$(CONNECTION_METHOD) \ + -e T4C_USERNAME=$(T4C_USERNAME) \ + -e PURE_VARIANTS_LICENSE_SERVER=$(PURE_VARIANTS_LICENSE_SERVER) \ + -e PURE_VARIANTS_KNOWN_SERVERS=$(PURE_VARIANTS_KNOWN_SERVERS) \ -e AUTOSTART_CAPELLA=$(AUTOSTART_CAPELLA) \ + -e CONNECTION_METHOD=$(CONNECTION_METHOD) \ -e XPRA_SUBPATH=$(XPRA_SUBPATH) \ -e MEMORY_MIN=$(MEMORY_MIN) \ -e MEMORY_MAX=$(MEMORY_MAX) \ -p $(RDP_PORT):3389 \ -p $(WEB_PORT):10000 \ -p $(METRICS_PORT):9118 \ - $(DOCKER_PREFIX)$<:$$(echo "$(DOCKER_TAG_SCHEMA)" | envsubst) + $(DOCKER_REGISTRY)/$(DOCKER_PREFIX)$<:$(DOCKER_TAG) -run-papyrus/remote: DOCKER_TAG=$(PAPYRUS_VERSION)-$(CAPELLA_DOCKERIMAGES_REVISION) -run-papyrus/remote: papyrus/remote +run-papyrus/remote: papyrus docker run \ --platform linux/amd64 \ -e RMT_PASSWORD=$(RMT_PASSWORD) \ @@ -327,24 +254,9 @@ run-papyrus/remote: papyrus/remote -p $(RDP_PORT):3389 \ -p $(WEB_PORT):10000 \ -p $(METRICS_PORT):9118 \ - $(DOCKER_PREFIX)$<:$(DOCKER_TAG) + $(DOCKER_PREFIX)$<:$(PAPYRUS_VERSION)-$(CAPELLA_DOCKERIMAGES_REVISION) -run-eclipse/remote: DOCKER_TAG=$(ECLIPSE_VERSION)-$(CAPELLA_DOCKERIMAGES_REVISION) -run-eclipse/remote: eclipse/remote - docker run \ - -e RMT_PASSWORD=$(RMT_PASSWORD) \ - -e CONNECTION_METHOD=$(CONNECTION_METHOD) \ - -e XPRA_SUBPATH=$(XPRA_SUBPATH) \ - -e WORKSPACE_DIR=/workspace/eclipse \ - -e MEMORY_MIN=$(MEMORY_MIN) \ - -e MEMORY_MAX=$(MEMORY_MAX) \ - -p $(RDP_PORT):3389 \ - -p $(WEB_PORT):10000 \ - -p $(METRICS_PORT):9118 \ - $(DOCKER_PREFIX)$<:$(DOCKER_TAG) - -run-eclipse/remote/pure-variants: DOCKER_TAG=$(ECLIPSE_VERSION)-$(PURE_VARIANTS_VERSION)-$(CAPELLA_DOCKERIMAGES_REVISION) -run-eclipse/remote/pure-variants: eclipse/remote/pure-variants +run-eclipse/remote: eclipse docker run $(DOCKER_RUN_FLAGS) \ -e RMT_PASSWORD=$(RMT_PASSWORD) \ -e PURE_VARIANTS_LICENSE_SERVER=$(PURE_VARIANTS_LICENSE_SERVER) \ @@ -360,61 +272,8 @@ run-eclipse/remote/pure-variants: eclipse/remote/pure-variants -p $(RDP_PORT):3389 \ -p $(WEB_PORT):10000 \ -p $(METRICS_PORT):9118 \ - $(DOCKER_PREFIX)$<:$(DOCKER_TAG) - - -run-t4c/client/remote-legacy: t4c/client/remote - docker run $(DOCKER_RUN_FLAGS) \ - -v $$(pwd)/volumes/workspaces/$(WORKSPACE_NAME):/workspace \ - -e T4C_LICENCE_SECRET=$(T4C_LICENCE_SECRET) \ - -e T4C_SERVER_HOST=$(T4C_SERVER_HOST) \ - -e T4C_SERVER_PORT=$(T4C_SERVER_PORT) \ - -e T4C_REPOSITORIES=$(T4C_REPOSITORIES) \ - -e RMT_PASSWORD=$(RMT_PASSWORD) \ - -e T4C_USERNAME=$(T4C_USERNAME) \ - -e CONNECTION_METHOD=$(CONNECTION_METHOD) \ - -e XPRA_SUBPATH=$(XPRA_SUBPATH) \ - -e MEMORY_MIN=$(MEMORY_MIN) \ - -e MEMORY_MAX=$(MEMORY_MAX) \ - -p $(RDP_PORT):3389 \ - -p $(WEB_PORT):10000 \ - -p $(METRICS_PORT):9118 \ - $(DOCKER_PREFIX)$<:$$(echo "$(DOCKER_TAG_SCHEMA)" | envsubst) - -run-t4c/client/remote: t4c/client/remote - docker run $(DOCKER_RUN_FLAGS) \ - -v $$(pwd)/volumes/workspaces/$(WORKSPACE_NAME):/workspace \ - -e T4C_LICENCE_SECRET=$(T4C_LICENCE_SECRET) \ - -e T4C_JSON=$(T4C_JSON) \ - -e RMT_PASSWORD=$(RMT_PASSWORD) \ - -e T4C_USERNAME=$(T4C_USERNAME) \ - -e CONNECTION_METHOD=$(CONNECTION_METHOD) \ - -e XPRA_SUBPATH=$(XPRA_SUBPATH) \ - -e MEMORY_MIN=$(MEMORY_MIN) \ - -e MEMORY_MAX=$(MEMORY_MAX) \ - -p $(RDP_PORT):3389 \ - -p $(WEB_PORT):10000 \ - -p $(METRICS_PORT):9118 \ - $(DOCKER_PREFIX)$<:$$(echo "$(DOCKER_TAG_SCHEMA)" | envsubst) + $(DOCKER_PREFIX)$<:$(ECLIPSE_VERSION)-$(PURE_VARIANTS_VERSION)-$(CAPELLA_DOCKERIMAGES_REVISION) -run-t4c/client/remote/pure-variants: t4c/client/remote/pure-variants - docker run $(DOCKER_RUN_FLAGS) \ - -v $$(pwd)/volumes/pure-variants:/inputs/pure-variants \ - -v $$(pwd)/volumes/workspace:/workspace \ - -e T4C_LICENCE_SECRET=$(T4C_LICENCE_SECRET) \ - -e T4C_JSON=$(T4C_JSON) \ - -e RMT_PASSWORD=$(RMT_PASSWORD) \ - -e T4C_USERNAME=$(T4C_USERNAME) \ - -e PURE_VARIANTS_LICENSE_SERVER=$(PURE_VARIANTS_LICENSE_SERVER) \ - -e PURE_VARIANTS_KNOWN_SERVERS=$(PURE_VARIANTS_KNOWN_SERVERS) \ - -e AUTOSTART_CAPELLA=$(AUTOSTART_CAPELLA) \ - -e CONNECTION_METHOD=$(CONNECTION_METHOD) \ - -e XPRA_SUBPATH=$(XPRA_SUBPATH) \ - -e WORKSPACE_DIR=/workspace/capella_pv \ - -p $(RDP_PORT):3389 \ - -p $(WEB_PORT):10000 \ - -p $(METRICS_PORT):9118 \ - $(DOCKER_PREFIX)$<:$$(echo "$(DOCKER_TAG_SCHEMA)" | envsubst) run-t4c/client/backup: t4c/client/base docker run $(DOCKER_RUN_FLAGS) --rm -it \ @@ -481,13 +340,11 @@ debug-eclipse/remote/pure-variants: run-eclipse/remote/pure-variants debug-jupyter-notebook: DOCKER_RUN_FLAGS=$(DOCKER_DEBUG_FLAGS) debug-jupyter-notebook: run-jupyter-notebook -t4c/server/server: SHELL=./capella_loop.sh t4c/server/server: $(MAKE) -C t4c/server PUSH_IMAGES=$(PUSH_IMAGES) CAPELLA_VERSION=$$CAPELLA_VERSION $@ -local-git-server: SHELL=./capella_loop.sh local-git-server: - docker build $(DOCKER_BUILD_FLAGS) -t $(DOCKER_PREFIX)$@:$$DOCKER_TAG tests/local-git-server + docker build $(DOCKER_BUILD_FLAGS) -t $(DOCKER_PREFIX)$@:$(DOCKER_TAG) tests/local-git-server $(MAKE) PUSH_IMAGES=$(PUSH_IMAGES) IMAGENAME=$@ .push run-local-git-server: local-git-server @@ -503,7 +360,6 @@ ifeq ($(RUN_TESTS_WITH_T4C_CLIENT), 1) test: t4c/client/remote endif -test: SHELL=./capella_loop.sh test: export CAPELLA_VERSION=$$CAPELLA_VERSION source .venv/bin/activate @@ -525,8 +381,8 @@ test: .push: @if [ "$(PUSH_IMAGES)" == "1" ]; \ then \ - docker tag "$(DOCKER_PREFIX)$(IMAGENAME):$$DOCKER_TAG" "$(DOCKER_REGISTRY)/$(DOCKER_PREFIX)$(IMAGENAME):$$DOCKER_TAG"; \ - docker push "$(DOCKER_REGISTRY)/$(DOCKER_PREFIX)$(IMAGENAME):$$DOCKER_TAG";\ + docker tag "$(DOCKER_PREFIX)$(IMAGENAME):$(DOCKER_TAG)" "$(DOCKER_REGISTRY)/$(DOCKER_PREFIX)$(IMAGENAME):$(DOCKER_TAG)"; \ + docker push "$(DOCKER_REGISTRY)/$(DOCKER_PREFIX)$(IMAGENAME):$(DOCKER_TAG)";\ fi .PHONY: tests/* t4c/* t4c/server/* * diff --git a/base/Dockerfile b/base/Dockerfile deleted file mode 100644 index 752f6fd8..00000000 --- a/base/Dockerfile +++ /dev/null @@ -1,75 +0,0 @@ - -# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -ARG BASE_IMAGE=debian:bookworm-slim -FROM $BASE_IMAGE - -USER root - -SHELL ["/bin/bash", "-euo", "pipefail", "-c"] -ENV SHELL=/bin/bash - -RUN apt-get update && \ - apt-get upgrade --yes && \ - apt-get install --yes \ - gettext-base \ - locales \ - python3 \ - python3-pip \ - python3-venv \ - git-lfs \ - unzip \ - neovim \ - zip \ - wget && \ - rm -rf /var/lib/apt/lists/* - -RUN echo "en_GB.UTF-8 UTF-8" >> /etc/locale.gen && \ - locale-gen en_GB.UTF-8 && \ - update-locale en_GB.UTF-8 && \ - dpkg-reconfigure --frontend=noninteractive locales && \ - echo "LANG=en_GB.UTF-8" >> /etc/default/locale && \ - echo "LANGUAGE=en_GB:en" >> /etc/default/locale && \ - echo "LC_ALL=en_GB.UTF-8" >> /etc/default/locale -ENV LANG="en_GB.UTF-8" -ENV LANGUAGE="en_GB:en" -ENV LC_ALL="en_GB.UTF-8" - -# Create techuser with UID -ARG UID=1001 -RUN useradd -l -m -u $UID techuser && \ - echo "techuser:tmp_passwd" | chpasswd \ - && chown techuser /home/techuser -ENV HOME=/home/techuser - -# This in analogous to the virtualenv activate script -# To allow deactivation of the virtualenv, we need to save the old PATH -ENV _OLD_VIRTUAL_PATH="$PATH" -ENV VIRTUAL_ENV=/opt/.venv -ENV PATH="$VIRTUAL_ENV/bin:$PATH" - -COPY --chmod=755 hooks/* /opt/git/global-hooks/ - -WORKDIR /opt/git/global-hooks - -RUN ln -s "$(which python3.11)" /usr/bin/python && \ - ln -sf "$(which python3.11)" /usr/bin/python3 && \ - ln -sf "$(which pip3.11)" /usr/local/bin/pip && \ - ln -sf "$(which pip3.11)" /usr/local/bin/pip3 && \ - python -m venv /opt/.venv && \ - # Configure pre-commit - pip install --no-cache-dir pre-commit lxml PyYAML --no-cache-dir && \ - echo "commit-msg post-rewrite pre-commit pre-merge-commit pre-rebase prepare-commit-msg" | xargs -n 1 cp /opt/git/global-hooks/+pre-commit-only.sh && \ - echo "pre-push post-checkout post-commit post-merge" | xargs -n 1 cp /opt/git/global-hooks/+pre-commit-and-lfs.sh && \ - git config --global core.hooksPath /opt/git/global-hooks && \ - chmod -R 755 /opt/git/global-hooks && \ - chown -R techuser /opt/.venv/bin/ /opt/.venv/lib/python3.11/site-packages - -# Make pre-commit cache persistent -ENV PRE_COMMIT_HOME=/workspace/.pre-commit - -ENV WORKSPACE_DIR=/workspace -WORKDIR /workspace - -USER techuser diff --git a/builder/Dockerfile b/builder/Dockerfile deleted file mode 100644 index 9908a4ee..00000000 --- a/builder/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -FROM python:3.11-bookworm - - -SHELL ["/bin/bash", "-euo", "pipefail", "-c"] -ENV SHELL=/bin/bash - -RUN apt-get update && \ - apt-get install -y maven - -RUN pip install --no-cache-dir lxml==4.9.3 - -COPY inject_architecture_into_pom.py /opt/inject_architecture_into_pom.py -COPY build_capella_from_source.sh /opt/build_capella_from_source.sh - -RUN chmod +x /opt/build_capella_from_source.sh - -ENTRYPOINT [ "/opt/build_capella_from_source.sh" ] diff --git a/builder/build_capella_from_source.sh b/builder/build_capella_from_source.sh deleted file mode 100755 index 341509f2..00000000 --- a/builder/build_capella_from_source.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/bin/bash -# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -set -ex - -export CAPELLA_VERSION=${CAPELLA_VERSION:-6.0.0} # Only 6.0.0 supported for now - - -echo "Build for Capella version $CAPELLA_VERSION" - -# Clean tmp directory -mkdir -p /tmp/capella -cd /tmp/capella - -# Print maven version -mvn --version - -# Clone Capella Github repository -git clone https://github.com/eclipse/capella -cd capella -git checkout v$CAPELLA_VERSION -cd .. - -# Install JDK 11 -curl -L -o jdk11-linux.tar.gz https://api.adoptium.net/v3/binary/latest/11/ga/linux/aarch64/jdk/hotspot/normal/eclipse?project=jdk -tar xf jdk11-linux.tar.gz -export PATH=$(find /tmp/capella -depth 2 -name "bin"):${PATH} - -# Print java version -java -version - -cd capella - -# Modify pom.xml and inject architecture -python /opt/inject_architecture_into_pom.py - -# Build Capella -mvn clean verify -f releng/plugins/org.polarsys.capella.targets/pom.xml - -# Inject JRE 17 (Linux) -curl -L -o linuxJRE.tar.gz https://api.adoptium.net/v3/binary/latest/17/ga/linux/x64/jre/hotspot/normal/eclipse?project=jdk -mkdir -p linuxJRE/jre -tar -xf linuxJRE.tar.gz --strip-components 1 -C linuxJRE/jre - -# Inject JRE 17 (Linux) -curl -L -o linuxJRE-aarch64.tar.gz https://api.adoptium.net/v3/binary/latest/17/ga/linux/aarch64/jre/hotspot/normal/eclipse?project=jdk -mkdir -p linuxJRE-aarch64/jre -tar -xf linuxJRE-aarch64.tar.gz --strip-components 1 -C linuxJRE-aarch64/jre - -# Inject JRE 17 (macOS) -curl -L -o macJRE.tar.gz https://api.adoptium.net/v3/binary/latest/17/ga/mac/x64/jre/hotspot/normal/eclipse?project=jdk -mkdir -p macJRE/jre -tar -xf macJRE.tar.gz --strip-components 1 -C macJRE/jre - -# Inject JRE 17 (macOS) -curl -L -o macJRE-aarch64.tar.gz https://api.adoptium.net/v3/binary/latest/17/ga/mac/aarch64/jre/hotspot/normal/eclipse?project=jdk -mkdir -p macJRE-aarch64/jre -tar -xf macJRE-aarch64.tar.gz --strip-components 1 -C macJRE-aarch64/jre - -# Inject JRE 17 (Windows) -curl -L -o winJRE.zip https://api.adoptium.net/v3/binary/latest/17/ga/windows/x64/jre/hotspot/normal/eclipse?project=jdk -mkdir -p winJRE -unzip winJRE.zip -d winJRE/jre -mv winJRE/jre/*/* winJRE/jre - -mvn clean verify -f pom.xml -DjavaDocPhase=none -Pfull - -mv /tmp/capella/capella/releng/plugins/org.polarsys.capella.rcp.product/target/products /output diff --git a/builder/inject_architecture_into_pom.py b/builder/inject_architecture_into_pom.py deleted file mode 100644 index 3d80d9d0..00000000 --- a/builder/inject_architecture_into_pom.py +++ /dev/null @@ -1,116 +0,0 @@ -# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -# pylint: skip-file - - -import copy -import pathlib - -from lxml import etree -from lxml.builder import E - - -def patch_root_pom_xml() -> None: - pom_xml_path = pathlib.Path("pom.xml") - - root = etree.parse(pom_xml_path.open()).getroot() - environments = root.xpath("//*[local-name()='environments']")[0] - environments.append( - E.environment( - E.os("linux"), - E.ws("gtk"), - E.arch("aarch64"), - ) - ) - environments.append( - E.environment( - E.os("macosx"), - E.ws("cocoa"), - E.arch("aarch64"), - ) - ) - pom_xml_path.write_bytes(etree.tostring(root)) - - -def patch_releng_pom_xml() -> None: - pom_xml_path = pathlib.Path( - "releng/plugins/org.polarsys.capella.rcp.product/pom.xml" - ) - - root = etree.parse(pom_xml_path.open()).getroot() - - create_dropins(root) - package_product(root) - - pom_xml_path.write_bytes(etree.tostring(root)) - - -def create_dropins(root: etree._Element) -> None: - # We do the packaging ourselves and don't need antrun - dropins = root.xpath( - "//*[local-name()='execution' and ./*[local-name()='id']/text()='create-dropins']/*[local-name()='configuration']/*[local-name()='target']" - )[0] - dropins.append( - E.mkdir( - dir="${project.build.directory}/products/org.polarsys.capella.rcp.product/linux/gtk/aarch64/dropins" - ) - ) - dropins.append( - E.mkdir( - dir="${project.build.directory}/products/org.polarsys.capella.rcp.product/macosx/cocoa/aarch64/Capella.app/Contents/Eclipse/dropins" - ) - ) - - -def package_product(root: etree._Element) -> None: - target = root.xpath( - "//*[local-name()='execution' and ./*[local-name()='id']/text()='package-product']/*[local-name()='configuration']/*[local-name()='target']" - )[0] - - existing_packages = { - "linux": { - "move": "${project.build.directory}/products/org.polarsys.capella.rcp.product/linux/gtk/x86_64/jre", - "tar": "${project.build.directory}/products/capella-${unqualifiedVersion}.${buildQualifier}-linux-gtk-x86_64.tar.gz", - }, - "macos": { - "move": "${project.build.directory}/products/org.polarsys.capella.rcp.product/macosx/cocoa/x86_64/Capella.app/Contents/jre", - "tar": "${project.build.directory}/products/capella-${unqualifiedVersion}.${buildQualifier}-macosx-cocoa-x86_64.tar.gz", - }, - } - - for package in existing_packages.values(): - move_todir = package["move"] - move = target.xpath( - f"./*[local-name()='move' and ./@todir='{move_todir}']" - )[0] - - copied_move = copy.deepcopy(move) - copied_move.attrib["todir"] = move_todir.replace("x86_64", "aarch64") - fileset = copied_move.getchildren()[0] - fileset.attrib["dir"] = fileset.attrib["dir"].replace( - "JRE", "JRE-aarch64" - ) - - target.append(copied_move) - - tar_destfile = package["tar"] - tar = target.xpath( - f"./*[local-name()='tar' and ./@destfile='{tar_destfile}']" - )[0] - - copied_tar = copy.deepcopy(tar) - copied_tar.attrib["destfile"] = copied_tar.attrib["destfile"].replace( - "x86_64", "aarch64" - ) - for tarfileset in copied_tar.getchildren(): - tarfileset.attrib["dir"] = tarfileset.attrib["dir"].replace( - "x86_64", "aarch64" - ) - - target.append(copied_tar) - - -if __name__ == "__main__": - patch_root_pom_xml() - patch_releng_pom_xml() diff --git a/buildpacks/builder/build.Dockerfile b/buildpacks/builder/build.Dockerfile new file mode 100644 index 00000000..6b8f0674 --- /dev/null +++ b/buildpacks/builder/build.Dockerfile @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +# Define the base image +ARG BASE_IMAGE=debian:bookworm-slim +FROM $BASE_IMAGE + +USER root + +# Install packages that we want to make available at build time +RUN apt-get update && \ + apt-get install -y \ + xz-utils \ + ca-certificates \ + rsync \ + python3 \ + python3-pip \ + python3-venv && \ + rm -rf /var/lib/apt/lists/* + +# Set required CNB user information +ARG CNB_USER_ID=1000 +ARG CNB_GROUP_ID=1000 +ENV CNB_USER_ID=${CNB_USER_ID} +ENV CNB_GROUP_ID=${CNB_GROUP_ID} + +# Create user and group +RUN groupadd cnb --gid ${CNB_GROUP_ID} && \ + useradd -l --uid ${CNB_USER_ID} --gid ${CNB_GROUP_ID} -m -s /bin/bash cnb + +RUN ln -s "$(which python3.11)" /usr/bin/python && \ + ln -sf "$(which python3.11)" /usr/bin/python3 && \ + ln -sf "$(which pip3.11)" /usr/local/bin/pip && \ + ln -sf "$(which pip3.11)" /usr/local/bin/pip3 && \ + pip install --break-system-packages --no-cache-dir pre-commit lxml PyYAML + +# Set user and group +USER ${CNB_USER_ID}:${CNB_GROUP_ID} diff --git a/buildpacks/builder/builder.toml b/buildpacks/builder/builder.toml new file mode 100644 index 00000000..f7dfa630 --- /dev/null +++ b/buildpacks/builder/builder.toml @@ -0,0 +1,68 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +[[buildpacks]] +uri = "../buildpacks/pre-commit" + +[[buildpacks]] +uri = "../buildpacks/capella" + +[[buildpacks]] +uri = "../buildpacks/capella-dropins" + +[[buildpacks]] +uri = "../buildpacks/t4c" + +[[buildpacks]] +uri = "../buildpacks/supervisord" + +[[buildpacks]] +uri = "../buildpacks/xpra" + +[[buildpacks]] +uri = "../buildpacks/xrdp" + +[[buildpacks]] +uri = "../buildpacks/xidletime" + +[[extensions]] +uri = "../extensions/base" + +[[extensions]] +uri = "../extensions/capella-deps" + +[[extensions]] +uri = "../extensions/xpra-deps" + +[[extensions]] +uri = "../extensions/xrdp-deps" + +[[extensions]] +uri = "../extensions/xidletime-deps" + +[[order]] +group = [ + { id = "pre-commit", version = "0.0.0" }, + { id = "capella", version = "0.0.0" }, + { id = "capella-dropins", version = "0.0.0" }, + { id = "t4c", version = "0.0.0", optional = true }, + { id = "supervisord", version = "0.0.0", optional = true }, + { id = "xpra", version = "0.0.0", optional = true }, + { id = "xrdp", version = "0.0.0", optional = true }, + { id = "xidletime", version = "0.0.0", optional = true }, +] + +[[order-extensions]] +group = [ + { id = "base", version = "0.0.0" }, + { id = "capella-deps", version = "0.0.0" }, + { id = "xpra-deps", version = "0.0.0", optional = true }, + { id = "xrdp-deps", version = "0.0.0", optional = true }, + { id = "xidletime-deps", version = "0.0.0", optional = true }, +] + +[build] +image = "localhost:12345/buildpacks-base" +[run] +[[run.images]] +image = "localhost:12345/run-base" diff --git a/buildpacks/builder/run.Dockerfile b/buildpacks/builder/run.Dockerfile new file mode 100644 index 00000000..198cfe22 --- /dev/null +++ b/buildpacks/builder/run.Dockerfile @@ -0,0 +1,12 @@ + +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +ARG BASE_IMAGE=debian:bookworm-slim +FROM $BASE_IMAGE + +ARG TECHUSER_USER_ID=1001 +RUN useradd -l -m -u $TECHUSER_USER_ID techuser && chown techuser /home/techuser +ENV HOME=/home/techuser + +USER techuser diff --git a/buildpacks/buildpacks/capella-dropins/bin/build b/buildpacks/buildpacks/capella-dropins/bin/build new file mode 100755 index 00000000..62475df6 --- /dev/null +++ b/buildpacks/buildpacks/capella-dropins/bin/build @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail + +mkdir -p ${CNB_LAYERS_DIR}/app +cp -r /app/capella/versions/${CAPELLA_VERSION}/dropins/* ${CNB_LAYERS_DIR}/app/ + +# In case someone had dropins in their archive, copy it to the dropins layer +if [ -n "$(ls -A /layers/capella/app/dropins 2>/dev/null)" ]; then + cp -r "/layers/capella/app/dropins/"* ${CNB_LAYERS_DIR}/app/ +fi + +# Replace dropins folder with symbolic link to dropins layer +rm -rf /layers/capella/app/dropins +ln -s ${CNB_LAYERS_DIR}/app /layers/capella/app/dropins + +python3 /app/buildpacks/buildpacks/capella-dropins/install_dropins.py + +cat > "${CNB_LAYERS_DIR}/app.toml" << EOL +[types] +launch = true +EOL diff --git a/buildpacks/buildpacks/capella-dropins/bin/detect b/buildpacks/buildpacks/capella-dropins/bin/detect new file mode 100755 index 00000000..93686406 --- /dev/null +++ b/buildpacks/buildpacks/capella-dropins/bin/detect @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail + +# Don't cache if capella layer has changed. The symlink has to be updated! + +# Otherwise cache if hash of dropins has not changed. diff --git a/buildpacks/buildpacks/capella-dropins/buildpack.toml b/buildpacks/buildpacks/capella-dropins/buildpack.toml new file mode 100644 index 00000000..d27f39f9 --- /dev/null +++ b/buildpacks/buildpacks/capella-dropins/buildpack.toml @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +api = "0.11" + +[buildpack] +id = "capella-dropins" +version = "0.0.0" + +[[targets]] +os = "linux" diff --git a/capella/install_dropins.py b/buildpacks/buildpacks/capella-dropins/install_dropins.py similarity index 84% rename from capella/install_dropins.py rename to buildpacks/buildpacks/capella-dropins/install_dropins.py index 0aef7ef5..f31a3e3e 100644 --- a/capella/install_dropins.py +++ b/buildpacks/buildpacks/capella-dropins/install_dropins.py @@ -9,10 +9,14 @@ import yaml +CAPELLA_VERSION = os.environ["CAPELLA_VERSION"] + def load_dropins() -> dict[str, t.Any]: return yaml.safe_load( - pathlib.Path("/opt/dropins.yml").read_text(encoding="utf-8") + pathlib.Path( + "/app/capella/versions", CAPELLA_VERSION, "dropins.yml" + ).read_text(encoding="utf-8") )["dropins"] @@ -44,7 +48,7 @@ def extract_repositories_and_install_ius(dropins: dict[str, t.Any]) -> None: def install_update_sites(repository: str, install_ui: list[str]) -> None: subprocess.run( [ - "/opt/capella/capella", + "/layers/capella/app/capella", "-consoleLog", "-application", "org.eclipse.equinox.p2.director", @@ -60,7 +64,8 @@ def install_update_sites(repository: str, install_ui: list[str]) -> None: def download_and_copy_dropin(download_url: str, file_name: str) -> None: urllib.request.urlretrieve( - download_url, pathlib.Path("/opt/capella/dropins") / file_name + download_url, + pathlib.Path("/layers/capella/app/dropins") / file_name, ) diff --git a/buildpacks/buildpacks/capella/autostart b/buildpacks/buildpacks/capella/autostart new file mode 100755 index 00000000..f29160cd --- /dev/null +++ b/buildpacks/buildpacks/capella/autostart @@ -0,0 +1,17 @@ +#!/bin/bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +# Autostart script +export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + +if [ "$AUTOSTART_CAPELLA" = "1" ]; +then + if [ "$RESTART_CAPELLA" = "1" ]; + then + # Run capella in a loop: + ( while true; do /layers/capella/app/capella -data ${WORKSPACE_DIR:-/workspace} > /var/log/capella.stdout.log 2> /var/log/capella.stderr.log; sleep 1; done ) & + else + /layers/capella/app/capella -data ${WORKSPACE_DIR:-/workspace} > /var/log/capella.stdout.log 2> /var/log/capella.stderr.log & + fi +fi diff --git a/buildpacks/buildpacks/capella/bin/build b/buildpacks/buildpacks/capella/bin/build new file mode 100755 index 00000000..6be1b49c --- /dev/null +++ b/buildpacks/buildpacks/capella/bin/build @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail + +CAPELLA_BUILD_TYPE=${CAPELLA_BUILD_TYPE:-offline} +BUILD_ARCHITECTURE=$(dpkg --print-architecture) +PATH_TO_CAPELLA_DIRECTORY=/app/capella/versions/$CAPELLA_VERSION/$BUILD_ARCHITECTURE + +BUILDPACK_CAPELLA_DIR=/app/buildpacks/buildpacks/capella + +if [ "$CAPELLA_BUILD_TYPE" = "online" ]; then + python3 ${BUILDPACK_CAPELLA_DIR}/download_archive.py $CAPELLA_VERSION; +else + cp /app/capella/versions/$CAPELLA_VERSION/$BUILD_ARCHITECTURE/capella.tar.gz /tmp/capella.tar.gz +fi + +mkdir /tmp/capella; +tar -xf /tmp/capella.tar.gz -C /tmp/capella; + +# Remove samples directory from archive and move to layer +mkdir ${CNB_LAYERS_DIR}/app +mv /tmp/capella/capella/* ${CNB_LAYERS_DIR}/app; + +# Some older archives don't have the correct execute permissions. Fix that. +chmod +x ${CNB_LAYERS_DIR}/app/capella +chmod -R +x ${CNB_LAYERS_DIR}/app/jre/bin +chmod -R +x ${CNB_LAYERS_DIR}/app/jre/lib + +# Create Eclipse settings directory +mkdir -p ${CNB_LAYERS_DIR}/app/configuration/.settings; + +# Do not show WORKSPACE_SELECTION_DIALOG +echo "SHOW_WORKSPACE_SELECTION_DIALOG=false" >> ${CNB_LAYERS_DIR}/app/configuration/.settings/org.eclipse.ui.ide.prefs; + +# Install patches +PATCH_DIR=/app/capella/versions/$CAPELLA_VERSION/patches /app/capella/patch.sh + +# Increase timeout and retries for better stability +echo '-Dorg.eclipse.equinox.p2.transport.ecf.retry=15' >> /layers/capella/app/capella.ini +echo '-Dorg.eclipse.ecf.provider.filetransfer.retrieve.readTimeout=10000' >> /layers/capella/app/capella.ini + +mkdir -p ${CNB_LAYERS_DIR}/meta +cp /app/capella/git_askpass.py ${CNB_LAYERS_DIR}/meta + +cp ${BUILDPACK_CAPELLA_DIR}/entrypoint.sh ${CNB_LAYERS_DIR}/meta +cp ${BUILDPACK_CAPELLA_DIR}/autostart ${CNB_LAYERS_DIR}/meta + +mkdir -p ${CNB_LAYERS_DIR}/meta/exec.d +cp ${BUILDPACK_CAPELLA_DIR}/exec.d/* ${CNB_LAYERS_DIR}/meta/exec.d +cp /app/eclipse/set_memory_flags.py ${CNB_LAYERS_DIR}/meta/exec.d +chmod +x ${CNB_LAYERS_DIR}/meta/exec.d/* + +python3 -m venv ${CNB_LAYERS_DIR}/meta/venv +${CNB_LAYERS_DIR}/meta/venv/bin/pip install --no-cache-dir lxml + +# Set environment variables for build & run +mkdir -p ${CNB_LAYERS_DIR}/meta/env +echo -n "capella" > ${CNB_LAYERS_DIR}/meta/env/BASE_TYPE +echo -n "/layers/capella/app" > ${CNB_LAYERS_DIR}/meta/env/ECLIPSE_INSTALLATION_PATH +echo -n "/layers/capella/app/capella" > ${CNB_LAYERS_DIR}/meta/env/ECLIPSE_EXECUTABLE +echo -n "1" > ${CNB_LAYERS_DIR}/meta/env/AUTOSTART_CAPELLA +echo -n "1" > ${CNB_LAYERS_DIR}/meta/env/RESTART_CAPELLA + +cat > "${CNB_LAYERS_DIR}/meta.toml" << EOL +[types] +launch = true +EOL + +cat > "${CNB_LAYERS_DIR}/app.toml" << EOL +[types] +launch = true +EOL + +cat > "${CNB_LAYERS_DIR}/launch.toml" << EOL +[[processes]] +type = "capella" +command = ["${CNB_LAYERS_DIR}/meta/entrypoint.sh"] +default = true +EOL diff --git a/buildpacks/buildpacks/capella/bin/detect b/buildpacks/buildpacks/capella/bin/detect new file mode 100755 index 00000000..95bd67de --- /dev/null +++ b/buildpacks/buildpacks/capella/bin/detect @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail + +CAPELLA_BUILD_TYPE=${CAPELLA_BUILD_TYPE:-offline} +BUILD_ARCHITECTURE=$(dpkg --print-architecture) +PATH_TO_CAPELLA_DIRECTORY=/app/capella/versions/$CAPELLA_VERSION/$BUILD_ARCHITECTURE + +if [[ -z $CAPELLA_VERSION ]]; then + echo "CAPELLA_VERSION is not set." + exit 100 +fi + +if [[ -z $BUILD_ARCHITECTURE ]]; then + echo "BUILD_ARCHITECTURE is not set." + exit 100 +fi + +if [ "$BUILD_ARCHITECTURE" != "amd64" ] && [ "$BUILD_ARCHITECTURE" != "arm64" ]; then + echo "BUILD_ARCHITECTURE must be one of 'amd64' or 'arm64'. Is '$BUILD_ARCHITECTURE'." + exit 100 +fi + +# Validate CAPELLA_BUILD_TYPE +if [ "$CAPELLA_BUILD_TYPE" != "online" ] && [ "$CAPELLA_BUILD_TYPE" != "offline" ]; then + echo "CAPELLA_BUILD_TYPE must be one of 'online' or 'offline'." + exit 100 +fi + +# Check if build mode is online, architecture is x86_64 (aarch64 not supported) +if [ "$BUILD_ARCHITECTURE" = "amd64" ] && [ "$CAPELLA_BUILD_TYPE" = "online" ]; then + echo "Online is not supported for arm64." + exit 100 +fi + +# Check if build mode is online or capella.tar.gz exists +if [ "$CAPELLA_BUILD_TYPE" = "offline" ] && [[ ! -f $PATH_TO_CAPELLA_DIRECTORY/capella.tar.gz ]]; then + echo "Couldn't find capella/versions/$CAPELLA_VERSION/$BUILD_ARCHITECTURE/capella.tar.gz." + echo "Add the mentioned file or use '--env CAPELLA_BUILD_TYPE=online'." + exit 100 +fi + +# Check if CAPELLA_FORCE_REDOWNLOAD=1 is set + +# Validate the checksum of the capella.tar.gz diff --git a/buildpacks/buildpacks/capella/buildpack.toml b/buildpacks/buildpacks/capella/buildpack.toml new file mode 100644 index 00000000..2b3f7914 --- /dev/null +++ b/buildpacks/buildpacks/capella/buildpack.toml @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +api = "0.11" + +[buildpack] +id = "capella" +version = "0.0.0" + +[[targets]] +os = "linux" diff --git a/capella/download_archive.py b/buildpacks/buildpacks/capella/download_archive.py similarity index 96% rename from capella/download_archive.py rename to buildpacks/buildpacks/capella/download_archive.py index 94cbb1c1..2a4b3f95 100644 --- a/capella/download_archive.py +++ b/buildpacks/buildpacks/capella/download_archive.py @@ -57,6 +57,6 @@ def get_directory_structure(url: str) -> list[str]: download_response = requests.get(download_url) download_response.raise_for_status() - download_path = pathlib.Path("/opt/capella.tar.gz").write_bytes( + download_path = pathlib.Path("/tmp/capella.tar.gz").write_bytes( download_response.content ) diff --git a/remote/bg-saved.cfg b/buildpacks/buildpacks/capella/entrypoint.sh old mode 100644 new mode 100755 similarity index 65% rename from remote/bg-saved.cfg rename to buildpacks/buildpacks/capella/entrypoint.sh index 72f2f872..81631f19 --- a/remote/bg-saved.cfg +++ b/buildpacks/buildpacks/capella/entrypoint.sh @@ -1,7 +1,5 @@ +#!/bin/bash # SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 -[xin_-1] -file=/tmp/wallpaper.png -mode=4 -bgcolor=#000000 +xvfb-run /layers/capella/app/capella "$@" diff --git a/capella/setup/provisioning.py b/buildpacks/buildpacks/capella/exec.d/provisioning.py old mode 100644 new mode 100755 similarity index 98% rename from capella/setup/provisioning.py rename to buildpacks/buildpacks/capella/exec.d/provisioning.py index 7d2c12e5..fb4d60d0 --- a/capella/setup/provisioning.py +++ b/buildpacks/buildpacks/capella/exec.d/provisioning.py @@ -1,3 +1,5 @@ +#!/layers/capella/meta/venv/bin/python + # SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 """Prepare models in the $MODEL_INBOX_DIRECTORIES for Capella.""" @@ -165,9 +167,7 @@ def provide_project_dirs_to_capella_plugin( projects: list[_ProjectDict], ) -> None: locations = ":".join([str(project["location"]) for project in projects]) - pathlib.Path("/etc/environment").write_text( - f"export MODEL_INBOX_DIRECTORIES={locations}\n", encoding="utf-8" - ) + os.write(3, f'MODEL_INBOX_DIRECTORIES="{locations}"\n'.encode()) log.info( "Set environment variable MODEL_INBOX_DIRECTORIES to '%s'", locations ) diff --git a/buildpacks/buildpacks/capella/exec.d/set_autostart.sh b/buildpacks/buildpacks/capella/exec.d/set_autostart.sh new file mode 100755 index 00000000..e31cb71a --- /dev/null +++ b/buildpacks/buildpacks/capella/exec.d/set_autostart.sh @@ -0,0 +1,8 @@ +#!/bin/bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail + +mkdir -p /home/techuser/.config/openbox +ln -s /layers/capella/meta/autostart /home/techuser/.config/openbox/autostart diff --git a/capella/setup/setup_workspace.py b/buildpacks/buildpacks/capella/exec.d/setup_workspace.py old mode 100644 new mode 100755 similarity index 99% rename from capella/setup/setup_workspace.py rename to buildpacks/buildpacks/capella/exec.d/setup_workspace.py index eac967b6..eafbf4a6 --- a/capella/setup/setup_workspace.py +++ b/buildpacks/buildpacks/capella/exec.d/setup_workspace.py @@ -1,3 +1,5 @@ +#!/usr/bin/python + # SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 diff --git a/buildpacks/buildpacks/jupyter/bin/build b/buildpacks/buildpacks/jupyter/bin/build new file mode 100755 index 00000000..62475df6 --- /dev/null +++ b/buildpacks/buildpacks/jupyter/bin/build @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail + +mkdir -p ${CNB_LAYERS_DIR}/app +cp -r /app/capella/versions/${CAPELLA_VERSION}/dropins/* ${CNB_LAYERS_DIR}/app/ + +# In case someone had dropins in their archive, copy it to the dropins layer +if [ -n "$(ls -A /layers/capella/app/dropins 2>/dev/null)" ]; then + cp -r "/layers/capella/app/dropins/"* ${CNB_LAYERS_DIR}/app/ +fi + +# Replace dropins folder with symbolic link to dropins layer +rm -rf /layers/capella/app/dropins +ln -s ${CNB_LAYERS_DIR}/app /layers/capella/app/dropins + +python3 /app/buildpacks/buildpacks/capella-dropins/install_dropins.py + +cat > "${CNB_LAYERS_DIR}/app.toml" << EOL +[types] +launch = true +EOL diff --git a/buildpacks/buildpacks/jupyter/bin/detect b/buildpacks/buildpacks/jupyter/bin/detect new file mode 100755 index 00000000..93686406 --- /dev/null +++ b/buildpacks/buildpacks/jupyter/bin/detect @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail + +# Don't cache if capella layer has changed. The symlink has to be updated! + +# Otherwise cache if hash of dropins has not changed. diff --git a/buildpacks/buildpacks/jupyter/buildpack.toml b/buildpacks/buildpacks/jupyter/buildpack.toml new file mode 100644 index 00000000..d644fec2 --- /dev/null +++ b/buildpacks/buildpacks/jupyter/buildpack.toml @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +api = "0.11" + +[buildpack] +id = "jupyter" +version = "0.0.0" + +[[targets]] +os = "linux" diff --git a/buildpacks/buildpacks/pre-commit/bin/build b/buildpacks/buildpacks/pre-commit/bin/build new file mode 100755 index 00000000..8ea910d9 --- /dev/null +++ b/buildpacks/buildpacks/pre-commit/bin/build @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail + +# Set environment variables for build & run +mkdir -p ${CNB_LAYERS_DIR}/hooks + +cp /app/buildpacks/buildpacks/pre-commit/hooks/* ${CNB_LAYERS_DIR}/hooks + +echo "commit-msg post-rewrite pre-commit pre-merge-commit pre-rebase prepare-commit-msg" | xargs -n 1 cp ${CNB_LAYERS_DIR}/hooks/+pre-commit-only.sh && \ +echo "pre-push post-checkout post-commit post-merge" | xargs -n 1 cp ${CNB_LAYERS_DIR}/hooks/+pre-commit-and-lfs.sh && \ + +mkdir -p ${CNB_LAYERS_DIR}/hooks/env +echo -n "/workspace/.pre-commit" > ${CNB_LAYERS_DIR}/hooks/env/PRE_COMMIT_HOME + +cat > "${CNB_LAYERS_DIR}/hooks.toml" << EOL +[types] +launch = true +EOL diff --git a/buildpacks/buildpacks/pre-commit/bin/detect b/buildpacks/buildpacks/pre-commit/bin/detect new file mode 100755 index 00000000..2491109f --- /dev/null +++ b/buildpacks/buildpacks/pre-commit/bin/detect @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail diff --git a/buildpacks/buildpacks/pre-commit/buildpack.toml b/buildpacks/buildpacks/pre-commit/buildpack.toml new file mode 100644 index 00000000..b32a62a6 --- /dev/null +++ b/buildpacks/buildpacks/pre-commit/buildpack.toml @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +api = "0.11" + +[buildpack] +id = "pre-commit" +version = "0.0.0" + +[[targets]] +os = "linux" diff --git a/base/hooks/+pre-commit-and-lfs.sh b/buildpacks/buildpacks/pre-commit/hooks/+pre-commit-and-lfs.sh similarity index 100% rename from base/hooks/+pre-commit-and-lfs.sh rename to buildpacks/buildpacks/pre-commit/hooks/+pre-commit-and-lfs.sh diff --git a/base/hooks/+pre-commit-only.sh b/buildpacks/buildpacks/pre-commit/hooks/+pre-commit-only.sh similarity index 100% rename from base/hooks/+pre-commit-only.sh rename to buildpacks/buildpacks/pre-commit/hooks/+pre-commit-only.sh diff --git a/base/hooks/+pre-commit.sh b/buildpacks/buildpacks/pre-commit/hooks/+pre-commit.sh similarity index 91% rename from base/hooks/+pre-commit.sh rename to buildpacks/buildpacks/pre-commit/hooks/+pre-commit.sh index d79f4466..052f6bc3 100755 --- a/base/hooks/+pre-commit.sh +++ b/buildpacks/buildpacks/pre-commit/hooks/+pre-commit.sh @@ -1,4 +1,7 @@ #!/bin/bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + # SPDX-FileCopyrightText: Copyright (c) 2014 pre-commit dev team: Anthony Sottile, Ken Struys # SPDX-License-Identifier: MIT diff --git a/buildpacks/buildpacks/supervisord/bin/build b/buildpacks/buildpacks/supervisord/bin/build new file mode 100755 index 00000000..5095abc8 --- /dev/null +++ b/buildpacks/buildpacks/supervisord/bin/build @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail + +mkdir -p ${CNB_LAYERS_DIR}/app + +python3 -m venv ${CNB_LAYERS_DIR}/app/venv +${CNB_LAYERS_DIR}/app/venv/bin/pip install --no-cache-dir supervisor + +cat > "${CNB_LAYERS_DIR}/app/supervisord.conf" << EOL +[supervisord] +nodaemon=true +childlogdir=/layers/supervisord +EOL + +cat > "${CNB_LAYERS_DIR}/app.toml" << EOL +[types] +launch = true +EOL + +cat > "${CNB_LAYERS_DIR}/launch.toml" << EOL +[[processes]] +type = "supervisord" +command = ["${CNB_LAYERS_DIR}/app/venv/bin/supervisord", "-c", "${CNB_LAYERS_DIR}/app/supervisord.conf"] +default = true +EOL diff --git a/buildpacks/buildpacks/supervisord/bin/detect b/buildpacks/buildpacks/supervisord/bin/detect new file mode 100755 index 00000000..2491109f --- /dev/null +++ b/buildpacks/buildpacks/supervisord/bin/detect @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail diff --git a/buildpacks/buildpacks/supervisord/buildpack.toml b/buildpacks/buildpacks/supervisord/buildpack.toml new file mode 100644 index 00000000..bfb20f68 --- /dev/null +++ b/buildpacks/buildpacks/supervisord/buildpack.toml @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +api = "0.11" + +[buildpack] +id = "supervisord" +version = "0.0.0" +description = "Install the supervisord process manager." + +[[targets]] +os = "linux" diff --git a/buildpacks/buildpacks/t4c/bin/build b/buildpacks/buildpacks/t4c/bin/build new file mode 100755 index 00000000..cd7fc602 --- /dev/null +++ b/buildpacks/buildpacks/t4c/bin/build @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail + +mkdir -p ${CNB_LAYERS_DIR}/meta +cp /app/buildpacks/buildpacks/t4c/entrypoint.sh ${CNB_LAYERS_DIR}/meta + +mkdir -p ${CNB_LAYERS_DIR}/meta/exec.d +cp /app/buildpacks/buildpacks/t4c/exec.d/* ${CNB_LAYERS_DIR}/meta/exec.d +chmod +x ${CNB_LAYERS_DIR}/meta/exec.d/* + +# Compute a diff (all files created when installing the T4C extension) +capella_layer=/layers/capella/app +capella_tmp_layer=$(mktemp -d) +t4c_diff_layer=${CNB_LAYERS_DIR}/app + +# Snapshot before installation of T4C extension +rsync -a ${capella_layer}/ ${capella_tmp_layer}/ + +# Install T4C client +T4C_ZIP=$(find /app/t4c/updateSite/$CAPELLA_VERSION -type f -iname "com.thalesgroup.mde.melody.team.license.update-*.zip" | head -n 1); \ +${capella_tmp_layer}/capella \ + -clean \ + -data $(mktemp -d) \ + -consoleLog \ + -application org.eclipse.equinox.p2.director \ + -noSplash \ + -repository jar:file://${T4C_ZIP}!/ \ + -installIU com.thalesgroup.mde.melody.collab.feature.feature.group,com.thalesgroup.mde.melody.collab.maintenance.feature.feature.group,com.thalesgroup.mde.melody.collab.licbranding.feature.feature.group + +# Install patches +PATCH_DIR=/app/t4c/updateSite/$CAPELLA_VERSION /app/capella/patch.sh + +# Compute diff +rsync -a --compare-dest=${capella_layer}/ ${capella_tmp_layer}/ ${t4c_diff_layer}/ + +# Install T4C CLI +mkdir -p ${CNB_LAYERS_DIR}/cli +python3 -m venv ${CNB_LAYERS_DIR}/cli + +cat > "${CNB_LAYERS_DIR}/app.toml" << EOL +[types] +launch = true +EOL + +cat > "${CNB_LAYERS_DIR}/meta.toml" << EOL +[types] +launch = true +EOL + +cat > "${CNB_LAYERS_DIR}/cli.toml" << EOL +[types] +launch = true +EOL + +cat > "${CNB_LAYERS_DIR}/launch.toml" << EOL +[[processes]] +type = "t4c" +command = ["${CNB_LAYERS_DIR}/meta/entrypoint.sh"] +default = true +EOL diff --git a/buildpacks/buildpacks/t4c/bin/detect b/buildpacks/buildpacks/t4c/bin/detect new file mode 100755 index 00000000..1da33548 --- /dev/null +++ b/buildpacks/buildpacks/t4c/bin/detect @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail + +shopt -s nullglob + +matches=(/app/t4c/updateSite/$CAPELLA_VERSION/com.thalesgroup.mde.melody.team.license.update-*.zip) +count_matches=${#matches[@]} +if [ $count_matches -lt 1 ]; then + echo "Couldn't find com.thalesgroup.mde.melody.team.license.update-*.zip in t4c/updateSite/$CAPELLA_VERSION." + exit 1 +elif [ $count_matches -gt 1 ]; then + echo "Found more than one com.thalesgroup.mde.melody.team.license.update-*.zip in t4c/updateSite/$CAPELLA_VERSION:" + echo $matches + exit 1 +fi diff --git a/buildpacks/buildpacks/t4c/buildpack.toml b/buildpacks/buildpacks/t4c/buildpack.toml new file mode 100644 index 00000000..d8b93288 --- /dev/null +++ b/buildpacks/buildpacks/t4c/buildpack.toml @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +api = "0.11" + +[buildpack] +id = "t4c" +version = "0.0.0" + +[[targets]] +os = "linux" diff --git a/t4c/docker_entrypoint.sh b/buildpacks/buildpacks/t4c/entrypoint.sh similarity index 69% rename from t4c/docker_entrypoint.sh rename to buildpacks/buildpacks/t4c/entrypoint.sh index e2da1d68..862f8491 100755 --- a/t4c/docker_entrypoint.sh +++ b/buildpacks/buildpacks/t4c/entrypoint.sh @@ -7,15 +7,15 @@ set -euo pipefail case ${1:-startup} in backup) - xvfb-run /opt/t4c_cli/.venv/bin/backup + xvfb-run /layers/t4c/cli/bin/backup ;; export) - xvfb-run /opt/t4c_cli/.venv/bin/exporter + xvfb-run /layers/t4c/cli/bin/exporter ;; startup) - /startup.sh + /layers/capella/meta/entrypoint.sh ;; *) diff --git a/buildpacks/buildpacks/t4c/exec.d/inject_in_capella_ini.sh b/buildpacks/buildpacks/t4c/exec.d/inject_in_capella_ini.sh new file mode 100755 index 00000000..59e884a4 --- /dev/null +++ b/buildpacks/buildpacks/t4c/exec.d/inject_in_capella_ini.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +cat >> /layers/capella/app/capella.ini < None: diff --git a/buildpacks/buildpacks/xidletime/bin/build b/buildpacks/buildpacks/xidletime/bin/build new file mode 100755 index 00000000..44175764 --- /dev/null +++ b/buildpacks/buildpacks/xidletime/bin/build @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail + +cat >> "/layers/supervisord/app/supervisord.conf" << EOL +[program:idletime] +command=/layers/xidletime/app/venv/bin/python /layers/xidletime/app/metrics.py +user=techuser +autorestart=true +environment=DISPLAY=":10" +EOL + +mkdir -p ${CNB_LAYERS_DIR}/app + +python3 -m venv ${CNB_LAYERS_DIR}/app/venv +${CNB_LAYERS_DIR}/app/venv/bin/pip install --no-cache-dir prometheus-client + +cp /app/buildpacks/buildpacks/xidletime/metrics.py ${CNB_LAYERS_DIR}/app/metrics.py + +cat > "${CNB_LAYERS_DIR}/app.toml" << EOL +[types] +launch = true +EOL diff --git a/buildpacks/buildpacks/xidletime/bin/detect b/buildpacks/buildpacks/xidletime/bin/detect new file mode 100755 index 00000000..2491109f --- /dev/null +++ b/buildpacks/buildpacks/xidletime/bin/detect @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail diff --git a/buildpacks/buildpacks/xidletime/buildpack.toml b/buildpacks/buildpacks/xidletime/buildpack.toml new file mode 100644 index 00000000..2887806e --- /dev/null +++ b/buildpacks/buildpacks/xidletime/buildpack.toml @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +api = "0.11" + +[buildpack] +id = "xidletime" +version = "0.0.0" +description = "Measure the idle time of the XServer and expose it via a metrics endpoint." + +[[targets]] +os = "linux" diff --git a/remote/metrics.py b/buildpacks/buildpacks/xidletime/metrics.py similarity index 100% rename from remote/metrics.py rename to buildpacks/buildpacks/xidletime/metrics.py diff --git a/buildpacks/buildpacks/xpra/bin/build b/buildpacks/buildpacks/xpra/bin/build new file mode 100755 index 00000000..235edf87 --- /dev/null +++ b/buildpacks/buildpacks/xpra/bin/build @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail + +mkdir -p ${CNB_LAYERS_DIR}/app/exec.d +cp /app/buildpacks/buildpacks/xpra/exec.d/* ${CNB_LAYERS_DIR}/app/exec.d +chmod +x ${CNB_LAYERS_DIR}/app/exec.d/* + +cp /app/buildpacks/buildpacks/xpra/nginx.conf ${CNB_LAYERS_DIR}/app/nginx.conf +mkdir -p ${CNB_LAYERS_DIR}/app/html +cp /app/buildpacks/buildpacks/xpra/error.html ${CNB_LAYERS_DIR}/app/html/error.html + +cat > "${CNB_LAYERS_DIR}/app.toml" << EOL +[types] +launch = true +EOL diff --git a/buildpacks/buildpacks/xpra/bin/detect b/buildpacks/buildpacks/xpra/bin/detect new file mode 100755 index 00000000..2491109f --- /dev/null +++ b/buildpacks/buildpacks/xpra/bin/detect @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail diff --git a/buildpacks/buildpacks/xpra/buildpack.toml b/buildpacks/buildpacks/xpra/buildpack.toml new file mode 100644 index 00000000..a556a74e --- /dev/null +++ b/buildpacks/buildpacks/xpra/buildpack.toml @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +api = "0.11" + +[buildpack] +id = "xpra" +version = "0.0.0" +description = "Install xpra-html5 to run X11 applications in a browser." + +[[targets]] +os = "linux" diff --git a/remote/error.html b/buildpacks/buildpacks/xpra/error.html similarity index 100% rename from remote/error.html rename to buildpacks/buildpacks/xpra/error.html diff --git a/remote/supervisord.xpra.conf b/buildpacks/buildpacks/xpra/exec.d/add_supervisord_config.sh old mode 100644 new mode 100755 similarity index 72% rename from remote/supervisord.xpra.conf rename to buildpacks/buildpacks/xpra/exec.d/add_supervisord_config.sh index e43d4a4a..54887469 --- a/remote/supervisord.xpra.conf +++ b/buildpacks/buildpacks/xpra/exec.d/add_supervisord_config.sh @@ -1,6 +1,10 @@ +#!/bin/bash + # SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +if [ "${CONNECTION_METHOD:-xrdp}" == "xpra" ]; then + cat >> "/layers/supervisord/app/supervisord.conf" << EOL [program:xpra] command=xpra start :10 --start=/home/techuser/.config/openbox/autostart --start-env=GTK_IM_MODULE=ibus --attach=yes --daemon=no --bind-tcp=0.0.0.0:10001 --min-quality=70 user=techuser @@ -8,6 +12,8 @@ autorestart=true environment=DISPLAY=":10",XPRA_DEFAULT_CONTENT_TYPE="text",XPRA_DEFAULT_VFB_RESOLUTION="1920x1080" [program:nginx] -command=nginx +command=nginx -c /layers/xpra/app/nginx.conf user=techuser autorestart=true +EOL +fi diff --git a/buildpacks/buildpacks/xpra/exec.d/replace_nginx_vars.sh b/buildpacks/buildpacks/xpra/exec.d/replace_nginx_vars.sh new file mode 100755 index 00000000..4daed0ce --- /dev/null +++ b/buildpacks/buildpacks/xpra/exec.d/replace_nginx_vars.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +# Replace Variables in the nginx.conf +sed -i "s|__XPRA_SUBPATH__|${XPRA_SUBPATH:-/}|g" /layers/xpra/app/nginx.conf +sed -i "s|__XPRA_CSP_ORIGIN_HOST__|${XPRA_CSP_ORIGIN_HOST:-}|g" /layers/xpra/app/nginx.conf diff --git a/remote/nginx.conf b/buildpacks/buildpacks/xpra/nginx.conf similarity index 96% rename from remote/nginx.conf rename to buildpacks/buildpacks/xpra/nginx.conf index 371ae1cc..1116f14a 100644 --- a/remote/nginx.conf +++ b/buildpacks/buildpacks/xpra/nginx.conf @@ -16,7 +16,7 @@ http { listen 10000; server_name _; - root /usr/share/nginx/html; + root /layers/xpra/app/html; error_page 502 /error.html; error_page 404 /error.html; diff --git a/buildpacks/buildpacks/xrdp/bin/build b/buildpacks/buildpacks/xrdp/bin/build new file mode 100755 index 00000000..70203bfd --- /dev/null +++ b/buildpacks/buildpacks/xrdp/bin/build @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail + +mkdir -p ${CNB_LAYERS_DIR}/app/exec.d +cp /app/buildpacks/buildpacks/xrdp/exec.d/* ${CNB_LAYERS_DIR}/app/exec.d +chmod +x ${CNB_LAYERS_DIR}/app/exec.d/* + +mkdir -p ${CNB_LAYERS_DIR}/app +cp /app/buildpacks/buildpacks/xrdp/rc.xml ${CNB_LAYERS_DIR}/app/rc.xml +cp /app/buildpacks/buildpacks/xrdp/menu.xml ${CNB_LAYERS_DIR}/app/menu.xml + +cat > "${CNB_LAYERS_DIR}/app.toml" << EOL +[types] +launch = true +EOL diff --git a/buildpacks/buildpacks/xrdp/bin/detect b/buildpacks/buildpacks/xrdp/bin/detect new file mode 100755 index 00000000..2491109f --- /dev/null +++ b/buildpacks/buildpacks/xrdp/bin/detect @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail diff --git a/buildpacks/buildpacks/xrdp/buildpack.toml b/buildpacks/buildpacks/xrdp/buildpack.toml new file mode 100644 index 00000000..fb866c8d --- /dev/null +++ b/buildpacks/buildpacks/xrdp/buildpack.toml @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +api = "0.11" + +[buildpack] +id = "xrdp" +version = "0.0.0" +description = "Install xrdp to expose X11 applications via RDP." + +[[targets]] +os = "linux" diff --git a/remote/supervisord.xrdp.conf b/buildpacks/buildpacks/xrdp/exec.d/add_supervisord_config.sh old mode 100644 new mode 100755 similarity index 66% rename from remote/supervisord.xrdp.conf rename to buildpacks/buildpacks/xrdp/exec.d/add_supervisord_config.sh index 384c7d9c..64cf35e2 --- a/remote/supervisord.xrdp.conf +++ b/buildpacks/buildpacks/xrdp/exec.d/add_supervisord_config.sh @@ -1,13 +1,20 @@ +#!/bin/bash + # SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 +if [ "${CONNECTION_METHOD:-xrdp}" == "xrdp" ]; then + cat >> "/layers/supervisord/app/supervisord.conf" << EOL [program:xrdp] command=/usr/sbin/xrdp --nodaemon user=techuser autorestart=true [program:xrdp-sesman] +directory=/home/techuser command=/usr/sbin/xrdp-sesman --nodaemon user=techuser autorestart=true environment=DISPLAY=":10" +EOL +fi diff --git a/buildpacks/buildpacks/xrdp/exec.d/copy_openbox_config.sh b/buildpacks/buildpacks/xrdp/exec.d/copy_openbox_config.sh new file mode 100755 index 00000000..81c36eb2 --- /dev/null +++ b/buildpacks/buildpacks/xrdp/exec.d/copy_openbox_config.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail + +cp /layers/xrdp/app/rc.xml /etc/xdg/openbox/rc.xml +cp /layers/xrdp/app/menu.xml /etc/xdg/openbox/menu.xml diff --git a/buildpacks/buildpacks/xrdp/exec.d/set_password.sh b/buildpacks/buildpacks/xrdp/exec.d/set_password.sh new file mode 100755 index 00000000..83a7f129 --- /dev/null +++ b/buildpacks/buildpacks/xrdp/exec.d/set_password.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail + +if [ "${CONNECTION_METHOD:-xrdp}" == "xrdp" ]; +then + if [ "$(whoami)" == "root" ] || [ "$(whoami)" == "techuser" ]; + then + salt=$(openssl rand -base64 16) + password_hash=$(openssl passwd -6 -salt ${salt} "${RMT_PASSWORD:?}") + line=$(grep techuser /etc/shadow); + echo ${line%%:*}:${password_hash}:${line#*:*:} > /etc/shadow; + else + echo "Only techuser and root are supported as users."; + exit 1; + fi +fi diff --git a/remote/menu.xml b/buildpacks/buildpacks/xrdp/menu.xml similarity index 100% rename from remote/menu.xml rename to buildpacks/buildpacks/xrdp/menu.xml diff --git a/remote/rc.xml b/buildpacks/buildpacks/xrdp/rc.xml similarity index 100% rename from remote/rc.xml rename to buildpacks/buildpacks/xrdp/rc.xml diff --git a/buildpacks/extensions/base/bin/detect b/buildpacks/extensions/base/bin/detect new file mode 100755 index 00000000..2491109f --- /dev/null +++ b/buildpacks/extensions/base/bin/detect @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail diff --git a/buildpacks/extensions/base/bin/generate b/buildpacks/extensions/base/bin/generate new file mode 100755 index 00000000..84980faa --- /dev/null +++ b/buildpacks/extensions/base/bin/generate @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail + +cat >> "${CNB_OUTPUT_DIR}/run.Dockerfile" <> /etc/locale.gen && \ + locale-gen en_GB.UTF-8 && \ + update-locale en_GB.UTF-8 && \ + dpkg-reconfigure --frontend=noninteractive locales && \ + echo "LANG=en_GB.UTF-8" >> /etc/default/locale && \ + echo "LANGUAGE=en_GB:en" >> /etc/default/locale && \ + echo "LC_ALL=en_GB.UTF-8" >> /etc/default/locale +ENV LANG="en_GB.UTF-8" +ENV LANGUAGE="en_GB:en" +ENV LC_ALL="en_GB.UTF-8" + +RUN ln -s "\$(which python3.11)" /usr/bin/python && \ + ln -sf "\$(which python3.11)" /usr/bin/python3 && \ + ln -sf "\$(which pip3.11)" /usr/local/bin/pip && \ + ln -sf "\$(which pip3.11)" /usr/local/bin/pip3 && \ + git config --global core.hooksPath /layers/pre-commit/hooks + +ENV WORKSPACE_DIR=/workspace + +ARG user_id +USER \${user_id} +EOL diff --git a/buildpacks/extensions/base/extension.toml b/buildpacks/extensions/base/extension.toml new file mode 100644 index 00000000..61639da9 --- /dev/null +++ b/buildpacks/extensions/base/extension.toml @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +api = "0.11" + +[extension] +id = "base" +name = "Basic setup" +version = "0.0.0" diff --git a/buildpacks/extensions/capella-deps/bin/detect b/buildpacks/extensions/capella-deps/bin/detect new file mode 100755 index 00000000..2491109f --- /dev/null +++ b/buildpacks/extensions/capella-deps/bin/detect @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail diff --git a/buildpacks/extensions/capella-deps/bin/generate b/buildpacks/extensions/capella-deps/bin/generate new file mode 100755 index 00000000..75a5e3fa --- /dev/null +++ b/buildpacks/extensions/capella-deps/bin/generate @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +set -eo pipefail + +cat >> "${CNB_OUTPUT_DIR}/run.Dockerfile" <> "${CNB_OUTPUT_DIR}/run.Dockerfile" +fi + +cat >> "${CNB_OUTPUT_DIR}/run.Dockerfile" <> "${CNB_OUTPUT_DIR}/run.Dockerfile" <> "${CNB_OUTPUT_DIR}/extend-config.toml" <> "${CNB_OUTPUT_DIR}/run.Dockerfile" <> "${CNB_OUTPUT_DIR}/run.Dockerfile" < /etc/X11/Xwrapper.config && \ + chmod 666 /etc/shadow && \ + mkdir -p /run/xrdp/sockdir && \ + chown -R techuser /etc/xrdp /run/xrdp /var/log/xrdp* /etc/xdg/openbox + +ARG user_id +USER \${user_id} +EOL diff --git a/buildpacks/extensions/xrdp-deps/extension.toml b/buildpacks/extensions/xrdp-deps/extension.toml new file mode 100644 index 00000000..250662e2 --- /dev/null +++ b/buildpacks/extensions/xrdp-deps/extension.toml @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +api = "0.11" + +[extension] +id = "xrdp-deps" +name = "Xrdp system dependencies" +version = "0.0.0" diff --git a/capella/.dockerignore.template b/capella/.dockerignore.template deleted file mode 100644 index 851fa735..00000000 --- a/capella/.dockerignore.template +++ /dev/null @@ -1,7 +0,0 @@ -# Please only edit the .dockerignore.template file! -# The .dockerignore file is gitignored and updated by the Makefile -# Environment variables are replaced before build with `envsubst` - -.dockerignore.template -versions/* -!versions/$CAPELLA_VERSION diff --git a/capella/.dockerignore.template.license b/capella/.dockerignore.template.license deleted file mode 100644 index 7ea22469..00000000 --- a/capella/.dockerignore.template.license +++ /dev/null @@ -1,2 +0,0 @@ -SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors -SPDX-License-Identifier: Apache-2.0 diff --git a/capella/Dockerfile b/capella/Dockerfile deleted file mode 100644 index b3945406..00000000 --- a/capella/Dockerfile +++ /dev/null @@ -1,174 +0,0 @@ -# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -ARG BASE_IMAGE=base -ARG BUILD_TYPE=online -ARG INSTALL_OLD_GTK_VERSION=true -FROM ${BASE_IMAGE} as base - -ENV DEBIAN_FRONTEND=noninteractive - -SHELL ["/bin/bash", "-euo", "pipefail", "-c"] -ENV SHELL=/bin/bash - -FROM base as old_gtk_true - -ONBUILD USER root - -# Install WebKit with GTK -ONBUILD COPY libs /tmp/libs -ONBUILD ARG INJECT_PACKAGES=false -# hadolint ignore=SC2046 -ONBUILD RUN if [ "$INJECT_PACKAGES" = "true" ]; then \ - apt-get update && \ - # Inject old packages manually - apt-get install -y $(find /tmp/libs -iname "*.deb"); \ - rm -rf /var/lib/apt/lists/*; \ - rm -r /tmp/libs; \ - else \ - # Download old packages from the Ubuntu focal registry - ## Add source - echo "deb http://de.archive.ubuntu.com/ubuntu/ focal main" >> /etc/apt/sources.list.d/focal.list; \ - ## Import the required keys - apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3B4FE6ACC0B21F32; \ - apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 871920D1991BC93C; \ - apt-get update; \ - ## Install the packages - apt-get install -y \ - libjavascriptcoregtk-4.0-18=2.28.1-1 \ - libwebkit2gtk-4.0-37=2.28.1-1; \ - rm /etc/apt/sources.list.d/focal.list; \ - fi; \ - rm -rf /var/lib/apt/lists/*; - -FROM base as old_gtk_false - -ONBUILD USER root -ONBUILD COPY libs /tmp/libs -ONBUILD RUN apt-get update && \ - apt-get install -y \ - libjavascriptcoregtk-4.0-18 \ - libwebkit2gtk-4.0-37 && \ - rm -rf /var/lib/apt/lists/*; - -FROM old_gtk_${INSTALL_OLD_GTK_VERSION} as base_new - -WORKDIR /opt/ - -FROM base_new as build_online -# Download a Capella executable archive - -# https://github.com/moby/moby/issues/26533#issuecomment-246966836 -ONBUILD ARG CAPELLA_VERSION="5.2.0" -ONBUILD ARG BUILD_ARCHITECTURE="amd64" -ONBUILD COPY ./download_archive.py /opt/.download_archive.py -ONBUILD RUN pip install --no-cache-dir requests && \ - python .download_archive.py ${CAPELLA_VERSION}; - -FROM base_new as build_offline - -# ONBUILD is required here -# https://github.com/moby/moby/issues/26533#issuecomment-246966836 -ONBUILD ARG CAPELLA_VERSION="5.2.0" -ONBUILD ARG BUILD_ARCHITECTURE="amd" -ONBUILD COPY ./versions/${CAPELLA_VERSION}/${BUILD_ARCHITECTURE}/capella.* /opt/ - - -FROM build_${BUILD_TYPE} - -ARG CAPELLA_VERSION - -RUN apt-get update && \ - apt-get install -y \ - libxtst6 \ - xdg-utils \ - xvfb \ - xauth \ - dbus-x11 && \ - rm -rf /var/lib/apt/lists/*; - -RUN if [ -s capella.zip ]; then \ - # Install Capella using zip - unzip capella.zip -d . && \ - rm capella.zip; \ - # Install Capella using tar.gz - elif [ -s capella.tar.gz ]; then \ - tar -xf capella.tar.gz; \ - else \ - echo "capella.zip and capella.tar.gz are empty! Please add you custom Capella zip or tar.gz" && \ - exit 1; \ - fi && \ - rm -rf samples capella.zip capella.tar.gz - -# Set Permissions -RUN chmod +x capella/capella && \ - chmod -R +x capella/jre/bin && \ - chmod -R +x capella/jre/lib && \ - chown -R techuser /opt/capella - -COPY patch.sh /opt/patch.sh -RUN chmod +x /opt/patch.sh - -# Install Dropins for Capella -USER techuser - -COPY ./versions/${CAPELLA_VERSION}/dropins /opt/capella/dropins -ARG CAPELLA_DROPINS="" -COPY install_dropins.py /opt/install_dropins.py -COPY ./versions/${CAPELLA_VERSION}/dropins.yml /opt/dropins.yml - -ARG MEMORY_LIMIT=5500m - -RUN echo '-Dorg.eclipse.equinox.p2.transport.ecf.retry=15' >> /opt/capella/capella.ini && \ - echo '-Dorg.eclipse.ecf.provider.filetransfer.retrieve.readTimeout=10000' >> /opt/capella/capella.ini && \ - sed -i "s/-Xmx[^ ]*/-Xmx$MEMORY_LIMIT/g" /opt/capella/capella.ini -RUN python install_dropins.py - -COPY ./versions/${CAPELLA_VERSION}/patches /opt/patches -RUN PATCH_DIR=/opt/patches /opt/patch.sh - -USER root - -# Eclipse settings -RUN mkdir -p /opt/capella/configuration/.settings; \ - ## Do not show WORKSPACE_SELECTION_DIALOG - echo "SHOW_WORKSPACE_SELECTION_DIALOG=false" >> /opt/capella/configuration/.settings/org.eclipse.ui.ide.prefs; \ - # Setup workspace - mkdir -p /workspace; \ - # Set workspace permissions - chown -R techuser /workspace && \ - chmod -R 777 /opt/capella/configuration && \ - chmod -R 777 /opt/capella/p2/org.eclipse.equinox.p2.engine/profileRegistry && \ - chown techuser /opt /opt/capella/capella.ini - -RUN echo '-Dosgi.configuration.area=file:/opt/capella/configuration' >> /opt/capella/capella.ini -COPY setup/* /opt/setup/ - -ENV AUTOSTART_CAPELLA=1 -ENV RESTART_CAPELLA=1 -COPY ./autostart /home/techuser/.config/openbox/autostart - -ENV ECLIPSE_INSTALLATION_PATH=/opt/capella -ENV ECLIPSE_EXECUTABLE=/opt/capella/capella - -# Set memory options for the JVM (used by set_memory_flags.py) -ARG MEMORY_MAX=90% -ENV MEMORY_MAX=${MEMORY_MAX} -ARG MEMORY_MIN=70% -ENV MEMORY_MIN=${MEMORY_MIN} - -COPY ./autostart /home/techuser/.config/openbox/autostart - -COPY git_askpass.py /etc/git_askpass.py -RUN chmod 555 /etc/git_askpass.py - -ARG BUILD_ARCHITECTURE=amd64 -ADD https://github.com/krallin/tini/releases/download/v0.19.0/tini-$BUILD_ARCHITECTURE /tini -RUN chmod +x /tini - -COPY startup.sh /startup.sh -ENTRYPOINT [ "/tini", "--", "/startup.sh" ] - -ENV BASE_TYPE=capella - -USER techuser diff --git a/capella/autostart b/capella/autostart deleted file mode 100755 index fc41979e..00000000 --- a/capella/autostart +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash -# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -# Autostart script -export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" - -# Load environment variables from /etc/environment -source /etc/environment - -nitrogen --restore & - -if [ "$AUTOSTART_CAPELLA" = "1" ]; -then - if [ "$RESTART_CAPELLA" = "1" ]; - then - # Run capella in a loop: - ( while true; do /opt/capella/capella -data ${WORKSPACE_DIR:-/workspace} > /var/log/capella.stdout.log 2> /var/log/capella.stderr.log; sleep 1; done ) & - else - /opt/capella/capella -data ${WORKSPACE_DIR:-/workspace} > /var/log/capella.stdout.log 2> /var/log/capella.stderr.log & - fi -fi diff --git a/capella/libs/.gitkeep b/capella/libs/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/capella/patch.sh b/capella/patch.sh index 1c4b94fe..9186475d 100755 --- a/capella/patch.sh +++ b/capella/patch.sh @@ -2,13 +2,15 @@ # SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 -if [[ -n "${PATCH_DIR:?}/patch_info.csv" ]]; +if [[ -f "${PATCH_DIR:?}/patch_info.csv" ]]; then while IFS="," read -r patch_zip install_iu tag do INSTALL_IU_JOIN=$(echo $install_iu | sed "s/ /,/g"); - /opt/capella/capella \ + /layers/capella/app/capella \ -consoleLog \ + -clean \ + -data $(mktemp -d) \ -application org.eclipse.equinox.p2.director \ -profile DefaultProfile \ -tag $tag \ diff --git a/capella/setup/README.md b/capella/setup/README.md deleted file mode 100644 index 1d7f876b..00000000 --- a/capella/setup/README.md +++ /dev/null @@ -1,9 +0,0 @@ - - -# Directory for startup scripts - -This directory contains scripts that are executed during container startup. As -of now, we support scripts with file-ending `.sh` and `.py`. diff --git a/capella/setup/replace_env_variables.sh b/capella/setup/replace_env_variables.sh deleted file mode 100755 index 21d456f6..00000000 --- a/capella/setup/replace_env_variables.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash - -# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -# Replace environment variables in capella.ini, e.g. licences -envsubst < /opt/capella/capella.ini > /tmp/capella.ini && mv /tmp/capella.ini /opt/capella/capella.ini; diff --git a/capella/startup.sh b/capella/startup.sh deleted file mode 100755 index 93c0626d..00000000 --- a/capella/startup.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -export DISPLAY=":99" -python3 /opt/setup/set_memory_flags.py -(Xvfb ${DISPLAY} -screen 0 1920x1080x8 -nolisten tcp 0>/dev/null 2>&1 &) -/opt/capella/capella "$@" diff --git a/capella_loop.sh b/capella_loop.sh deleted file mode 100755 index 35152b47..00000000 --- a/capella_loop.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -set -e - -# This tries to represent a matrix build -# When a Make target uses this shell, the target runs for each CAPELLA_VERSION -# Please make sure to use environment variables instead of Make variables in the targets. -# This applies to the following variables: -# - $$CAPELLA_VERSION instead of $(CAPELLA_VERSION) -# - $$DOCKER_TAG instead of $(DOCKER_TAG) - -ASCII_BOLD="\033[1m" -ASCII_GREEN="\033[0;32m" -ASCII_RED="\033[0;31m" -ASCII_CYAN="\033[0;36m" -ASCII_RESET="\033[0m" - -for version in $CAPELLA_VERSIONS -do - printf "${ASCII_BOLD}${ASCII_CYAN}Running target '$MAKE_CURRENT_TARGET' for Capella version $version...${ASCII_RESET}\n" >&2 - export CAPELLA_VERSION=$version - export DOCKER_TAG=$(echo "$DOCKER_TAG_SCHEMA" | envsubst) - /bin/bash -euo pipefail "$@" || r=$? - if [[ -z "$r" ]]; then - printf "${ASCII_BOLD}${ASCII_GREEN}Successfully ran target '$MAKE_CURRENT_TARGET' for Capella version $version.${ASCII_RESET}\n" >&2 - else - printf "${ASCII_BOLD}${ASCII_RED}Running target '$MAKE_CURRENT_TARGET' for Capella version $version failed. Please check the logs above.${ASCII_RESET}\n" >&2 - exit $r - fi -done diff --git a/ci-templates/gitlab/diagram-cache.yml b/ci-templates/gitlab/diagram-cache.yml index f90421b1..f277403c 100644 --- a/ci-templates/gitlab/diagram-cache.yml +++ b/ci-templates/gitlab/diagram-cache.yml @@ -7,15 +7,13 @@ variables: update_capella_diagram_cache: image: name: $DOCKER_REGISTRY/capella/base:${CAPELLA_DOCKER_IMAGES_TAG} - entrypoint: [""] + entrypoint: [''] script: - - pip install "capellambse[cli]@git+https://github.com/DSD-DBS/py-capellambse.git@${CAPELLAMBSE_REVISION}" + - pip install + "capellambse[cli]@git+https://github.com/DSD-DBS/py-capellambse.git@${CAPELLAMBSE_REVISION}" - mkdir diagram_cache - - xvfb-run python -m capellambse.diagram_cache - --model "${ENTRYPOINT}" - --output ./diagram_cache - --index - --exe "/opt/capella/capella" + - xvfb-run python -m capellambse.diagram_cache --model "${ENTRYPOINT}" + --output ./diagram_cache --index --exe "/layers/capella/app/capella" - | if [[ "${PUSH_DIAGRAM_CACHE}" == "false" ]]; then exit 0 @@ -27,11 +25,11 @@ update_capella_diagram_cache: git push -o ci.skip --set-upstream origin ${DERIVED_BRANCH_NAME} --force artifacts: paths: - - "diagram_cache" + - 'diagram_cache' variables: # Virtual display used to run Capella in the background. # Do not modify the value! - DISPLAY: ":99" + DISPLAY: ':99' # Docker tag, which is used for the capella/base image. # Defaults to ${CAPELLA_VERSION}-${CAPELLA_DOCKER_IMAGES_REVISION} if not defined @@ -39,15 +37,15 @@ update_capella_diagram_cache: # Whether to push the diagram cache changes or not # Defaults to false if not defined - PUSH_DIAGRAM_CACHE: "false" + PUSH_DIAGRAM_CACHE: 'false' # The commit message used for updating the diagram cache # Defaults to "chore: Update diagram cache" if not defined - COMMIT_MSG: "chore: Update diagram cache" + COMMIT_MSG: 'chore: Update diagram cache' # The revision of the py-capellambse package to install # Defaults to "master" if not defined - CAPELLAMBSE_REVISION: "master" + CAPELLAMBSE_REVISION: 'master' # Relative entrypoint to .aird file inside repository (starting from the root of the repository). # Example: test/test.aird diff --git a/ci-templates/gitlab/image-builder.yml b/ci-templates/gitlab/image-builder.yml index 7710e365..cbecbbdd 100644 --- a/ci-templates/gitlab/image-builder.yml +++ b/ci-templates/gitlab/image-builder.yml @@ -2,15 +2,9 @@ # SPDX-License-Identifier: Apache-2.0 variables: - BASE: - value: '0' - description: 'Build the base image?' CAPELLA_BASE: value: '0' description: 'Build the capella/base image?' - CAPELLA_REMOTE: - value: '0' - description: 'Build the capella/remote image?' T4C_CLIENT_BASE: value: '0' description: 'Build the t4c/client/base image?' @@ -23,21 +17,17 @@ variables: JUPYTER: value: '0' description: 'Build the jupyter-notebook image?' - PAPYRUS_BASE: - value: '0' - description: 'Build the papyrus/base image?' PAPYRUS_REMOTE: value: '0' description: 'Build the papyrus/remote image?' - ECLIPSE_BASE: - value: '0' - description: 'Build the eclipse/base image?' - ECLIPSE_REMOTE: - value: '0' - description: 'Build the eclipse/remote image?' ECLIPSE_REMOTE_PURE_VARIANTS: value: '0' description: 'Build the eclipse/remote/pure-variants image?' + BUILD_FOR_LATEST_TAG: + value: '0' + description: + "Fetch the latest tag for the image builder repository and use it as + revision. If 0, '$CI_COMMIT_REF_NAME' will be used." CAPELLA_DOCKER_IMAGES_REVISION: value: 'main' description: @@ -49,11 +39,6 @@ variables: 'Capella version. Please make sure that a subdirectory with the name of the value exists. The value must be valid ASCII and may contain lowercase and uppercase letters, digits, underscores, periods and dashes.' - BUILD_FOR_LATEST_TAG: - value: '0' - description: - "Fetch the latest tag for the image builder repository and use it as - revision. If 0, '$CI_COMMIT_REF_NAME' will be used." JUPYTER_VERSION: value: 'python-3.11' description: 'Python version for the jupyter notebook.' @@ -63,24 +48,17 @@ variables: ECLIPSE_VERSION: value: '4.27' description: 'Semantic version of Eclipse.' + PURE_VARIANTS_VERSION: + value: '6.0.1' + description: 'Semantic version of pure::variants.' ENVIRONMENT: value: 'staging' description: - 'Specifies the environment. Make sure that all related environment - variables are set on the repository level. More information in the - documentation.' - T4C_SERVER_REGISTRY: '' # Registry where you can find the t4c server image - T4C_SERVER_TAG: '$CAPELLA_VERSION-main' # Tag that is used for the t4c server image - T4C_SERVER_TEST_DATA_REPO: '' # Link to the t4c test data repo needed to run the backup tests - LOCAL_GIT_BASE_IMAGE: 'debian:bookworm' # Specifies the base images used to build the local git server needed to run the tests - DOCKER_BUILD_ARGS: '--no-cache' - BUILD_ARCHITECTURE: amd64 - PURE_VARIANTS_VERSION: '6.0.1' - XPRA_REGISTRY: 'https://xpra.org' - ECLIPSE_REPOSITORY: 'https://download.eclipse.org' + 'Specifies the environment. Must match a key in the config.yml file.' stages: - build + - test default: image: $DOCKER_REGISTRY/base @@ -88,25 +66,25 @@ default: - docker .github: &github - - git clone https://github.com/DSD-DBS/capella-dockerimages.git - - cd capella-dockerimages - - git checkout $CAPELLA_DOCKER_IMAGES_REVISION -- + - git clone https://github.com/DSD-DBS/capella-dockerimages.git /tmp/capella-dockerimages + - git -C /tmp/capella-dockerimages checkout $CAPELLA_DOCKER_IMAGES_REVISION -- + - cp -R /tmp/capella-dockerimages/* $CI_PROJECT_DIR .docker: &docker - docker info - - DOCKER_REGISTRY_USER_QUERY=DOCKER_REGISTRY_USER_${ENVIRONMENT_UPPERCASE} - - DOCKER_REGISTRY_PASSWORD_QUERY=DOCKER_REGISTRY_PASSWORD_${ENVIRONMENT_UPPERCASE} # prettier-ignore - - echo ${!DOCKER_REGISTRY_PASSWORD_QUERY:?} | docker login -u ${!DOCKER_REGISTRY_USER_QUERY:?} --password-stdin $DOCKER_REGISTRY - - docker pull $BASE_IMAGE + - | + echo $(cat config.yml | yq -r .environments.$ENVIRONMENT.registry.user) | \ + docker login -u $(sops -d config.yml | yq -r .environments.$ENVIRONMENT.registry.password) --password-stdin $DOCKER_REGISTRY -.push: &push - - docker push $IMAGE:$DOCKER_TAG +.sops: &sops + - apk add sops gpg + - gpg --import /secrets/private.gpg .prepare: &prepare - - ENVIRONMENT_UPPERCASE=$(echo ${ENVIRONMENT:?} | tr '[:lower:]' '[:upper:]') - - DOCKER_REGISTRY_QUERY=DOCKER_REGISTRY_${ENVIRONMENT_UPPERCASE} - - DOCKER_REGISTRY=${!DOCKER_REGISTRY_QUERY:?} + - *sops + - (curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.34.2/pack-v0.34.2-linux.tgz" | sudo tar -C /usr/local/bin/ --no-same-owner -xzv pack) + - DOCKER_REGISTRY=$(cat config.yml | yq -r .environments.$ENVIRONMENT.registry.url) - > if [[ "$BUILD_FOR_LATEST_TAG" == "1" ]]; then git fetch --tags; @@ -117,135 +95,80 @@ default: fi # prettier-ignore - GENERAL_IMAGE_TAG=$(echo $CAPELLA_DOCKER_IMAGES_REVISION | sed 's/[^a-zA-Z0-9.]/-/g')-$IMAGE_BUILDER_REVISION - - IMAGE=${DOCKER_REGISTRY}/$IMAGE -.resolve-base-image: &resolve-base-image - - BASE_IMAGE=${DOCKER_REGISTRY}/${BASE_IMAGE} +.previous-image: &previous-image - > - if [[ "$BASE_IMAGE" == "$DOCKER_REGISTRY/base2" ]]; then - BASE_IMAGE=$BASE_IMAGE:$GENERAL_IMAGE_TAG; + export PREVIOUS_IMAGE=""; + if [[ "$ENVIRONMENT" == "production" ]]; then + [ -f /shared/$CI_PROJECT_ID/$CAPELLA_VERSION/ ] export PREVIOUS_IMAGE=$(cat config.yml | yq -r .environments.staging.registry.url)/$IMAGE:$DOCKER_TAG; else - BASE_IMAGE=$BASE_IMAGE:$DOCKER_TAG; + export PREVIOUS_IMAGE=$(cat config.yml | yq -r .environments.production.registry.url)/$IMAGE:$DOCKER_TAG; fi + - Using previous image "$PREVIOUS_IMAGE" -.prepare-capella: &prepare-capella - - *prepare - - export DOCKER_TAG=$CAPELLA_VERSION-$GENERAL_IMAGE_TAG - - *resolve-base-image - - cd capella/versions/$CAPELLA_VERSION - - *github +.save-previous-image: $save-previous-image + - mkdir -p /shared/$CI_PROJECT_ID + - echo -n $PREVIOUS_IMAGE > /shared/$CI_PROJECT_ID/$CACHE_KEY.txt .prepare-papyrus: &prepare-papyrus - *prepare - export DOCKER_TAG=$PAPYRUS_VERSION-$GENERAL_IMAGE_TAG - - *resolve-base-image - - cd papyrus/versions/$PAPYRUS_VERSION - *github .prepare-eclipse: &prepare-eclipse - *prepare - export DOCKER_TAG=$ECLIPSE_VERSION-$GENERAL_IMAGE_TAG - - *resolve-base-image - - cd eclipse/versions/$ECLIPSE_VERSION - *github .prepare-eclipse-pv: &prepare-eclipse-pv - *prepare # prettier-ignore - export DOCKER_TAG=$ECLIPSE_VERSION-$PURE_VARIANTS_VERSION-$GENERAL_IMAGE_TAG - - BASE_IMAGE="${DOCKER_REGISTRY}/${BASE_IMAGE}:$ECLIPSE_VERSION-$GENERAL_IMAGE_TAG" - cd pure-variants - *github -.local-git-server: &local-git-server - - | - docker build $DOCKER_BUILD_ARGS \ - -t local-git-server \ - --build-arg BASE_IMAGE=$LOCAL_GIT_BASE_IMAGE \ - tests/local-git-server - -.prepare-tests-general: &prepare-tests-general - - export LOCAL_GIT_TAG=latest - - export DOCKER_PREFIX=${DOCKER_REGISTRY:?}/ - - apt-get update && apt-get -y install jq - # This command lists docker containers, identifies the current job and writes the network ID of the current container into the DOCKER_NETWORK variable. - # prettier-ignore - - export DOCKER_NETWORK=$(docker inspect -f "{{json .NetworkSettings.Networks }}" $(docker ps -q -f "label=com.gitlab.gitlab-runner.job.id=$CI_JOB_ID" -f "label=com.gitlab.gitlab-runner.type=build") | jq -r 'keys[0]' | head -n 1) - - *local-git-server - - python -m venv .venv - - source .venv/bin/activate - - pip install -e '.[test]' - - cd tests - -.prepare-t4c-server-tests: &prepare-t4c-server-tests - - *prepare-tests-general - # prettier-ignore - - GIT_PASSWORD=${T4C_SERVER_TEST_DATA_REPO_TOKEN:?} git clone ${T4C_SERVER_TEST_DATA_REPO:?} - -base: +builder: stage: build needs: [] - rules: - - if: '$BASE == "1"' - when: always variables: - BASE_IMAGE: debian:bookworm - IMAGE: base2 + IMAGE: builder script: - *prepare - - DOCKER_TAG=$GENERAL_IMAGE_TAG - *github - *docker - - UID_QUERY=UID_${ENVIRONMENT_UPPERCASE} - | - docker build $DOCKER_BUILD_ARGS \ - -t $IMAGE:$DOCKER_TAG \ - --build-arg UID=${!UID_QUERY} \ - --build-arg BASE_IMAGE=$BASE_IMAGE \ - base - - *push + docker build buildpacks/builder:$GENERAL_IMAGE_TAG \ + --build-arg CNB_USER_ID=$(cat config.yml | yq -r ".environments.$ENVIRONMENT.uid") + - | + pack builder create $DOCKER_REGISTRY/builder:$GENERAL_IMAGE_TAG \ + --config buildpacks/builder/builder.toml \ + --publish capella/base: stage: build needs: - - job: base + - job: builder optional: true rules: - if: '$CAPELLA_BASE == "1"' when: always variables: - BASE_IMAGE: base2 IMAGE: capella/base script: - - *prepare-capella + - *prepare + - export DOCKER_TAG=$CAPELLA_VERSION-$GENERAL_IMAGE_TAG + - *resolve-previous-image + - *github - *docker - # prettier-ignore - - mv ../capella.tar.gz ./capella/versions/$CAPELLA_VERSION/$BUILD_ARCHITECTURE/capella.tar.gz - - cp ./eclipse/set_memory_flags.py ./capella/setup/set_memory_flags.py - - > - if [[ -n "$(find ../dropins -maxdepth 1 -type d)" ]]; then - mv ../dropins/* ./capella/versions/$CAPELLA_VERSION/dropins/ - else - echo "No files to move in dropins" - fi - - > - if [[ -n "$(find ../patches -maxdepth 1 -type f -not -path '*/\.*')" ]]; - then - mv ../patches/* ./capella/versions/$CAPELLA_VERSION/patches/ - else - echo "No files to move in patches" - fi - - mv ../../../libs/* ./capella/libs/ - | - docker build $DOCKER_BUILD_ARGS \ - -t $DOCKER_REGISTRY/capella/base:$DOCKER_TAG \ - --build-arg BUILD_TYPE=offline \ - --build-arg CAPELLA_VERSION=$CAPELLA_VERSION \ - --build-arg INJECT_PACKAGES=true \ - --build-arg BUILD_ARCHITECTURE="$BUILD_ARCHITECTURE" \ - --build-arg BASE_IMAGE=$BASE_IMAGE \ - capella - - *push + pack build $DOCKER_REGISTRY/$IMAGE:$DOCKER_TAG \ + --env TOOL=capella \ + --env CAPELLA_VERSION=$CAPELLA_VERSION \ + --uid $(cat config.yml | yq -r ".environments.$ENVIRONMENT.uid") \ + --builder $DOCKER_REGISTRY/builder:$GENERAL_IMAGE_TAG \ + --publish \ + --path ./buildpacks \ + --volume $CI_PROJECT_DIR:/app:ro capella/remote: stage: build @@ -256,7 +179,6 @@ capella/remote: - if: '$CAPELLA_REMOTE == "1"' when: always variables: - BASE_IMAGE: capella/base IMAGE: capella/remote script: - *prepare-capella @@ -264,7 +186,6 @@ capella/remote: - | docker build $DOCKER_BUILD_ARGS \ -t $DOCKER_REGISTRY/capella/remote:$DOCKER_TAG \ - --build-arg BASE_IMAGE=$BASE_IMAGE \ --build-arg XPRA_REGISTRY=$XPRA_REGISTRY \ remote - *push @@ -278,7 +199,6 @@ t4c/client/base: - if: '$T4C_CLIENT_BASE == "1"' when: always variables: - BASE_IMAGE: capella/base IMAGE: t4c/client/base script: - *prepare-capella @@ -288,16 +208,8 @@ t4c/client/base: docker build $DOCKER_BUILD_ARGS \ -t $DOCKER_REGISTRY/t4c/client/base:$DOCKER_TAG \ --build-arg CAPELLA_VERSION=$CAPELLA_VERSION \ - --build-arg BASE_IMAGE=$BASE_IMAGE \ t4c - - *prepare-t4c-server-tests - # prettier-ignore - - pytest -o log_cli=true -s -m t4c_server test_backups.py test_exporter_local.py test_exporter_git.py || r=3 - *push - - exit $r - allow_failure: &allow_failure - exit_codes: - - 3 # Exit code when pytest fail t4c/client/remote: stage: build @@ -308,7 +220,6 @@ t4c/client/remote: - if: '$T4C_CLIENT_REMOTE == "1"' when: always variables: - BASE_IMAGE: t4c/client/base IMAGE: t4c/client/remote script: - *prepare-capella @@ -316,13 +227,9 @@ t4c/client/remote: - | docker build $DOCKER_BUILD_ARGS \ -t $DOCKER_REGISTRY/t4c/client/remote:$DOCKER_TAG \ - --build-arg BASE_IMAGE=$BASE_IMAGE \ --build-arg XPRA_REGISTRY=$XPRA_REGISTRY \ remote - - *prepare-tests-general - - pytest -o log_cli=true -s test_t4c_repository_injection.py || r=3 - *push - - exit $r allow_failure: *allow_failure t4c/client/remote/pure-variants: @@ -334,7 +241,6 @@ t4c/client/remote/pure-variants: - if: '$T4C_CLIENT_REMOTE_PURE_VARIANTS == "1"' when: always variables: - BASE_IMAGE: t4c/client/remote IMAGE: t4c/client/remote/pure-variants script: - *prepare-capella @@ -345,36 +251,10 @@ t4c/client/remote/pure-variants: docker build $DOCKER_BUILD_ARGS \ -t $DOCKER_REGISTRY/t4c/client/remote/pure-variants:$DOCKER_TAG \ --build-arg ECLIPSE_REPOSITORY=${ECLIPSE_REPOSITORY} \ - --build-arg BASE_IMAGE=$BASE_IMAGE \ --build-arg PURE_VARIANTS_VERSION="$PURE_VARIANTS_VERSION" \ pure-variants - *push -eclipse/base: - stage: build - needs: - - job: base - optional: true - rules: - - if: '$ECLIPSE_BASE == "1"' - when: always - variables: - BASE_IMAGE: base2 - IMAGE: eclipse/base - script: - - *prepare-eclipse - - *docker - # prettier-ignore - - mv ../eclipse.tar.gz ./eclipse/versions/$ECLIPSE_VERSION/$BUILD_ARCHITECTURE/eclipse.tar.gz - - | - docker build $DOCKER_BUILD_ARGS \ - -t ${IMAGE}:${DOCKER_TAG} \ - --build-arg ECLIPSE_VERSION=${ECLIPSE_VERSION} \ - --build-arg BASE_IMAGE=${BASE_IMAGE} \ - --build-arg ECLIPSE_REPOSITORY=${ECLIPSE_REPOSITORY} \ - eclipse - - *push - eclipse/remote: stage: build needs: @@ -384,7 +264,6 @@ eclipse/remote: - if: '$ECLIPSE_REMOTE == "1"' when: always variables: - BASE_IMAGE: eclipse/base IMAGE: eclipse/remote script: - *prepare-eclipse @@ -392,12 +271,11 @@ eclipse/remote: - | docker build $DOCKER_BUILD_ARGS \ -t ${IMAGE}:${DOCKER_TAG} \ - --build-arg BASE_IMAGE=$BASE_IMAGE \ --build-arg XPRA_REGISTRY=$XPRA_REGISTRY \ remote - *push -eclipse/remote/pure-variants: +eclipse/pure-variants/remote: stage: build needs: - job: eclipse/remote @@ -406,7 +284,6 @@ eclipse/remote/pure-variants: - if: '$ECLIPSE_REMOTE_PURE_VARIANTS == "1"' when: always variables: - BASE_IMAGE: eclipse/remote IMAGE: eclipse/remote/pure-variants script: - *prepare-eclipse-pv @@ -416,36 +293,10 @@ eclipse/remote/pure-variants: docker build $DOCKER_BUILD_ARGS \ -t ${IMAGE}:${DOCKER_TAG} \ --build-arg ECLIPSE_REPOSITORY=${ECLIPSE_REPOSITORY} \ - --build-arg BASE_IMAGE=${BASE_IMAGE} \ --build-arg PURE_VARIANTS_VERSION="$PURE_VARIANTS_VERSION" \ pure-variants - *push -papyrus/base: - stage: build - needs: - - job: base - optional: true - rules: - - if: '$PAPYRUS_BASE == "1"' - when: always - variables: - BASE_IMAGE: base2 - IMAGE: papyrus/base - script: - - *prepare-papyrus - - *docker - - mv ../papyrus.tar.gz ./papyrus/versions/$PAPYRUS_VERSION/papyrus.tar.gz - - cp ./eclipse/set_memory_flags.py ./papyrus/set_memory_flags.py - - | - docker build $DOCKER_BUILD_ARGS \ - -t ${IMAGE}:${DOCKER_TAG} \ - --build-arg ECLIPSE_REPOSITORY=${ECLIPSE_REPOSITORY} \ - --build-arg PAPYRUS_VERSION=$PAPYRUS_VERSION \ - --build-arg BASE_IMAGE=$BASE_IMAGE \ - papyrus - - *push - papyrus/remote: stage: build needs: @@ -455,7 +306,6 @@ papyrus/remote: - if: '$PAPYRUS_REMOTE == "1"' when: always variables: - BASE_IMAGE: papyrus/base IMAGE: papyrus/remote script: - *prepare-papyrus @@ -463,7 +313,6 @@ papyrus/remote: - | docker build $DOCKER_BUILD_ARGS \ -t ${IMAGE}:${DOCKER_TAG} \ - --build-arg BASE_IMAGE=$BASE_IMAGE \ --build-arg XPRA_REGISTRY=$XPRA_REGISTRY \ remote - *push @@ -477,7 +326,6 @@ jupyter: - if: '$JUPYTER == "1"' when: always variables: - BASE_IMAGE: base2 IMAGE: jupyter-notebook script: - *prepare @@ -488,6 +336,36 @@ jupyter: - | docker build $DOCKER_BUILD_ARGS \ -t $IMAGE:$DOCKER_TAG \ - --build-arg BASE_IMAGE=$BASE_IMAGE \ jupyter-notebook - *push + +.local-git-server: &local-git-server + - | + docker build $DOCKER_BUILD_ARGS \ + -t local-git-server \ + --build-arg BASE_IMAGE=$(cat config.yml | yq -r '.["base-images"].debian') \ + tests/local-git-server + +test: + stage: test + rules: + - if: '$T4C_CLIENT_REMOTE == "1"' + when: always + script: + - export LOCAL_GIT_TAG=latest + - export DOCKER_PREFIX=${DOCKER_REGISTRY:?}/ + - apk add jq + # This command lists docker containers, identifies the current job and writes the network ID of the current container into the DOCKER_NETWORK variable. + # prettier-ignore + - export DOCKER_NETWORK=$(docker inspect -f "{{json .NetworkSettings.Networks }}" $(docker ps -q -f "label=com.gitlab.gitlab-runner.job.id=$CI_JOB_ID" -f "label=com.gitlab.gitlab-runner.type=build") | jq -r 'keys[0]' | head -n 1) + - *local-git-server + - python -m venv .venv + - source .venv/bin/activate + - pip install -e '.[test]' + - cd tests + - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@${CI_SERVER_HOST}/$(cat config.yml | yq -r '.["test-data-repository"]') test-data-repo + - apk add yq envsubst + - export T4C_SERVER_REGISTRY=$(cat config.yml | yq -r '.["t4c-server"].registry') + - export T4C_SERVER_TAG=$(cat config.yml | yq -r '.["t4c-server"].tag' | envsubst) + # prettier-ignore + - pytest -o log_cli=true -s -m t4c_server test_backups.py test_exporter_local.py test_exporter_git.py test_t4c_repository_injection.py diff --git a/ci-templates/gitlab/model-validation.yml b/ci-templates/gitlab/model-validation.yml index ff728e48..8b715453 100644 --- a/ci-templates/gitlab/model-validation.yml +++ b/ci-templates/gitlab/model-validation.yml @@ -4,34 +4,36 @@ model-validation: image: name: $DOCKER_REGISTRY/capella/base:${CAPELLA_DOCKER_IMAGES_TAG} - entrypoint: [""] + entrypoint: [''] script: # As our containers run with techuser (non-root) git commands fail with dubious ownership, see for the solution. - git config --global --add safe.directory ${CI_PROJECT_DIR} - git fetch - - if [ ! -z $CI_COMMIT_BRANCH ]; then git reset --hard origin/$CI_COMMIT_BRANCH; fi + - if [ ! -z $CI_COMMIT_BRANCH ]; then git reset --hard + origin/$CI_COMMIT_BRANCH; fi - BUILD_DIR=$(pwd) - - if [ ! -f ".project" ]; then echo "The model validator requires a '.project' file!" && exit 1; fi - - 'if [[ "$(dirname "${ENTRYPOINT}")" != "." ]]; then echo ''The model validator requires a "*.aird" in the root level of the repository!'' && exit 1; fi' + - if [ ! -f ".project" ]; then echo "The model validator requires a + '.project' file!" && exit 1; fi + - 'if [[ "$(dirname "${ENTRYPOINT}")" != "." ]]; then echo ''The model + validator requires a "*.aird" in the root level of the repository!'' && + exit 1; fi' - mkdir /tmp/project - cp -r . /tmp/project - cd /tmp - > - xvfb-run /opt/capella/capella - -nosplash - -application org.polarsys.capella.core.commandline.core - -appid org.polarsys.capella.core.validation.commandline - -data "$(pwd)" - -input "project/$ENTRYPOINT" - -outputfolder "project/validation" - - cp "/tmp/project/validation/project/$ENTRYPOINT/validation-results.html" $BUILD_DIR + xvfb-run /layers/capella/app/capella -nosplash -application + org.polarsys.capella.core.commandline.core -appid + org.polarsys.capella.core.validation.commandline -data "$(pwd)" -input + "project/$ENTRYPOINT" -outputfolder "project/validation" + - cp "/tmp/project/validation/project/$ENTRYPOINT/validation-results.html" + $BUILD_DIR artifacts: paths: - - "validation-results.html" + - 'validation-results.html' variables: # Virtual display used to run Capella in the background. # Do not modify the value! - DISPLAY: ":99" + DISPLAY: ':99' # Docker tag, which is used for the capella/base image. # Defaults to ${CAPELLA_VERSION}-${CAPELLA_DOCKER_IMAGES_REVISION} if not defined diff --git a/docs/docs/base.md b/docs/docs/base.md deleted file mode 100644 index a04959c4..00000000 --- a/docs/docs/base.md +++ /dev/null @@ -1,69 +0,0 @@ - - -# Base image - - -!!! info - The Docker image name for this image is `base` - -Our base image updates all `apt-get` packages and installs the following -packages: - -- `python3` -- `python3-pip` - -Also, we create a custom user `techuser`. This user will always be used to run -the containers and allows to assign a custom `UID`. This can make sense, if you -want to deploy the containers in a K8s cluster and your company has some -security restrictions (e.g. specific `UID` ranges). - -Feel free to modify this image to your specific needs. You are able to set -proxies, custom registry URLs, your timezone, CA certificates and any other -stuff. - -The following environment variable can be set in all images: - -- `WORKSPACE_DIR`: The directory applications (Eclipse, Capella, Jupyter) will - use as workspace. The workspace directory shall be a subdirectory of - `/workspace` or `/home/techuser`. - -## Use the prebuilt image - -``` -docker run -it ghcr.io/dsd-dbs/capella-dockerimages/base:$CAPELLA_DOCKER_IMAGES_REVISION -``` - -where `$CAPELLA_DOCKER_IMAGES_REVISION` is the tag or branch of this -repository. In case of branches, replace all characters matching the regex -`[^a-zA-Z0-9.]` with `-`. - -## Build it yourself - -### Build it manually with Docker - -To build the base image, please run: - -```zsh -docker build -t base base -``` - -**Important:** If your company has a specific base image with all company -configurations, of course, it can also be used: - -```zsh -docker build -t base --build-arg BASE_IMAGE=$CUSTOM_IMAGE base -``` - -Make sure that your `$CUSTOM_IMAGE` is a Linux image that has the common tools -installed and uses the `apt` / `apt-get` package manager. If this is not the -case, the image can not be used. Our images were tested with the image -`debian:bookworm`. - -If you like to set a custom `UID` for the user `techuser`, you can run: - -```zsh -docker build -t base --build-arg UID=1001 base -``` diff --git a/docs/docs/base/capella/index.md b/docs/docs/base/capella/index.md new file mode 100644 index 00000000..555830b8 --- /dev/null +++ b/docs/docs/base/capella/index.md @@ -0,0 +1,278 @@ + + +# Capella Base + +The Capella base image installs a selected Capella client version. The Capella +client can be downloaded and can optionally be customised prior to building the +Docker image or can be downloaded automatically in the Docker image. + +The images are meant to have a containerised Capella (with or without a Team +for Capella client) that can be run headless (as command line interface). + +!!! info + + The functionality for running capella as a command-line app used to be part of + the `capella/cli` image. An image with this name is no longer built. Use + `capella/base` instead. + +## Supported versions + +Currently, we support Capella versions `5.0.0`, `5.2.0`, `6.0.0`, and `6.1.0`. + +## Use the prebuilt image + +``` +docker run ghcr.io/dsd-dbs/capella-dockerimages/capella/base:$TAG +``` + +where `$TAG` is the Docker tag. For more information, have a look at our +[tagging schema](#tagging-schema). + +Please check the [`Run the container`](#run-the-container) section for more +information about the usage. + +### Tagging schema + +The Capella related images are tagged using the following schema: + +`$CAPELLA_VERSION-$DROPINS_TYPE-$CAPELLA_DOCKER_IMAGES_REVISION`, e.g., +`6.0.0-selected-dropins-v1.10.2` for Capella version `6.0.0` with selected +dropins and Capella Docker images revision `v1.10.2`. + +`$CAPELLA_VERSION` is the semantic version of Capella (see supported versions +[above](#supported-versions)). `$DROPINS_TYPE` is the name of the set of +dropins. `$CAPELLA_DOCKER_IMAGES_REVISION` can be a tag or branch of this +repository. In case of branches, all characters matching the regex +`[^a-zA-Z0-9.]` will be replaced with `-`. + +We don't tag images with the `latest` tag. You may want to use +`$CAPELLA_VERSION-selected-dropins-main` for the latest version, but we +recommend using tags for the best stability. + +### Supported dropins + +Our prebuilt images are published a pre-selected set of dropins. Available +options are: + +- `without-dropins`: Without dropins +- `selected-dropins`: With + [CapellaXHTMLDocGen](https://github.com/eclipse/capella-xhtml-docgen), + [DiagramStyler](https://github.com/eclipse/capella/wiki/PVMT), + [PVMT](https://github.com/eclipse/capella/wiki/PVMT), + [Filtering](https://github.com/eclipse/capella-filtering), + [Requirements](https://github.com/eclipse/capella-requirements-vp) and + [SubsystemTransition](https://github.com/eclipse/capella-sss-transition) + +If you need a custom set of dropins, you have two options: + +**Option 1**: Mount a dropins folder with all dropins into +`/opt/capella/dropins` when starting the container. + +**Option 2**: Build the image manually. More information: +[Build it yourself](#build-it-yourself) + +## Build it yourself + +### Preparation + +#### Optional: Download Capella manually + +Download a Capella Linux binary `tar.gz` archive. You can get a release +directly from Eclipse. Visit , +select a version and follow the hyperlink labelled `Product` to find a binary +release for Linux. + +Save the downloaded archive as +`capella/versions/$CAPELLA_VERSION/$ARCHITECTURE/capella.tar.gz`. + +If you don't have a Capella archive, we'll download it automatically. + +#### Optional: Install dropins + +=== "Download automatically" + + You have to pass a comma-separated list of dropin names as `CAPELLA_DROPINS` + build argument to the `pack build` command: + + ```zsh + --env CAPELLA_DROPINS="ModelsImporter,CapellaXHTMLDocGen,DiagramStyler,PVMT,Filtering,Requirements,SubsystemTransition" + ``` + + Supported dropins are: + + - [CapellaXHTMLDocGen](https://github.com/eclipse/capella-xhtml-docgen) + - [DiagramStyler](https://github.com/eclipse/capella/wiki/PVMT) + - [PVMT](https://github.com/eclipse/capella/wiki/PVMT) + - [Filtering](https://github.com/eclipse/capella-filtering) + - [Requirements](https://github.com/eclipse/capella-requirements-vp) + - [SubsystemTransition](https://github.com/eclipse/capella-sss-transition) + - [TextualEditor](https://github.com/eclipse/capella-textual-editor) + + The dropins are registered in the + `capella/versions/$CAPELLA_VERSION/dropins.yml` file. If you're missing a + dropin in the list, feel free to open a PR. + +=== "Download manually" + +You can also download the dropins manually and copy the dropins to the +`capella/versions/$CAPELLA_VERSION/dropins` directory. The directory structure +will be preserved. + +### Build it manually + +```zsh +pack build capella \ + --env TOOL=capella \ + --env CAPELLA_VERSION="$CAPELLA_VERSION" \ + --uid "$TECHUSER_UID" \ + --builder mbse-builder +``` + +
+ +## Run the container + +If you want to configure the JVM memory options, have a look at +[Eclipse memory options](../eclipse/memory-options.md). + +### Locally on X11 systems + +If you don't need remote access, have a local X11 server running and just want +to run Capella locally, this may be the best option for you. + +On some systems, you have to whitelist connections to the X-Server with: + +```zsh +xhost +local +``` + +It allows all local programs to connect to your X server. You can further +restrict the access to the X server. Please read theThis feature replaces the +read-only image +[documentation of `xhost`](https://man.archlinux.org/man/xhost.1) for more +details. + +The container can be started with the following command. The `DISPLAY` +environment has to be passed to the container. + +```zsh +docker run -d \ + --entrypoint capella \ + -v /tmp/.X11-unix:/tmp/.X11-unix \ + -v /path/to/your/local/volume:/workspace \ + -e DISPLAY=$(DISPLAY) \ + capella +``` + +Capella should start after a few seconds. + +### Remotely + +Please follow the instructions on the +[remote extension](../../extensions/remote.md) page. When running the image, +add the following variables to the `docker run` command: + +```zsh +-e AUTOSTART_CAPELLA=$AUTOSTART_CAPELLA +-e RESTART_CAPELLA=$RESTART_CAPELLA +``` + +Please replace the followings variables: + +- `AUTOSTART_CAPELLA` defines the autostart behaviour of Capella. When set to 1 + (default), Capella will be started as soon as an RDP connection has been + established to the running container. +- `RESTART_CAPELLA` defines the restart behaviour of Capella. When set to 1 + (default) and when `AUTOSTART_CAPELLA=1`, Capella will be re-started as soon + as it has been exited (after clean quits as well as crashs). + +### Use the Capella CLI + +You can use the Capella CLI directly. All arguments passed to the `docker run` +command are passed to Capella directly. + +``` +docker run --rm -it \ + --entrypoint capella + capella +``` + +#### Example to export representations (diagrams) as SVG images + +Replace `/path/to/model` and `` to pass any local Capella model. +Set the project name so that it fits your Capella project name for the model as +it is given in the file `/path/to/model/.project`. + +Exported diagrams will appear on the host machine at `/path/to/model/diagrams`. + +```zsh +docker run --rm -it \ + --entrypoint capella \ + -v /path/to/model:/model \ + capella \ + -nosplash \ + -consolelog \ + -application org.polarsys.capella.core.commandline.core \ + -appid org.polarsys.capella.exportRepresentations \ + -data /workspace \ + -import /model \ + -input "/all" \ + -imageFormat SVG \ + -exportDecorations \ + -outputfolder //diagrams \ + -forceoutputfoldercreation +``` + +### Load models to your workspace automatically + +!!! info "Technical prerequisites" + + The feature relies on an Eclipse plugin that is not part of Capella. + The plugin is called `models-from-directory-importer` and is available + on GitHub: https://github.com/DSD-DBS/capella-addons. + + The plugin is part of all Capella based pre-built images on GitHub. + If you build it manually, make sure that you follow the ["Install dropins" instructions](./base.md#install-dropins). + +To load models to your workspace automatically, you can mount a volume to the +container. + +``` +docker run -d \ + -v path/to/models/on/host:/models \ + -e ECLIPSE_PROJECTS_TO_LOAD='[]' \ + capella/base +``` + +The `ECLIPSE_PROJECTS_TO_LOAD` environment variable is a JSON array that +contains: + +```json +[ + { + "revision": "master", // (1) + "nature": "project", // (2) + "path": "/models/directory", // (3) + "entrypoint": "test.aird" // (4) + } +] +``` + +1. The revision of the Eclipse project. In case of duplicated project names, + the revision is added as suffix to the project name. +2. Optional: Can be either 'project' or 'library'. Defaults to 'project'. + Ignored if the the directory provided in the `path` attribute contains a + `.project` file. +3. Path to the directory where the project should be loaded from. +4. Path to the aird file, starting from the directory provided in the `path` + attribute. Required if the `.aird` is not placed directly in the directory + provided as `path`. If None, the aird is searched in the path directory + without recursion. + +All additional attributes are ignored. + +You can use all images that are based on the `capella/base` image for this +feature. diff --git a/docs/docs/base/capella/logo.png b/docs/docs/base/capella/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1dcb8140d30594be87af875783cca0d505384c83 GIT binary patch literal 71882 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

@+x}h^akLg) z$}~D}=zXws3w-Jy7AHll1(@IAzY!CKd~X7fDDnUO&hw}_>8F4hcW)q?7KA-%YqGOQ z-7IfwE1%vLQ{K+se`(?whCq#^y31)2=u@25rGg`nU)^?L%60{y^QwDsjWxTVf=X)9 zB>!dZHr~8j+PGsinmtZ(FutD(F$*1v{fuiDx3cs})Ls9qQF)&0ixjugObOu&J}g9t zM}aHxRk(~@I=1ec#@Vt|ywR&iS#hoN3~K8--Ul{uM%;l$TAxo8gcl(Xok@TEIBK{U zU+salp)e68@s zJs=9}!rd&Vd4ZKGHA0V|`dRW>SzeVUgB>Q%~y~<4lO>cQiS$P&D{N8Tg@RGj4 z5f3nfK>1Q&1Np~Xl~=vBHn)nh?S0-Bt~)VG$hPKM=|!ZO{u8023f;ki>ukI ziORz&iWXKssc_{r;lq@ub1-Icy^Hh9Xxgj6ZeWaaoGOY!e4JE^+1Hy@iA$9Ht zxs{|G;fkiIT6dJLN05UcyN~z60;BT6t#KVAR(JUY@u4GeuG=?rWHATJy?2NJS=E5b zUCC~cMYLhNmM;W6$dHt=OwK_qp}5voiS_k$uvw9Qoo8Sz!=p!yr$s8em1-R8;0M#h z6X!@-fa3Zi@JAlo_QbJ5@qMY$k6o2cN{DBxq%ajKX9FXgM--^s5s0E*GJfmPAKWc> z>r2IJh6P-}r!|9$CZJDg1sb*1`7?->738K$PeY3HuK)g)wA~Jg0RPl1|9@MsTX$b{ zKwl=5xWpJCQyU8#)5p7{LIygvEz*#a%{waT@?~F!mePYAYb-kMO%3CEh62isZ4Y`E3 z(yZoji9q*imjRJOmMH=CWMlUB_Iv}zHga-TTF5?>in{R$^yf_~mvN!@cj4ERqYaB` zI(|}Bq1snQ{K_5M43jI{ehi$=-OixR`hV@cWmuG5_&o{&5|WAnQUX#kCUVv;4Y|*a?u0qUOwNaUiNdBIB*)IuQPeuPQVI$+2rQ0Br7dXguyMI+7gx`DBB5$!!1J zxcVuI0Z%O}L3BoYprAE8qO z0nk0d4-v}A4U*NG_A*9$q`OZ8NA{jyzi8)t zKuV4#%KH*r>=S7<9>%B{E~&W1zY9{Hz|pGj&;A{5XUHod^M_$WB@b}jtPJ*1@K!9* zH@{l%nj0Ry$U~FAqd#9P<3IgA^)4)d4|ECgKm$uk5nD10D`4J4Q*o0d0t*KqmIJwx zqUumwWQvxQuZ8tzo3#(>VCfx&9pLeb{4Gbk9$J?N5zhWd6a@f}0KaxdBdCfp^+vlG zBDFeL>}5&$PUZKCXS=t)Pun{cny^llXdd2m=GdRwDyLLmQ10xy!DlSlb?j;ENXSfk zpIC~Lmc-5QFk=hQ8Q4dCXu!tV{O8ToB;Y0y(fj=st_m>{k6XMX`AsmvJ|pyDD?A0G z1A?x(iU-p+1p3j0BR+Z&Nr6J_e}+fa@r8vOTz%4GE2zI2h$U;zin30QBoFAbzkCz= zx~D?uCtRj{@K05tfeSt&PF%!MVLy2r&0OC-0=i!o<+XwKzjcV8zhU9()T2Q{W_7ER zR8IK&X;L~to?r%4E6$Iy5CY%=(YO8`wS>to6%YZ&XwIq7J|QQ6=O09CI-lN}?@aD( zgxxdFY>}j?8MsmPzmnPSc@>hI|FD=cn(eN??--}~lkSA9ZsIuaBh#Vx#S?ta{;Q;- zp9C{<){$&86OTEP8X}m~fHR{uRL#)RwxPfORRYMjur4jxH;Fhoe<5(CVk0N~^at$x zx(nmd_c7X5gx49R8)svFe*j^!E_X4$k%is-jb1t;rE(>t*vY`(5<88Bbn1`d~Gv^a-oKyBMkq#ZuA z8G(TA4^*Y0NBZ~j)Z9J_ne>rLxJrY~=ck8bH=Y#y5&3H2Sl)|ON*K{#N_f6Lt`K1E^si?wZ|RMAiyFvVkfrTh%E>MKrKj6x|g&*@&rr zKZocP1oDBy&4haYrJJR?e7xn*AVPGCruDAD!CQCv$c+!#59phH-I7ClBoqBzol0Tk zh|POL6l5K@0Vb5Tv0%IWXh>&?>FzuI+OOC`^I+0F>u1|1)~k3(M1tD1lOeLaG|q*v z|DDeJQxotL{q#$v-rnjtPCu}%VzQeI)h)I1R3V1Vpsf$Om*qKqGI8AS?9yhMT z2e}a~WP1;F4hCA#>A~`->%zcnG0*^f=qVKLQBLLu_9bL%{($8s1mSQhiS2sL;HaJspg;tcvilhzdWENf!k zH2KJmYGx&FcFniaH1bmxkyr=4%_E)8&a;dYhIuU?{|rUAtE0_9~ab)sqq;RN$i%$EUU6}r|Z9sXMKPO z@YZ`lO`zCQpX3aP8QpQkTC|j34^y75v^jrI3YVmi%4Rf}D+VTExt4SEwMfI2@wDD> z^<&wd&=WuUAsygW$C^Klo0}D33URE?OvYQ=YsuX5+PM_MnynU81#4$~wNQK=nHl)) zaGwua6o#z$paE3QPs7l3 z^l&%@3#oOmI87@0&-W%$prCE5UwHCb4B2c~lP2Ng5Y8WfqGI;p}Zl?=|aCZYT{umH8IPA@BKU@KI7z7 zkAOoupp(v4=Ie}hXp7kSDc5f5txRZM2;|&Zr~a>O8hOn`XMWuWYW5^JC?C+X9%vAX zJ|W-?NTZ*O5>S-|H`X)_Qv5wn~%B%^_ZPon$D=uvt^y`h%8N+8~TLrti9CDipPphVfP2% zs=Jtw39rkJ&2aTVbT*r5`X=WCT3-^W5yt4GLuk;zJ5=52yv;x)>bK(@Vg&9072Xl# zA#T5A5sdhYEXo9FVKoB1gs|M9YD|#2bJ^sxSO?Cknn|mpJ=SJEPm@uD>(F9bIoJ!t z#*agsc8NbsKs}x4_x)*k0@1h3iOp!uKL*5BL<+`*vB_48-!5Cc9X!fs2qr9iS)I;y zK+&4XGs8O3cXT=8-&`ICkb8*%u4Ff)DxfiAks(}Nv}|6o*W}`mbEZK8uO$c-oi*6c z2S#WC9$~BAS-7F`L-)o|kYr$*F)?)%A@uI|3rS+$khG0QbN2T;?5(x4&Z=Py4vGJs zCejo{E`EH-HX$c(oIF#}BmVY*^;50#h6L5Rut+21r+854AfDX0-$wtLo!34c|~Kf>d|WkG{5tRXfw zcnl`j?V_y(>4?CR`E+UI(C%DyeSI%LD~owv{Yu0WLx9x6h2}$Ob*$#)n7bnlla&54 z0JSLh0%hkX!J8Jp z>Q8O7WnVXo^yFSl?A4=rL%U@+x~%8!%^w!8mx_cs9o$KN+OS>bOyo2C}*0Tnrw{Co$Uv7a*U-*-qaMqARQ5^J>~ zlO8f=^{w9p7WY}2U#gff%rX)uxU=fCE-uxa25g8^#>)L#Dm@0F;uTZ%gO{G+(J!dw@v=>=+*0~WrBVr* zI|Cp8+$a6KP?;+7-GP7acu2grw{cnOY5laBa^?JWna(O(W2m@5N~hb%{xh}Oq40~$ z;Zoy_)tnHRF8kAc#BxpPA}K`iY2*UQenWll0p0Po<2(Y$y%2`2(i+>B+lLdQr9Tl? zPC8=ZzNJm{E#0VL=v1ZX7I`Aq6OFjibb@;(Gu;+-sWPfA0mT{J`k~L;>=Xy_>%h zuj@!$2;NR<^hIy;kKH@1GEsgRa4E|<{MAWftzfBGn&~;Lcx0h+6mrQBYuEMqVt6Z- z6(qZQ&uiq(HwJAv`ApJQCroCROfFJPU`MH4G?W(bzHrHp9D|n{E+xR4=Vt%+!v$Hb z3>yaK5R(85)8)F%B`>p|Z-Rqkii2Yauz{?T&u@HA^zO98Qn`?sAyT@#>KDpG3Pxzs zcjYLyQLyw1)_1lmWo1zH<41dT$PIvmBUXj*%^583G^0f6ZT#$`fR@T!HHd_v*C%1_ z012#l%MU$&^^S~Z*4uF<))x(pI7wi<$8Cxki4=a)FqX}VcFbc!=67o^F)Dl}Z$_B# zx>1iAdH0WL5$P7TDNXo~;=HW(JC9=bjepYm+t*3cmA`~nK1}DLgCDWTY&t8kW6jgoG~HtqGduI!7ygcPUTf3PG1+mYv6(IcYJlJ85O)I zU2ejtnBNF&D{lE7o!IzdeuvC7d&CvK`P9>GFK-lg4_76TDXTnEYwnQtZ?|8y$w+F+ zx>ayjV&#c1SeHb#8D-#yX=VaEdtz-o!VuT7NT$U z{pQQBPeTIU)CviJ4ZSb|-`SfimjNs&K_}DYJi)OxZ>5^fvhlv1PkM&r-$)mERv5>2 zLuHd}-ptXE+XVltH%=5gO{%FZi(?H2r!Xt|Ge?qg(t-f;)L5w~_NZL{Ka#E5_2-$Nd&s14P=|DBO9w)9@&J$! z*~}Dc4mz>Y8MJ0XzWQ}@)jHuA(`X{52P@dnY9eo}ZJa_5 zbqX?tMuj*n+~htK@W%R}00v_n3=KF~BCV^#aqa`%uX4EF2U*^aVhe-~C|Wlpt=zvc zYTzpduu79kU-w^)lDopX>^X`=Wp~v~>av`2f`1R!xs)#SO;TX@E;1L#Uyv>JaIi!I zvzDCKAd|h|2hv>S6CEK(80INb%GtG~fz!T%fK@jzG%lOYC(2;+TlFx; zhgr?6u~W)p*P3tXzzs7kfOKxz7J#NCy6iNg;nRoohoDT*`~|$a+uo0DpuV%yh(h?hjV@;wHNy_x%h#YqpLdc9|pQ^Z#Cp6h3FeQr9sKfJy* z#8dkw3Rs6Xwv|eF)VUl&00|O!RPq2rfZ%I?5^bpO{C>?9QLHuTt3fWZQDTdVpB`ArZ+x!2P(pnE7T{xIdZ$?M=117Osx3_*TXArp9&xCxbKaCpD4Y4$H|Y$v z+}*Qi2Pc2)&!NQXLs@r~%q~>_Ep8635+`?BxaEXvM}`3By=q6tbjFs&8amolwVWUc zAGJ6MV84F^^SxF!o4Rn?PYfLUHcLtYDk8Jog=F3Z@a(Kw*2p@bD}AaAO6q8QtZX%# zOWddDsAuD`;b1>7*a9dNUelxPrpiIhr@(71;*be2+_3=e!NGm-I1ot5am2l@F`9UHXcta35D^VkAkb2t*-9;XU-TsjBMt#6;3?byXplb28Xb@kpN? zo~f!0!xcvBiS=^j3@yCg*4b>4jfSR7$O34?OQica;=1k|O6u`!ZgSD$MMDQz8eaWr z+Q~PwJH#w?wvr!k4eRhnY#dR0oVAricE!6c{{29K zHDz1!RKjv>es@mII+D8mq)A4GL&*p7;(Pe^_O|U0Q%jczz%Rr{NtxWqvz+})s=InG z`E*W-fhF^BUG_)&lCs^evbtp#QZQpyiJ%MV5q^NvQ<@XX2<#ig*#>0d0b+^K zzJW+^=n#mf$o3d=rnKHSH_wEAC&xKk$?nf656J; zV3qEJRno@5oF|2q!H$m`13=SEflu;h&$Hr01S1#{&TA?5FNHZiwi7`ihi&Ra9P+;=gWEn%_7J@J0LqLASWYI5(*}?y&fF0?I3f!Nx{SK zHc;>Fu7YuzZJ8je87#X@6E&@91_Pd0FYO*9$@K_*du^c#U2+VhwwCaeJ#Wq{<_Em@ z&1yOZXDuqVE&6Rmd`UuMWD^=rRvJ(}B$ggD?10`{?s;b)_~ECH;niB6sV z@;=ZdG39fHUQvd?9I2&T^B%w;;FBNwfBf53b{pIQ_f-{RfRQf8YF;s@v3s4XvQJn7 zRPGi31AuU;npZhlEG32eD*62XozvZRJS8D|EVRqVO!03vrRC#QQ|<47WKt)45dI5h z3QdB~<*Wjd5F!BoQAK-<6e|mJ)$a*w_N;|2C`H2CMe`n)`3G)beXIZbalFI4S!I8t&QS`Tzdj{*Jgyv}2<`XY z9U?To@LCT9G-~j+Ub@9ig)vba#y-k>g!y_)fj!%?G?o4PH^MmF$i=TvHVS2teMdXt z@6Z0{j<4#-jCAFB&mNmAAq?4Y_pZggK5Wo=m7_)-7vAE$zQ)GB(PeX4IYvryfq`Qi zf@+))dsx$z$)*VTciv7@1XMNY7njAvk?nZq)!Rx7-ZlwsfDfPtNK#q(EcjdiBGrmOr#anyI9WmBC0onQp+#PM1y`@sxYfunlm_ z!|yyXZXCB8^JbK2Sd9}=2TzljWRT`A0)@gvMV|hS@^4DYiW-6_aD8TAAP+G4IFJ4= zQ@vzWMDRb6H8#BVrH_KM=oP2>450`U5ODL}>Fmg%GrN5%>uKDMfRV#d)FE#3n^DG)11$fZacl@UqI}1 zEl~llK)qo3^Ps%+yMNEPqoE<+E-N0Wtht%_Wp{L*-bqP$_14}V@HqSD91=jaD0g>v zqHhH8t?DgpnbE3;P$l~Qt3FexLl}5Ceb~y$!I7ApZ0c>By1xEwAT7fjcoBZpvQSn1 zk(f#v%u4vV2e7R`1wDtd*EC$MN zUXM5^0|Nx@k(WP&7l*P5uqZ8XUj{~plT$Mmc8&MrPjFwxTan}7LK2e5Tk;*xB`1!> zLT%zmubQB7d2%n2{>=+PBFbC(_wki9s6{gZV|1qhf1ec8T1cuF_ze`QtK-{ts%q~k zmLoJ;%9OR@ckgR}*YHr}U$PiPmgo!XFQWMX5%@&F={q370LjPU?Z>sZ)(=Y_OdWY5 z#nv2?hDUm3(@@weg3N?fRzJjS#|}5rAv@M}u)T##veaF+*d9m)u-eXyi&HD)c(9*5g78}IDe1T1~ag{TI1@JP;g+k@BgL>)aA2jFmy|6}&6sdW5Vf2%Etc)P_CPP22gs8^#VhZ}wDyBs z90s;IbByhT4YM)sjiw%iAiImL-f(`h$qXuZs6uWq@M^jQbqeqyPhbuK$;R00F6;Qr z9e7wrvq?)PX1?6{} zGX8IcY?6k5W^O|NqD42)gDY<*rtZ#oA%&O&yi=lN>U%$^*!#BS#ujP3WfG|9&YbL2`}Y`ONC!??0(a$L_5VLB+r<+HnhxfXEDEVFx28wCix?IoEys)O5co(ZjG z{*s9beVUi^QxaIQ6f36&z<`K^C1O{j=px^%b?Lv{s+u?nE$z40JdHu`q(PaRczTDQ zIEj(FGN}+FWc&9_$gGG=1dFG9^J4iGy0h=^Tvq)A<*APGfN8BD_m7I@B2z)a_wj~q zy1x(6H&#pTJ+=iZA~ref8&r#IPViGHFrbearjtpepy9GI zMTc3P_wS@>J^9qbt;ARqoRuVapd88oI*3v{gwR2EcE~RP`W8~zK>WJ=#yn<4@bVCe zNn&ozV2>x5tD%Pxl!`~rwMr3EdBH6@$_TsT;IJFUj2Gnjs6z`w;Ll|E|_)){|tVs3wB6ExN&feX>vtO<~!~ z>X-YBGX63ulQ%p?6e09BB5!vCGv@`K88}@vhDg0_qG`)0t}!dR`;+K3E9_1?uGQBq zW3mev;ug!%6gL6xzY#EKNYwxHOa`adadJHGztTP)Ad41hN+7tJ{08Ya2V1*Y=h`+W zE8;>I_d=`?I}DrbvkYotc%N#2T}|685A$h$`k4Sg(B~+b%QhLz;;V5zl_u1I_ug}p z3y(-Jy9pJ)uaPDGZt#au0eLA2XT~h51H=*J>56u$6at=Hgu zx=}yV9tB{uMQjx_^t@D7HDRf4aR#?W{H`Y!H)+UqD7qD_|GvKK)%kjq%pS;X&8qp? zs0S}Nm7V&Ai88mlwa1+d!_QYt_jVTsvt=@_ZoI}2pQiYJC?QXg%Ds4UK8#nsn}vPpS5g34#Ct{yg0k4HlHy(I+4HzdVQP_ALypG3~uMQI9J~W6rcYjfYm2$~1 z;~bXk3~`*ldiCo39OcJn!kZ|K5*)E1Nu?*}PNNSMNL#I!3LDl?vb@fM|DGR&5ZG!w zJiVKNfNLwyh`{ z5@c0Fl>&rNHWOAC`!IXg3Unq`WOGO6#0MZ`1y#;~Ibq~uWnzSV4vcG%p$(+Cz{juu zkqgQmnhjwCrN}Zi!@CmI7@F?m9(nt^;#Wpv5ZPlTP zTWIDY2|kBm382Dj^)^7xNk-Ya`1$H%^EvlMvoK?x$bdq zd=z3CwX=;vD+COnL4{ul3HCXkgc6K@Pg|p}M&7O$mylS@Ov7utVoNKpGzU(z^~@A) zk!ozu?{*dl0;FHyGz8M_@6Ft9a(JASd(~drm-Z^H-x15I0&*>~J2ZOhZdw7yP3V?+ zCbW~d84I>W^L6glOEM#+($qpuPS^qbClEx9n?SA~;1l;n8kB!yb0K$kZ@`v1nNRH( zM=b6SZba_fB)JawKp@mI3}yQo2{|2_iLOia)d?44{E zzsKO4E@OItU$9+zb+grVomPV>*<0rCc3N z<*_`yj`|MkZ=-mkw@I(Uj;4YaqD>)Hs-WCQMdc8nXkG8O!O;?%Ljle)>7S~ciOoYa z#i{|OKj+^mE!U9OkW)H04ArZMXcaU_2|vi8pIc!G zp5Sp5nYQBvdb7D)kmP+nA)S;hq6ES4Z|M?isjzsm)f?Ws-<}JpKV-v?f{EGb+mJ#d zvVmL{bcMM`yQYz~hxA>6cr>5lT|AT=?~@|hcWL8W8|9)GVVd&$42D(*7u@ZuA1kY? zLtwa>;&K{CO6m_=db{;D^R8A+G3mq8(~5ToceMj371))&+$j`Z6|%^Db6=IK;4%A9!g$~13W|z zc>!K2UaNKe+FIAuv&ySIz&;zvDZ^HVeT=5J?Tf3Jgw;ip`<^d(gsBtPT?Sq%#y-ZY z&Q2wE@-SqVK>OXdN0z|A5B)KY^a|c-%R0aA@S8 z$;)bAsd4^)jB5QqX~+Ll+Z~fyTld$d!5o&CMl*)bzo65j)vwT>Xp4WTn$IN{Qg0I(Y3zsK<0E?G^n8$lVQprz z-yGbpuCzVeox57p+kVQP{mWN7VTet`a{pR#yV=DLj~BsDsoIhj`=XUQK8fjX&9|r6 z@G!s|vahb*)OfQFzpNzE?eSX0a+{>JTVTRz&zUp+(oXKDq*}MMPNv`8C_A0I zpZV~qG97kx$abRC>H$t~z{H9i!GJZG2E-);cY2SVP8yKy2CRH5&t}Qg6%{sIi!>_L z@6!Yc{3gp=lN(Sip^o(Z=H@&92*Ex;e%rnM1Z7k2x_(W8Cn4->(|-& zx>%B@z5rK<#>d^K;V;^fpznC}_?>pnsil`2td;Jq-v?%j*i&4TEWGafAya5>9kD+Q z($zG4YEWq^7m=h5xjCQw$Gg9=G~C#5OMyqTs~a^pK&@7XS`~x%GaD-M+>T9&#;db% zFOlDcI=4;zP4*$%6y*mja%>S>4i3~EUW6i>3c&}s3J8QgEEp0w9nFYNl$lt%*peb6 zD6f_3Zaz1Wx@wbKc}cW%Qa_*Cyf|$)jWbM2y}LBL0yAvzjjwb@n*Tu&HS3K;CV)K& z`zVws4K~nM0*Tn$yMw)7VI~3Z1#=md#74Hsmb+_1WzVk0K)#$*exp|(4K&1{xwYaC zQ*u||)54tDBVf26jY7b2Oz#Ln!!*J+u_yi17oN59I?9XBSR9lbebd=_qYqeI`rFN% z!ct@xcEkT0za#e;UHbM*OFxr2P?VfFiHPosrW!xT(-v=j7b5 zF}sK^&DYZW8i_UJ`c%}OsQcXs@Zr6Xb7a{`4$`lc*w_H|DIXYQzamM0Rhcx)xg=9}6Gndi)1NK(7R zZp6!!@9gcB{H}TbQv)CXc*y23!Hk5&WIn7T&`h1z2U;Jt41@W}xSl)ME2&=d08r!4+{})~7egP@l;JW*yV3nL>+Jr9|VzT@OYgJj42bZu}$Q=8K z^#>m6Lc)%=Ty-97=x_iUFDvUc(pB>DGB@4?j+du&hUsqe1Amqq5%B+zh_qoSUr3=+CA;P$Rq58+ZW=_W zpza7E+Z=o9rl26x@xL$%kn&`(j;2gSwG`wxicNG^ks?_R)O@aIV9R`n5whTnrNKYl z-@Hju=e-RHIGku*-KrOSQsF&yCXx@+i>;f05R9-cD$2Q*%hpU1TV0Pyl z6G*7^`r6SCrvWMpH>DrG7&eMIhI4M0$A3w0l6|T_{~oaV8*kC8EcR+T5WGuVw_dLbz(i>jk7$IiyNSnzVx#ZwXG8kc;}K@3 zWai|@45zTBcZRYFDo?dqs|zRqg%QjRd+kahb?$!Gk()_DML1FpJG;a3UdG()D7T>d z#dg`Scz(~W9db$Uik|@&jWYWQ<;*XRuFci#r?s@Gi!ePLKoEALwyCsLfdeU6bhTg+ z-j5g1v2;O?sz-@PQCqSqsZ+OymmS0P`U&7HZd0HgD!}<}8x7+UM%Zc&GNv0=;K4T8s3FeeIfpQggf#y{n zuDb)(RE=W?4eOQTEGbnFE>`xuJm_{J>fv?P{{X7 zdYJyj2lrg$*_2!L_KM?~m96dmT%*Z~%;g2z3$aZBTT(RQeE?-5>Umf$J}qO?Jd*Cw zGL6E)+2xX&v48H@`ghLNe@en!nAruhj(sxY{?_wd_MGqXP!Y2<@1No^cfu7m-6KG{ekP4N@pY54H)u3>385`P#cJx( zyE7eS(BTB?#tQM>)f#c+Cx=SQT%Q{_vSqKHgmiq_KHhA|VeY&KNn*F^-n(VQCyBN+ z_S)w3+Rorx87;J&8nj2x&D_%P{DQy8Cd?-~5@%`fHD}yFQ^jFc6?2+U>~(SIv7aw< zE;dm%+r8Sk2f5?-QF?M_=8sFgp%HPKBf zIpbz->Ee9U&K-S>HnzbIQgl6W#8;@B^`q4x)3Br^9( zlsv$raNT9edw<77dSJ6P0P2WAuK4iFVc7iaORCpW{WueiK9RYm5^nMtN`%Bl1y$&G!a%INdK=G?wv*i-`GfsK^b;H$rnEGB&JBSN)1s!8Fx!`UY{Bdo6;J2+g;Lx= zz1BZHTJ&D)D~SE_*3+)0-LG2Q#lKM=X+I;`K6{83Hc!7~lq8isei|XGuccKbICuAz zR##At`t#?E5*ib9QDwr$;9K&QK9YNR=TUG{rt#4k>3q)(qFx zQuKHw`Du*5M`6jm+gEVUkuM& zqI;2k@gkVBS>N+T#L2-|e%RUgmoj=i7Dj~iL=BIS2NUnX$u%6O3iq8A9v`@s7BppI zw4txlx_=Uuzd!REb+R5NGXSH>=>IA+rK~*M|B=*BcjTE~c>_NYXEAEauh!a`5`R&y z(Sx!~#>B34WJ6^HwRGn|E)UP{T$(vyv3c+L{uoWK;mN^E404-de_$H9vJ@rV8$mB3 zl#=kaFN!@Wq8BsXP`&>;CXL=1>DqMMhl@k2bkK6Kv{BqUV4K}h(f1-@{cKvy-#i|r zp3#3prv6J9JOloLIRkEC6N&h%`;7b9`ki8r`KLm>nqRjwJR0xBB)KY3O5c6I`{Mch z@{;nmB#anYbGbu-5noJs%NA-f4h|HDdiAHZ{4OW^E566=afZ#_5^jk$(i!`VD^&M0K22eiWiGpBb)_ zVBB2|hBZoGsQ*6WhAqd;#(6c5QA|ChvXX5!(W07hbAxj>FrI%jNK>8d zpuSB|fLT#JSu>`EBTOM9Cx^%APT2BzzqDT2Ij@Py83#RmO8vY}rt{|qI5+|5KLb;@ z(L&%v8St;+*&l5NQhTBXGWy0Voxfzo=A@K9adHbYtM51|A(EWJUotrySHtU zX?<4~k{i1+IAKs*jqY_Vm~E+Pq3JudL!K}P(&FQo_P_mcCRsH;X3h^vK_$fsV#e>N zZP}GR$}OH&pV;nSnb4goFYGuL|F=t&#C#NhgBBs1^f1z85tWh|bxxXNkz>qH7-(?* z{91%3X&yDg=rSKIhl7(0jXWCj`#NF!{7$dJ>MX=p2nvHz94pEQQS{)mRoe%YmZsFj(hCdXoxOA4B zX9zG-HhNA`y58Vbe|7*rLv|R2j5a>!S=lXk$7WV=mN3hB6z6U9e85nvdC~RT>?i{o`StI|u?IOu zg*EflrN7cWAeA8tRF=WJgU_ecX6#Q_FOF;s%H}PO15NO7a40PPl6N*Gd!=h_j|QwkUM6*VnYTpXMh#tvl}QLV@1V>)y=I16U_ z+N*Jz!-Z0*9f@_F4uA!I*OUA3)vkk zW!ARSQyCB1F6%}4hv=j+t-0aiEU4w(-tXETR7BHT2(fy`(y>Su{n_Y}n%!Jz=#~0N zanJy+k=e3od%{4VNQ8?M=(y-mJsOUWp(F+?M4)1l5D5;>AeSs?od5Onf3?8>YJvY3 zw?M%qAuWZl8p-L8aU2{RRxABy*3X_jzyU)W2Ny>W_Zki%_|skR3;R)WbhLSEg@g0U zJn|m+E4!xo=uAZeH*`&NBry`LZXb@BcSl4FY-q literal 0 HcmV?d00001 diff --git a/docs/docs/base/capella/logo.png.license b/docs/docs/base/capella/logo.png.license new file mode 100644 index 00000000..e389fe68 --- /dev/null +++ b/docs/docs/base/capella/logo.png.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Copyright THALES GLOBAL SERVICES +SPDX-License-Identifier: EPL-2.0 diff --git a/docs/docs/eclipse/base.md b/docs/docs/base/eclipse/index.md similarity index 96% rename from docs/docs/eclipse/base.md rename to docs/docs/base/eclipse/index.md index 47b5c4f7..99fda923 100644 --- a/docs/docs/eclipse/base.md +++ b/docs/docs/base/eclipse/index.md @@ -3,11 +3,7 @@ ~ SPDX-License-Identifier: Apache-2.0 --> -# Eclipse base - - -!!! info - The Docker image name for this image is `eclipse/base` +# Eclipse Base The Eclipse base image runs the [Eclipse Project](https://download.eclipse.org/eclipse/downloads/) in a Docker diff --git a/docs/docs/base/eclipse/logo.png b/docs/docs/base/eclipse/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..1e70a02e67df835d10fa57a9a1e5baf350157524 GIT binary patch literal 10747 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∋D`5@n9j0yM#jL#7D7R9Svi323dT)3`MaSl^?rbmqC_4MX}Uy@E>tF zn^2L2<~h;eG5IlLI11sPtUgq<)3hH#qiE*bItcA8deU^1%JKFO2rnZ~3g3Sb7J7qh zo&o-7svWGE;(;X^ql0nttlr9Ri_%iKN_rL=$nv!CGQ`5A)mm@bD8oApI^2-4X=Ho; z8Ab4#HJDvW9T<(l&Djlph~^tdjra32Hg+n~W)6yf%~dDynz@oBY0K2RY(@8eC4}3p zKIr19tB9K}T(Z*3|+%C1w zrWx!+lp&>0=USpz%!|N4oC(;8m_S#NKVqR6PxT1b!}@f|J!%E=QgUzmcJjO99%Wem z69T|o8^4}D00ZWC%jk)i)w>;0`|myv_jYsfn$@jFCR7EuI>jA0VJzmlMwehx1aG*X zLA8xqyFbLF(Zbj01#1G%(aYt6S-1tyz8~Gut)ra$b?5q4oAWB}3w;1lTLl0Zm>q>d zm6^-l$toJx7OMM1XN-98~-U47OI*;^e;y5iLc1`HR%>5`zucZ3frtP zazr>xm*Kg88fG+>e9(16XhIGAIi=~vXs8LELHM3{S2k*0ysKKe7v>Zr%shN(!PH1DEI6ZxNvX2GFOrE(48k1u(e_e0WOOfDhMC{%@Ypg=m)g`;&_JZ;+HHN!pbRl%J*4I}OwxyZN@+58+qVXu3qrB` z1^e>7X@_@7Uok>E5bZx}c0juUi+&o?7L2Y-oI@y6ek)FtN7-P+#Q-4tJN(^3PcB+d zD_0N2L&6Jb^F%%r5lQsGh)XfzbkvIA!CyEA5N_Q*fNA#CKYi4tyNdh37(m=<0{JU2 zZwa}3S|Qyq5s}F1N=&Cu01%uG^kc_-%i27I_+uW3S}^d}036X8xOKg(RP?fFTafjE z;s}7@ir`Pc>4V4pyQR#R)veM2gdiy8$3Bbm?isNW^(wArK0p+tM*$YsLL{&|p^C zjtE`8`jHpqF@r%)QIU%`;!fsaDP&2`!3 zObD+DC+M;(VgrWQc<6Z{m<%bs^JwW=x0L&>1~nN2;LHy~@JE=gGh-9_u*Jw> z^?YZ7tH74&X9}T18k?}S39EL|W&`yXT56G5nsBVJQ2oKjAchQ3&7?S3VA7d=m;Xjb1RDlGga^mo#9$Y*1`AYO=rY<_$>pw0?yK+q&{R%r4j zE5fM51*yNW_9LKuhNofvdX? -## Build the `jupyter-notebook` image +# Jupyter Base -The `jupyter-notebook` image provides a JupyterLab server that can run on the -Collab-Manager environment. +The `jupyter` image provides a JupyterLab server with some additional +configuration options. The image is configured to connect to the same workspace shared volume as the Capella remote images. If the `notebooks/` folder on the shared volume contains @@ -14,10 +14,10 @@ a `requirements.txt` file, dependencies defined in that file will be installed before the server launches. ```zsh -docker build -t jupyter-notebook jupyter-notebook +docker build -t jupyter jupyter ``` -## Run the `jupyter-notebook` image +## Run the `jupyter` image ```zsh docker run -ti --rm -e WORKSPACE_DIR=/tmp/notebooks -p 8888:8888 jupyter-notebook diff --git a/docs/docs/base/jupyter/logo.png b/docs/docs/base/jupyter/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..d4fe0ccfba908c408ec39f0a0eb15f06c6eeacd9 GIT binary patch literal 81419 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 literal 0 HcmV?d00001 diff --git a/docs/docs/base/jupyter/logo.png.license b/docs/docs/base/jupyter/logo.png.license new file mode 100644 index 00000000..0441988a --- /dev/null +++ b/docs/docs/base/jupyter/logo.png.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Copyright Project Jupyter +SPDX-License-Identifier: BSD-3-Clause diff --git a/docs/docs/papyrus/base.md b/docs/docs/base/papyrus/index.md similarity index 87% rename from docs/docs/papyrus/base.md rename to docs/docs/base/papyrus/index.md index 51da7abc..f93d8077 100644 --- a/docs/docs/papyrus/base.md +++ b/docs/docs/base/papyrus/index.md @@ -3,16 +3,10 @@ ~ SPDX-License-Identifier: Apache-2.0 --> -# Papyrus base +# Papyrus Base - -!!! info - The Docker image name for this image is `papyrus/base` - -The Papyrus base image runs -[Eclipse Payprus](https://www.eclipse.org/papyrus/index.php) in a Docker -container. The Papyrus client can be downloaded and can optionally be -customised prior to building the Docker image. +The Papyrus base image runs [Eclipse Payprus](https://eclipse.dev/papyrus/) in +a Docker container. ## Supported versions @@ -23,10 +17,6 @@ The image has been tested with the following versions: The only supported build architecture is amd64. To build and run the image on other build architectures, use QEMU or Rosetta. -## Use the prebuilt image - -The Papyrus image is not available as prebuilt image yet. - ## Build it yourself ### Preparation diff --git a/docs/docs/base/papyrus/logo.png b/docs/docs/base/papyrus/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..804639d5f742b3592373a541637ebea0bd6435d8 GIT binary patch literal 25059 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 - -# Capella base - - -!!! info - The Docker image name for this image is `capella/base` - -The Capella base image installs a selected Capella client version. The Capella -client can be downloaded and can optionally be customised prior to building the -Docker image or can be downloaded automatically in the Docker image. - -The images are meant to have a containerised Capella (with or without a Team -for Capella client) that can be run headless (as command line interface). - - -!!! info - The functionality for running capella as a command-line app used to be part of - the `capella/cli` image. An image with this name is no longer built. Use - `capella/base` instead. - -## Use the prebuilt image - -``` -docker run ghcr.io/dsd-dbs/capella-dockerimages/capella/base:$TAG -``` - -where `$TAG` is the Docker tag. For more information, have a look at our -[tagging schema](introduction.md#tagging-schema-for-prebuilt-images). - -Please check the [`Run the container`](#run-the-container) section for more -information about the usage. - -## Build it yourself - -### Preparation - -#### Optional: Download Capella manually - -Download a Capella Linux binary `zip` or `tar.gz` archive. You can get a -release directly from Eclipse. Visit -, select a version and follow the -hyperlink labelled `Product` to find a binary release for Linux. - -Place the downloaded archive in the subdirectory -`capella/versions/$CAPELLA_VERSION/$ARCHITECTURE` of the present repository and -ensure that the end result is either - -- `capella/versions/$CAPELLA_VERSION/$ARCHITECTURE/capella.tar.gz` or -- `capella/versions/$CAPELLA_VERSION/$ARCHITECTURE/capella.zip`. - -Check that the archive has a structure similar to the following coming with a -top level directory named `capella` and several sub directories and files in -it. - -For Capella 5.0.0 the structure is illustrated below: - -```zsh -$ tree -L 1 capella -capella -├── artifacts.xml -├── capella -├── capella.ini -├── configuration -├── dropins -├── epl-v10.html -├── features -├── jre -├── notice.html -├── p2 -├── plugins -└── readme -``` - -#### Optional: Customisation of the Capella client - -To customise the Capella client you can - -1. extract the downloaded archive, -1. apply any modifications (e.g., installation of plugins and/ or dropins) to - it, and -1. compress the modified folder `capella` to get a `capella.zip` or - `capella.tar.gz` again. - -##### Install dropins - -As alternative to the solution presented above, we provide an interface to -install dropins. - -You have to pass a comma-separated list of dropin names as `CAPELLA_DROPINS` -build argument to the `docker build` command: - -```zsh ---build-arg CAPELLA_DROPINS="ModelsImporter,CapellaXHTMLDocGen,DiagramStyler,PVMT,Filtering,Requirements,SubsystemTransition" -``` - -Supported dropins are: - -- [CapellaXHTMLDocGen](https://github.com/eclipse/capella-xhtml-docgen) -- [DiagramStyler](https://github.com/eclipse/capella/wiki/PVMT) -- [PVMT](https://github.com/eclipse/capella/wiki/PVMT) -- [Filtering](https://github.com/eclipse/capella-filtering) -- [Requirements](https://github.com/eclipse/capella-requirements-vp) -- [SubsystemTransition](https://github.com/eclipse/capella-sss-transition) -- [TextualEditor](https://github.com/eclipse/capella-textual-editor) - -The dropins are registered in the -`capella/versions/$CAPELLA_VERSION/dropins.yml` file. If you're missing a -dropin in the list, feel free to open a PR. - -#### Optional: Workaround of pinned library versions to remove incompatibilities - -**Note:** _This workaround is normally handled in the Dockerfile and it is only -necessary to download below libraries if there are restrictions on your network -that block an access to these libraries when the Docker image is being built._ - -In some Capella versions, there are incompatiblities with certain versions of -the following libraries: - -- `libjavascriptcoregtk-4.0-18` (version `2.32.4`) -- `libwebkit2gtk-4.0-37` (version `2.32.4`) - -The workaround is to use version `2.28.1` for both libraries in the container. - -So if your build environment restricts access to the latest versions you need -to manually download the packages with the command `apt download` and inject -them into the container. - -For more information refer to -[Download older packages manually](#download-older-packages-manually). - - -!!! info - You have to add `--build-arg INJECT_PACKAGES=true` to the `docker build` command if you want to use the previously downloaded packages. - -### Build it manually with Docker - -If you want to download the Capella archive automatically, use the following -command. Does only work for -[supported Capella versions](introduction.md#supported-versions). - -```zsh -docker build -t capella/base capella --build-arg BUILD_TYPE=online --build-arg CAPELLA_VERSION=$CAPELLA_VERSION -``` - -If you've downloaded the Capella archive manually before, use this command: - -```zsh -docker build -t capella/base capella --build-arg CAPELLA_VERSION=$CAPELLA_VERSION -``` - -### Miscellaneous - -#### Download older debian packages manually - -Unfortunately the version `2.28.1` of `libwebkit2gtk-4.0-37` is no longer -available in the stable Debian registry, but it is still available in the -Ubuntu `focal` repository -(). - -First of all, you have to add the source to your `apt`-sources and add the apt -keys. - -Recommendation: Spawn a `debian:bookworm` Docker container and execute the -steps inside the container. - -```zsh -apt update && apt install -y gnupg -echo "deb http://security.ubuntu.com/ubuntu focal-security main" >> /etc/apt/sources.list.d/focal.list -apt-key adv --keyserver keyserver.ubuntu.com --recv-keys "3B4FE6ACC0B21F32" -apt-key adv --keyserver keyserver.ubuntu.com --recv-keys "871920D1991BC93C" -apt update -``` - -Please download all packages and place the files in the folder `capella/libs`: - -- `libicu66_66.1-2ubuntu2_amd64.deb`
(Run - `apt download libicu66=66.1-2ubuntu2`) - -- `libjavascriptcoregtk-4.0-18_2.28.1-1_amd64.deb`
(Run - `apt download libjavascriptcoregtk-4.0-18=2.28.1-1`) - -- `libjpeg-turbo8_2.0.3-0ubuntu1_amd64.deb`
(Run - `apt download libjpeg-turbo8=2.0.3-0ubuntu1`) - -- `libjpeg8_8c-2ubuntu8_amd64.deb`
(Run - `apt download libjpeg8=8c-2ubuntu8`) - -- `libwebkit2gtk-4.0-37_2.28.1-1_amd64.deb`
(Run - `apt download libwebkit2gtk-4.0-37=2.28.1-1`) - -- `libwebp6_0.6.1-2ubuntu0.20.04.2_amd64.deb`
(Run - `apt download libwebp6=0.6.1-2ubuntu0.20.04.2`) - -## Run the container - -### Locally on X11 systems - -If you don't need remote access, have a local X11 server running and just want -to run Capella locally, this may be the best option for you. - -On some systems, you have to whitelist connections to the X-Server with: - -```zsh -xhost +local -``` - -It allows all local programs to connect to your X server. You can further -restrict the access to the X server. Please read the -[documentation of `xhost`](https://man.archlinux.org/man/xhost.1) for more -details. - -The container can be started with the following command. The `DISPLAY` -environment has to be passed to the container. - -```zsh -docker run -d \ - -v /tmp/.X11-unix:/tmp/.X11-unix \ - -e DISPLAY=$(DISPLAY) \ - capella/base -``` - -Capella should start after a few seconds. - -### In a remote container (RDP) - -Please follow the instructions on the [remote](../remote.md) page. When running -the image, add the following variables to the `docker run` command: - -```zsh - -e AUTOSTART_CAPELLA=$AUTOSTART_CAPELLA \ - -e RESTART_CAPELLA=$RESTART_CAPELLA \ -``` - -Please replace the followings variables: - -- `AUTOSTART_CAPELLA` defines the autostart behaviour of Capella. When set to 1 - (default), Capella will be started as soon as an RDP connection has been - established to the running container. -- `RESTART_CAPELLA` defines the restart behaviour of Capella. When set to 1 - (default) and when `AUTOSTART_CAPELLA=1`, Capella will be re-started as soon - as it has been exited (after clean quits as well as crashs). - -If you want to configure the JVM memory options, have a look at -[Eclipse memory options](../eclipse/memory-options.md). - -### Example to export representations (diagrams) as SVG images - -Replace `/path/to/model` and `` to pass any local Capella model. -Set the project name so that it fits your Capella project name for the model as -it is given in the file `/path/to/model/.project`. - -Exported diagrams will appear on the host machine at `/path/to/model/diagrams`. - -```zsh -docker run --rm -it \ - -v /path/to/model:/model \ - capella/base \ - -nosplash \ - -consolelog \ - -application org.polarsys.capella.core.commandline.core \ - -appid org.polarsys.capella.exportRepresentations \ - -data /workspace \ - -import /model \ - -input "/all" \ - -imageFormat SVG \ - -exportDecorations \ - -outputfolder //diagrams \ - -forceoutputfoldercreation -``` diff --git a/docs/docs/capella/build-from-source.md b/docs/docs/capella/build-from-source.md deleted file mode 100644 index 4999e90b..00000000 --- a/docs/docs/capella/build-from-source.md +++ /dev/null @@ -1,25 +0,0 @@ - - -# Build Capella from source - -We provide a script, which runs a Docker container to build Capella from -source. Our builder script adds the aarch64 build platform for macOS and linux. -Please note that Capella itself has no official support for the aarch64 -architecture yet. We can not guarantee that everything works, but all of the -official Capella tests passed on the generated archives. - -In addition, the builder only works for Capella version 6.0.0 for now. To start -the script, please run: - -``` -CAPELLA_VERSION=6.0.0 make capella/builder -``` - -When the script is successful, you should see the result in `builder/output`. - -We store a cache of the maven repository in the `builder/m2_cache` directory. -If you don't need the cache anymore, you can delete the directory after -building. diff --git a/docs/docs/capella/introduction.md b/docs/docs/capella/introduction.md deleted file mode 100644 index 04b247a5..00000000 --- a/docs/docs/capella/introduction.md +++ /dev/null @@ -1,64 +0,0 @@ - - -# Capella introduction - -## Supported versions - -Currently, we support Capella versions `5.0.0`, `5.2.0`, `6.0.0`, and `6.1.0`. - -## Supported architectures - -Currently, we support amd64 for all supported Capella version. In addition, we -added support for arm64 starting with Capella `6.0.0`. - -## Supported dropins - -Starting with version `v1.11.0`, we have prebuilt images with a pre-selected -sets of dropins. Available options are: - -- `without-dropins`: Without dropins -- `selected-dropins`: With - [CapellaXHTMLDocGen](https://github.com/eclipse/capella-xhtml-docgen), - [DiagramStyler](https://github.com/eclipse/capella/wiki/PVMT), - [PVMT](https://github.com/eclipse/capella/wiki/PVMT), - [Filtering](https://github.com/eclipse/capella-filtering), - [Requirements](https://github.com/eclipse/capella-requirements-vp) and - [SubsystemTransition](https://github.com/eclipse/capella-sss-transition) - -If you need a custom set of dropins, you have two options: - -**Option 1**: Mount a dropins folder with additional dropins into -`/opt/capella/dropins` when starting the container. - -**Option 2**: Build the `capella/base` Docker image manually. More information: -[Build it yourself](./base.md#build-it-yourself) - -## Tagging schema for prebuilt images - -The Capella related images are tagged using the following schema: - -`$CAPELLA_VERSION-$DROPINS_TYPE-$CAPELLA_DOCKER_IMAGES_REVISION`, e.g., -`6.0.0-selected-dropins-v1.10.2` for Capella version `6.0.0` with selected -dropins and Capella Docker images revision `v1.10.2`. - -`$CAPELLA_VERSION` is the semantic version of Capella (see supported versions -[above](#supported-versions)). `$DROPINS_TYPE` is the name of the set of -dropins. `$CAPELLA_DOCKER_IMAGES_REVISION` can be a tag or branch of this -repository. In case of branches, all characters matching the regex -`[^a-zA-Z0-9.]` will be replaced with `-`. - -We don't tag images with the `latest` tag. You may want to use -`$CAPELLA_VERSION-selected-dropins-main` for the latest version, but we -recommend using tags for the best stability. - -## Tips - -- You can mount a Capella workspace inside the container by appending the - following to the `docker run` command: - - ```zsh - -v /path/to/your/local/volume:/workspace - ``` diff --git a/docs/docs/capella/provisioning.md b/docs/docs/capella/provisioning.md deleted file mode 100644 index c334b8f5..00000000 --- a/docs/docs/capella/provisioning.md +++ /dev/null @@ -1,62 +0,0 @@ - - -# Load models to your workspace automatically - -!!! info "Migration from `v1.X.X` to `v2.X.X` and later" - - This feature replaces the read-only image of version `v1.X.X`. - Before starting the new image, you have to clone the Git repositories - that you've passed to the read-only image manually. The path with all - repositories can then be mounted to the new image as described below. - -!!! info "Technical prerequisites" - - The feature relies on an Eclipse plugin that is not part of Capella. - The plugin is called `models-from-directory-importer` and is available - on GitHub: https://github.com/DSD-DBS/capella-addons. - - The plugin is part of all Capella based pre-built images on GitHub. - If you build it manually, make sure that you follow the ["Install dropins" instructions](./base.md#install-dropins). - -To load models to your workspace automatically, you can mount a volume to the -container. - -``` -docker run -d \ - -v path/to/models/on/host:/models \ - -e ECLIPSE_PROJECTS_TO_LOAD='[]' \ - capella/base -``` - -The `ECLIPSE_PROJECTS_TO_LOAD` environment variable is a JSON array that -contains: - -```json -[ - { - "revision": "master", // (1) - "nature": "project", // (2) - "path": "/models/directory", // (3) - "entrypoint": "test.aird" // (4) - } -] -``` - -1. The revision of the Eclipse project. In case of duplicated project names, - the revision is added as suffix to the project name. -2. Optional: Can be either 'project' or 'library'. Defaults to 'project'. - Ignored if the the directory provided in the `path` attribute contains a - `.project` file. -3. Path to the directory where the project should be loaded from. -4. Path to the aird file, starting from the directory provided in the `path` - attribute. Required if the `.aird` is not placed directly in the directory - provided as `path`. If None, the aird is searched in the path directory - without recursion. - -All additional attributes are ignored. - -You can use all images that are based on the `capella/base` image for this -feature. diff --git a/docs/docs/capella/t4c/base.md b/docs/docs/capella/t4c/base.md deleted file mode 100644 index ef4c2bd6..00000000 --- a/docs/docs/capella/t4c/base.md +++ /dev/null @@ -1,124 +0,0 @@ - - -# TeamForCapella client base - - -!!! info - The Docker image name for this image is `t4c/client/base` - -The T4C base image builds on top of the Capella base image and installs the T4C -client plugins. - -## Build it yourself - -### Preparation - -#### Download TeamForCapella bundle - - -1. Download a Team for Capella client for Linux from - - - Note that the T4C client version must match the version for Capella itself. - To obtain a Linux T4C client version below 5.2 you may want to contact - [Obeo](https://www.obeosoft.com/en/team-for-capella-download) to get a bundle. - -1. Extract the downloaded archive. The extracted folder comes with a `.zip` file - containing the T4C client: - - ```text - $ tree -L 2 TeamForCapella-5.0.0-linux.gtk.x86_64 - TeamForCapella-5.0.0-linux.gtk.x86_64 - ├── (...) - └── updateSite - └── com.thalesgroup.mde.melody.team.license.update-5.0.0-202012091024.zip - ``` - -1. That `.zip` file needs to be copied into the subdirectory `t4c/updateSite/$CAPELLA_VERSION` - of the present repository. - -#### Optional: Add feature patches - -It is possible to provide feature patches for our t4c base image that are -installed after the initial installation. To install such feature patches, you -have to do the following things. - -1. The feature patch `.zip` file needs to be copied into the subdirectory - `t4c/updateSite/$CAPELLA_VERSION` of the present repository -1. You have to create the `patch_info.csv` file inside the same subdirectory if - not yet existing -1. You have to add a new line to the `patch_info.csv` having the following - format: - - ```csv - ,, - ``` - - In case that you have one feature patch zip containing different things you - want to install you can provide multiple _install iu_, each with a - whitespace seperated. So in this case the `patch_info.csv` would contain a - line with the following format: - - ```csv - , ... , - ``` - -Please ensure that the `patch_info.csv` contains an empty line at the end -otherwise the last feature patch might not be installed. - -### Build it manually with Docker - -Build the container: - -```zsh -docker build -t t4c/client/base --build-arg CAPELLA_VERSION=$CAPELLA_VERSION t4c -``` - -## Run the container - -Running the T4C client container is analogous to the Capella Base container. -Please run the -[instructions of the Capella Base container](../base.md#run-the-container), but -add the following environment variables during the `docker run` command: - -```zsh - -e T4C_LICENCE_SECRET=XXX \ - -e T4C_JSON='[{"repository": "", "port": 0, "host": "", "instance": "", "protocol": "ssl"}]' \ - -e T4C_SERVER_HOST=$T4C_SERVER_HOST \ - -e T4C_SERVER_PORT=$T4C_SERVER_PORT \ - -e T4C_REPOSITORIES=$T4C_REPOSITORIES \ - -e T4C_USERNAME=$T4C_USERNAME \ -``` - -Please replace the followings variables: - -- `$T4C_LICENCE_SECRET` with your TeamForCapella licence secret. -- `$T4C_USERNAME` with the username that is suggested when connecting to t4c. -- One of the two options: - - - `$T4C_JSON` with a list of repositories with name, host, port and instance name as JSON: - ```json - [ - { - "repository": "repoCapella", - "host": "localhost", - "port": 2036, - "instance": "", //optional, required if the repository names are not unique - "protocol": "ssl" //optional, defaults to ssl - } - ] - ``` - - The environment variables `$T4C_SERVER_HOST`, `$T4C_SERVER_PORT` and `$T4C_REPOSITORIES` will be ignored. - - - Three environment variables: - - `$T4C_SERVER_HOST` with the IP-Address of your T4C server (default: `127.0.0.1`). - - `$T4C_SERVER_PORT` with the port of your T4C server (default: `2036`). - - `$T4C_REPOSITORIES` with a comma-seperated list of repositories. These repositories show - up as default options on connection (e.g. `repo1,repo2`). - -When Capella has started, you should see the T4C models in the dropdown menu of -the connection dialog. diff --git a/docs/docs/capella/t4c/exporter.md b/docs/docs/capella/t4c/exporter.md deleted file mode 100644 index dab97aa3..00000000 --- a/docs/docs/capella/t4c/exporter.md +++ /dev/null @@ -1,96 +0,0 @@ - - -# TeamForCapella client exporter - - -!!! info - The Docker image name for this image is `t4c/client/base` - - -!!! info - The exporter can export a model from Git to a TeamForCapella repository with the `merge`-strategy of TeamForCapella. - -The T4C client exporter image imports a model from a git repository and exports -it to a T4C server. - -## Build it yourself - -### Build it manually with Docker - -Build instructions are the same as the [T4C client base](base.md) image. - -## Run the container - -Run the following command to export from Git to T4C: - -```zsh -docker run -d \ - -e GIT_REPO_URL=https://github.com/example/example.git \ - -e GIT_REPO_BRANCH=main \ - -e GIT_USERNAME=user \ - -e GIT_PASSWORD=password \ - -e T4C_REPO_HOST=localhost \ - -e T4C_REPO_PORT=2036 \ - -e T4C_REPO_NAME=repoCapella \ - -e T4C_PROJECT_NAME=test \ - -e T4C_USERNAME=user \ - -e T4C_PASSWORD=password \ - -e LOG_LEVEL=DEBUG \ - t4c/client/base export -``` - -You can find the description for these values in the run instructions of the -[importer](./importer.md#run-the-container). - -## Testing - -### Manual Testing - -For development purposes, you can test the exporter locally. - -!!! warning - - For the next steps, you need a running TeamForCapella server. - - -1. Start a lightweight local Git server with the following command: - ```zsh - make run-local-git-server - ``` -1. Clone the sample repository: - ```zsh - git clone 'http://localhost:10001/git/git-test-repo.git' - ``` -1. Copy the model to the newly created repository. Push the model you'd like to - export: - - ```zsh - git add . - git commit -m "Initial commit" - git push - ``` - -1. Set the `GIT_REPO_ENTRYPOINT` environment variable to the relative path from - the root of the repository to the aird file: - - ```zsh - export GIT_REPO_ENTRYPOINT="path/to/your/model.aird" - ``` - -1. Create a new TeamForCapella repository via the REST API. -1. Set the `T4C_REPO_NAME` environment variable to the repository name that you - chose in the previous step: - - ```zsh - export T4C_REPO_NAME="repoCapella" - ``` - -1. Run the following command to start the exporter: - ```zsh - make run-t4c/client/exporter - ``` -1. Connect to the repository from a Capella client to verify that the model was - exported correctly. diff --git a/docs/docs/capella/t4c/importer.md b/docs/docs/capella/t4c/importer.md deleted file mode 100644 index 0ba23353..00000000 --- a/docs/docs/capella/t4c/importer.md +++ /dev/null @@ -1,139 +0,0 @@ - - -# TeamForCapella client backup / importer - - -!!! info - The Docker image name for this image is `t4c/client/base` - - -!!! info - The importer exports a model from a TeamForCapella repository to a Git repository. - -The T4C client backup image imports a model from a TeamForCapella server and -exports it to a Git repository. It can be used as a backup solution, for -example, in a scheduled job. - -## Build it yourself - -### Build it manually with Docker - -Build instructions are the same as the [T4C client base](base.md) image. - -## Run the container - -Please run the following command to run the backup from T4C to Git: - -```zsh -docker run -d \ - -e GIT_REPO_URL=https://github.com/example/example.git \ - -e GIT_REPO_BRANCH=main \ - -e GIT_USERNAME=user \ - -e GIT_PASSWORD=password \ - -e T4C_REPO_HOST=localhost \ - -e T4C_REPO_PORT=2036 \ - -e T4C_CDO_PORT=12036 \ - -e T4C_REPO_NAME=repoCapella \ - -e T4C_PROJECT_NAME=test \ - -e T4C_USERNAME=user \ - -e T4C_PASSWORD=password \ - -e LOG_LEVEL="DEBUG" \ - -e INCLUDE_COMMIT_HISTORY=false \ - t4c/client/base backup -``` - -Set the following values for the corresponding keys: - -- `GIT_REPO_URL`: URL to the target Git repository where the model will be - pushed to. All URI-formats supported by the `git clone` command will work. - You can provide HTTP credentials via the `GIT_USERNAME` and `GIT_PASSWORD` - variables (see below). -- `GIT_REPO_BRANCH`: branch of the Git repository. -- `GIT_USERNAME`: Git username if the repository is access protected. -- `GIT_PASSWORD`: Git password that is used during cloning from and pushing to - the Git repository. -- `T4C_REPO_HOST`: hostname to the T4C server. The same value that you enter in - Capella to connect to a remote repository. -- `T4C_REPO_PORT`: port to the T4C server. The same value that you enter in - Capella to connect to a remote repository. Defaults to 2036. -- `T4C_REPO_NAME`: T4C repository name. The same value that you enter in - Capella to connect to a remote repository. -- `T4C_PROJECT_NAME`: name of the Capella project. It's displayed in the - Capella project explorer and in the last step when connecting to a remote - repository. -- `T4C_USERNAME`: T4C username that is used during the import. The user needs - to have access to the repository. -- `T4C_PASSWORD`: T4C password that is used during the import. -- `LOG_LEVEL`: your preferred logging level (all Python logging levels are - supported). -- `INCLUDE_COMMIT_HISTORY`: `true` or `false` to define if the T4C commit - history should be exported. Important: Exporting the commit history can take - a few hours for large models. - -## Extract TeamForCapella commit messages to Git - -The importer extracts the commit messages from TeamForCapella and adds them to -the Backup commit description. The commit has the format: - -```yaml -Backup - -- user: admin - time: '2024-03-25T16:51:27.697000+00:00' - description: '' -- user: admin - time: '2024-03-25T16:51:20.523000+00:00' - description: null -- user: admin - time: '2024-03-25T16:51:09.755000+00:00' - description: Second Example commit -- user: admin - time: '2024-03-25T16:50:57.138000+00:00' - description: First example commit -``` - -The commit body is always in the YAML format. - -## Testing - -### Manual Testing - -For development purposes, you can test the importer / backup locally. - -!!! warning - - For the next steps, you need a running TeamForCapella server and a TeamForCapella license server. - - -1. Start a lightweight local Git server with the following command: - ```zsh - make run-local-git-server - ``` - -1. Create a new TeamForCapella repository via the REST API. - If you want to include the user information in the commits, you have to enable authentication. -1. Set the `T4C_REPO_NAME` environment variable to the repository name that you - chose in the previous step: - - ```zsh - export T4C_REPO_NAME="repoCapella" - ``` - -1. Open Capella and create a new Capella project with the project name "test". -1. [Export the project to the TeamForCapella repository](https://dsd-dbs.github.io/capella-collab-manager/user/tools/capella/teamforcapella/export/export-to-t4c/) -1. Run the following command to start the importer: - - ```zsh - make run-t4c/client/backup - ``` - -1. Clone the sample repository: - - ```zsh - git clone 'http://localhost:10001/git/git-test-repo.git' - ``` - -1. Check the commit history and the model in the repository. diff --git a/docs/docs/capella/t4c/introduction.md b/docs/docs/capella/t4c/introduction.md deleted file mode 100644 index b8d599f2..00000000 --- a/docs/docs/capella/t4c/introduction.md +++ /dev/null @@ -1,10 +0,0 @@ - - -# TeamForCapella client introduction - -!!! info - - TeamForCapella is a commercial product of OBEO. More information: diff --git a/docs/docs/ci-templates/gitlab/image-builder.md b/docs/docs/ci-templates/gitlab/image-builder.md index 5a1e341e..9c36c3b4 100644 --- a/docs/docs/ci-templates/gitlab/image-builder.md +++ b/docs/docs/ci-templates/gitlab/image-builder.md @@ -3,23 +3,142 @@ ~ SPDX-License-Identifier: Apache-2.0 --> -# Image builder +# Image Builder The image builder template builds all images supported by this repository, executes our tests on each builded images, and pushes them to any Docker registry. We use it in our automated deployment environment for our -[Collaboration project](https://github.com/DSD-DBS/capella-collab-manager). We -have restricted internet access in our build environment, so the Gitlab CI -template is optimized for restricted network access. +[Collaboration project](https://github.com/DSD-DBS/capella-collab-manager). -Please add the following section to your `.gitlab-ci.yml`: +## Setup -```yaml -include: - - remote: https://raw.githubusercontent.com/DSD-DBS/capella-dockerimages/${CAPELLA_DOCKER_IMAGES_REVISION}/ci-templates/gitlab/image-builder.yml +### SOPS Configuration + +We use [SOPS](https://github.com/getsops/sops) to encrypt the configuration +file in the repository. To set up SOPS, you have to create a private key first: +[Generate a private GPG key](https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key) + +### Gitlab Runner Configuration + +To use the CI template, a few changes have to be made to the Gitlab runner: + +```yaml title="config.toml" +[[runners]] +[runners.docker] +volumes = [ + "/shared:/shared", # (1)! + "/builds:/builds", # (2)! + "/var/run/docker.sock:/var/run/docker.sock", # (3)! + ":/secrets/private.gpg", # (4)! +] ``` -## Tagging of images +1. In order to make optimal use of caching, we have to save the tag of the last + built image. Since the Gitlab CI cache is not shared between concurrent + runners, we use a shared volume `/shared`. +2. Expose `/builds` to the host, so that we can mount it to the build context. +3. The pack CLI needs access to the Docker daemon. +4. The previously generated private GPG key has to be mounted to the runner in + order to decrypt the configuration file. + +### Gitlab Repository Configuration + +1. Create a new repository where you'll store the archives of the tools. +1. Set the CI/CD variable `CAPELLA_DOCKER_IMAGES_REVISION` on a repository + level. The value can be any revision of this GitHub repository (we + recommend `main`) +1. In the new repository, add a `.gitlab-ci.yml` and include our build + template: + + ```yaml title=".gitlab-ci.yml" + include: + - remote: https://raw.githubusercontent.com/DSD-DBS/capella-dockerimages/${CAPELLA_DOCKER_IMAGES_REVISION}/ci-templates/gitlab/image-builder.yml + ``` + +1. Add the SOPS configuration to the repository. Add the GPG fingerprints of + all users who should be able to access the encrypted configuration file. + + ```yaml title=".sops.yaml" + creation_rules: + - path_regex: .* + encrypted_regex: '^(password|token)$' + key_groups: + - pgp: + - + - + ``` + +1. Create an encrypted file using SOPS `sops config.yml`. Add the following + content: + + ```yaml title="config.yml" + t4c-server: + registry: 'https://example.com' + tag: '$CAPELLA_VERSION-main' # (1)! + test-data-repository: '' # (2)! + + registries: # (3)! + xpra: 'https://xpra.org' + eclipse: 'https://download.eclipse.org' + + base-images: + debian: 'debian:bookworm' + alpine: 'alpine:latest' + + architecture: amd64 # (4)! + + environments: + staging: + uid: 1001 + registry: + url: 'https://registry.gitlab.com' + user: username + password: password + production: ... + development: ... + ``` + + 1. Environment variables are resolved by the CI. + 2. Link to a Gitlab repository (relative to ) containing TeamForCapella + test data needed to run the backup tests. The repository needs to have + the following structure: `/data/$CAPELLA_VERSION/repositories/test-repo` + 3. Can be used to use custom registries / mirrors in restricted internet + access. + 4. Build images for `amd64` or `arm64`. + +1. Add all required archives to the repository. The repository structure + should look like: + ```zsh + ├── capella + │ └── versions + │ ├── 5.0.0 + │ │ ├── capella.tar.gz + │ │ ├── dropins + │ │ └── updateSite + │ ├── 5.2.0 + │ │ ├── capella.tar.gz + │ │ ├── dropins + │ │ └── updateSite + │ ├── 6.0.0 + │ │ ├── capella.tar.gz + │ │ ├── dropins + │ │ └── updateSite + │ └── 6.1.0 + │ ├── capella.tar.gz + │ ├── dropins + │ └── updateSite + ├── papyrus + │ └── versions + │ └── 6.4.0 + │ └── papyrus.tar.gz + ├─── pure-variants + │ └── updateSite + ├── .gitlab-ci.yml + ├── .sops.yml + └── config.yml + ``` + +## Tagging of Images ### Capella @@ -49,75 +168,3 @@ where: The resulting Papyrus based images will be tagged in the following format: `$PAPYRUS_VERSION-$CAPELLA_DOCKER_IMAGES_REVISION-$GITLAB_IMAGE_BUILDER_REVISION`, e.g., `6.4.0-v1.7.0-v1.0.0`. - -## Variables - -In addition, you have to add the following environment variables on repository -level. Make sure to enable the "Expand variable reference" flag. - -- `CAPELLA_DOCKER_IMAGES_REVISION`: Revision of this Github repository. -- `ENVIRONMENT`: Specifies the environment. In addition, you need to have the - following variables for each environment: - - `UID_${ENVIRONMENT}`: The user ID which will be used for the technical - user. - - Variables related to the Docker registry (all parameters are passed to - `docker login`): - - `DOCKER_REGISTRY_${ENVIRONMENT}`: The URL to the Docker registry - - `DOCKER_REGISTRY_USER_${ENVIRONMENT}`: Username of a techuser with push - permission to the Docker registry - - `DOCKER_REGISTRY_PASSWORD_${ENVIRONMENT}`: Corresponding password of the - techuser -- `T4C_SERVER_REGISTRY`: Docker registry which contains the required t4c server - image -- `T4C_SERVER_TAG`: Docker tag that is used for the t4c server image -- `T4C_SERVER_TEST_DATA_REPO`: Link to a Git repository containing t4c test - data needed to run the backup tests. The repository needs to have the - following structure: `/data/$CAPELLA_VERSION/repositories/test-repo` -- `LOCAL_GIT_BASE_IMAGE`: Specifies the base image which is used to build the - local git server for the pytest. - -## Repository tree - -The tree inside of your Gitlab repository should look like: - -```zsh -├── capella -│ ├── libs -│ │ ├── libicu66_66.1-2ubuntu2_amd64.deb -│ │ ├── libjavascriptcoregtk-4.0-18_2.28.1-1_amd64.deb -│ │ ├── libjpeg-turbo8_2.0.3-0ubuntu1.20.04.1_amd64.deb -│ │ ├── libjpeg8_8c-2ubuntu8_amd64.deb -│ │ └── libwebkit2gtk-4.0-37_2.28.1-1_amd64.deb -│ └── versions -│ ├── 5.0.0 -│ │ ├── capella.tar.gz -│ │ ├── dropins -│ │ ├── ease -│ │ └── updateSite -│ ├── 5.2.0 -│ │ ├── capella.tar.gz -│ │ ├── dropins -│ │ ├── ease -│ │ └── updateSite -│ ├── 6.0.0 -│ │ ├── capella.tar.gz -│ │ ├── dropins -│ │ ├── ease -│ │ └── updateSite -│ └── 6.1.0 -│ ├── capella.tar.gz -│ ├── dropins -│ ├── ease -│ └── updateSite -├── papyrus -│ └── versions -│ └── 6.4.0 -│ └── papyrus.tar.gz -└── pure-variants - ├── dependencies - └── updateSite -``` - -This is the minimal configuration. For more advanced configuration options, -please refer to the -[Gitlab CI template](https://github.com/DSD-DBS/capella-dockerimages/blob/main/ci-templates/gitlab/image-builder.yml). diff --git a/docs/docs/ci-templates/gitlab/release-train.md b/docs/docs/ci-templates/gitlab/release-train.md index bd108442..1aa23640 100644 --- a/docs/docs/ci-templates/gitlab/release-train.md +++ b/docs/docs/ci-templates/gitlab/release-train.md @@ -10,8 +10,8 @@ Capella version. In addition, we provide a release train template, which can be used to trigger the image builder pipeline with a matrix of Capella versions and environments. - !!! warning + To continue, create a new image builder Gitlab repository and follow the instuctions of the image builder template. The pipeline is not triggered automatically. There are a few options to trigger @@ -46,19 +46,19 @@ include: - when: manual variables: ENVIRONMENT: staging - CAPELLA_DOCKER_IMAGES_REVISION: "$CI_COMMIT_REF_NAME" - IMAGE_BUILDER_GITLAB_REPOSITORY: "$IMAGE_BUILDER_GITLAB_REPOSITORY" - BUILD_FOR_LATEST_TAG: "0" + CAPELLA_DOCKER_IMAGES_REVISION: '$CI_COMMIT_REF_NAME' + IMAGE_BUILDER_GITLAB_REPOSITORY: '$IMAGE_BUILDER_GITLAB_REPOSITORY' + BUILD_FOR_LATEST_TAG: '0' .production: &production rules: # For tags, build for the production environment - - if: "$CI_COMMIT_TAG != null" + - if: '$CI_COMMIT_TAG != null' variables: ENVIRONMENT: production - CAPELLA_DOCKER_IMAGES_REVISION: "$CI_COMMIT_REF_NAME" - IMAGE_BUILDER_GITLAB_REPOSITORY: "$IMAGE_BUILDER_GITLAB_REPOSITORY" - BUILD_FOR_LATEST_TAG: "1" + CAPELLA_DOCKER_IMAGES_REVISION: '$CI_COMMIT_REF_NAME' + IMAGE_BUILDER_GITLAB_REPOSITORY: '$IMAGE_BUILDER_GITLAB_REPOSITORY' + BUILD_FOR_LATEST_TAG: '1' production-base: extends: .base diff --git a/docs/docs/development/buildpacks.md b/docs/docs/development/buildpacks.md new file mode 100644 index 00000000..404522e4 --- /dev/null +++ b/docs/docs/development/buildpacks.md @@ -0,0 +1,61 @@ + + +# Build Images with Cloud Native Buildpacks + +Starting with version 3.0.0, we use Cloud Native Buildpacks for the Capella +based images. + +## Why Cloud Native Buildpacks? + +### Disadvantages of Dockerfiles + +Before v3.0.0, we have used Dockerfiles to build the images. To keep the system +dependencies up to date, we have regularly rebuilt the images without build +cache. + +In combination with bad use of multi-stage builds, this has led to enormous +image sizes and long build times. Image layers were not cached at all. Our +internal Docker registry has grown to several terabytes in size. + +In addition, pulling the images from the registry was slow and after updates, +all layers had to be pulled again. + +### Advantages of Buildpacks for our Images + +- Exchange individual layers. Some examples where this is useful: + - Update the dependencies without update of the Capella layer + - Update TeamForCapella archive without update of the Capella layer + - Update dropins independently +- Reduction of the image sizes = faster image pull times = faster session + startup in the Collaboration Manager. Examples: + - `capella/base:6.0.0`: 3.5GB -> 1.5GB + - `t4c/client/base:6.0.0`: 4.5GB -> 1.6GB +- Reuse layers of last builds, massively reducing storage space and traffic in + registries Less layers, the new layers are clearly mentioned and separated in + different scopes (capella layer, capella-dropins layer, t4c-client layer, + ...) +- Full reproducible builds (same input = same digest of output images) +- No extensive build cache which sums up in local development (instead, it uses + layers of the last image as cache) +- It makes the images modular. Users can decide which modules they want to have + in their images (e.g. add xpra or xrdp as add-on to get remote support) +- Reduce number of layers in the images. + +### Two layers: Meta and App + +To have consistent naming, we've decided that we want to allow each buildpack +to define two layers: + +- `meta` layer: contains `exec.d` scripts, entrypoint scripts, and other + customizations which change frequently. +- `app` layer: contains the application (e. g. Capella, Eclipse, ...) and other + files which change less frequently. As the app layer is larger in size, we + want to cache it as much as possible. + +## Get Started with Buildpacks + +If you want to learn more about buildbacks, check their documentation: +https://buildpacks.io/docs/ diff --git a/docs/docs/pure-variants.md b/docs/docs/extensions/pure-variants.md similarity index 70% rename from docs/docs/pure-variants.md rename to docs/docs/extensions/pure-variants.md index 5ede9f31..ebf9f4e4 100644 --- a/docs/docs/pure-variants.md +++ b/docs/docs/extensions/pure-variants.md @@ -3,31 +3,21 @@ ~ SPDX-License-Identifier: Apache-2.0 --> -# pure::variants +# pure::variants extension - -!!! info - The Docker image name for this image is `/pure-variants` or `/pure-variants` - where `` is one of the following options: +!!! info "" - - `capella/remote` - - `t4c/client/remote` - - `eclipse/remote` + This extension in only available when using `capella` or `eclipse` as base. - for remote images or +!!! commercial "Commercial Product" - - `capella/base` - - `t4c/client/base` - - `eclipse/base` + `pure::variants` is a commercial product of PTC Inc.
+ More information: - for local images without the remote feature. +This extension installs the `pure::variants` client in Eclipse. If used with +`capella` as base, the Capella connector is also installed. -As part of this Docker image, `pure::variants` is installed into the Eclipse -software of the base image. In addition, it has some basic configuration -support, e.g., for setting the license server automatically during runtime. - -If the base image is based on Capella, the `pure::variants` Capella plugin is -installed in addition. +It contains some additional options to configure licenses during runtime. ## Build it yourself diff --git a/docs/docs/remote.md b/docs/docs/extensions/remote.md similarity index 60% rename from docs/docs/remote.md rename to docs/docs/extensions/remote.md index 80063a73..43208cb6 100644 --- a/docs/docs/remote.md +++ b/docs/docs/extensions/remote.md @@ -3,61 +3,38 @@ ~ SPDX-License-Identifier: Apache-2.0 --> -# Remote image +# Remote extension -The remote images allow to extend the +!!! info "" -- Capella base image (`capella/base`) -- T4C base image (`t4c/client/base`) -- Pure::variants image (`t4c/client/pure-variants`) + This extension in only available when using `capella`, `eclipse` or `papyrus` as base. -with a RDP or XPRA server and a metrics endpoint to measure the container -activity. +This extension embeds an RDP and Xpra server and a metrics endpoint to measure +the container activity into the container image. -It is a basic Linux server with a [Openbox](http://openbox.org/) installation. +The remote extension is enabled per default. If you want to decrease the +container size, you can skip the installation during the image build: -## Build it yourself +=== "Build via `make`" -### Preparation + `REMOTE_EXTENSION_ENABLED=0 make ` -#### Optional: Customize Openbox +=== "Build via `pack`" -!!! info "Openbox is only used for the connection method RDP." - -Feel free to adjust the configurations `remote/rc.xml` and `remote/menu.xml` to -satisfy custom Openbox configuration needs. - -If you like to use your own wallpaper, replace `remote/wallpaper.png`. - -### Build it manually with Docker - -```zsh -docker build -t $BASE_IMAGE/remote remote --build-arg BASE_IMAGE=$BASE_IMAGE -``` - -where `$BASE_IMAGE` is `capella/base`, `t4c/client/base` or -`t4c/client/pure-variants` (depends on the image you'd like to extend with -remote functionality) + Pass `--env REMOTE_EXTENSION_ENABLED=0` to the `pack build` command. ## Run the container Replace the followings variables: -- `$BASE_IMAGE` with `capella/base`, `t4c/client/base` or - `t4c/client/pure-variants`. Please check the individual section - `In a remote container (RDP)` of the individual base image documentation - pages for additional configuration options. - === "Connect via RDP" - The container image contains a `xrdp` server. To use RDP to connect to the container, run the container with the following command: + The container image contains a `xrdp` server. To use RDP to connect to the container, add the following flags to your `docker run` command: ```zsh - docker run -d \ - -p $RDP_EXTERNAL_PORT:3389 \ - -e CONNECTION_METHOD=xrdp \ - -e RMT_PASSWORD=$RMT_PASSWORD \ - $BASE_IMAGE/remote + -p $RDP_EXTERNAL_PORT:3389 \ + -e CONNECTION_METHOD=xrdp \ + -e RMT_PASSWORD=$RMT_PASSWORD ``` Replace `$RDP_EXTERNAL_PORT` with the external port that the RDP @@ -83,14 +60,12 @@ Replace the followings variables: === "Connect via XPRA" - The container image contains a `xpra-html5` server. To use XPRA via HTML5 to connect to the container, run the container with the following command: + The container image contains a `xpra-html5` server. To use XPRA via HTML5 to connect to the container, add the following flags to your `docker run` command: ```zsh - docker run -d \ - -p $XPRA_PORT:10000 \ - -e CONNECTION_METHOD=xpra \ - -e XPRA_SUBPATH="/" \ - $BASE_IMAGE/remote + -p $XPRA_PORT:10000 \ + -e CONNECTION_METHOD=xpra \ + -e XPRA_SUBPATH="/" ``` !!! warning "Authentication" diff --git a/docs/docs/extensions/t4c.md b/docs/docs/extensions/t4c.md new file mode 100644 index 00000000..ab8061a0 --- /dev/null +++ b/docs/docs/extensions/t4c.md @@ -0,0 +1,342 @@ + + +# TeamForCapella client base + +!!! info "" + + This extension in only available when using `capella` as base. + +!!! commercial "Commercial Product" + + TeamForCapella is a commercial product of OBEO.
+ More information: + +The T4C base image builds on top of the Capella base image and installs the T4C +client plugins. + +## Build it yourself + +### Preparation + +#### Download TeamForCapella bundle + +1. Download a Team for Capella client for Linux from + + + Note that the T4C client version must match the version for Capella itself. + To obtain a Linux T4C client version below 5.2 you may want to contact + [Obeo](https://www.obeosoft.com/en/team-for-capella-download) to get a + bundle. + +1. Extract the downloaded archive. The extracted folder comes with a `.zip` + file containing the T4C client: + + ```text + $ tree -L 2 TeamForCapella-5.0.0-linux.gtk.x86_64 + TeamForCapella-5.0.0-linux.gtk.x86_64 + ├── (...) + └── updateSite + └── com.thalesgroup.mde.melody.team.license.update-5.0.0-202012091024.zip + ``` + +1. That `.zip` file needs to be copied into the subdirectory + `t4c/updateSite/$CAPELLA_VERSION` of the present repository. + +#### Optional: Add feature patches + +It is possible to provide feature patches for our t4c base image that are +installed after the initial installation. To install such feature patches, you +have to do the following things. + +1. The feature patch `.zip` file needs to be copied into the subdirectory + `t4c/updateSite/$CAPELLA_VERSION` of the present repository +1. You have to create the `patch_info.csv` file inside the same subdirectory + if not yet existing +1. You have to add a new line to the `patch_info.csv` having the following + format: + + ```csv + ,, + ``` + + In case that you have one feature patch zip containing different things you + want to install you can provide multiple _install iu_, each with a + whitespace seperated. So in this case the `patch_info.csv` would contain a + line with the following format: + + ```csv + , ... , + ``` + +Please ensure that the `patch_info.csv` contains an empty line at the end +otherwise the last feature patch might not be installed. + +### Build it manually with Docker + +Build the container: + +```zsh +docker build -t t4c/client/base --build-arg CAPELLA_VERSION=$CAPELLA_VERSION t4c +``` + +## Run the container + +### Capella with T4C client + +Running the T4C client container is analogous to the Capella Base container. +Please run the +[instructions of the Capella Base container](../base.md#run-the-container), but +add the following environment variables during the `docker run` command: + +```zsh + -e T4C_LICENCE_SECRET=XXX \ + -e T4C_JSON='[{"repository": "", "port": 0, "host": "", "instance": "", "protocol": "ssl"}]' \ + -e T4C_SERVER_HOST=$T4C_SERVER_HOST \ + -e T4C_SERVER_PORT=$T4C_SERVER_PORT \ + -e T4C_REPOSITORIES=$T4C_REPOSITORIES \ + -e T4C_USERNAME=$T4C_USERNAME \ +``` + +Please replace the followings variables: + +- `$T4C_LICENCE_SECRET` with your TeamForCapella licence secret. +- `$T4C_USERNAME` with the username that is suggested when connecting to t4c. +- One of the two options: + + - `$T4C_JSON` with a list of repositories with name, host, port and instance name as JSON: + ```json + [ + { + "repository": "repoCapella", + "host": "localhost", + "port": 2036, + "instance": "", //optional, required if the repository names are not unique + "protocol": "ssl" //optional, defaults to ssl + } + ] + ``` + + The environment variables `$T4C_SERVER_HOST`, `$T4C_SERVER_PORT` and `$T4C_REPOSITORIES` will be ignored. + + - Three environment variables: + - `$T4C_SERVER_HOST` with the IP-Address of your T4C server (default: `127.0.0.1`). + - `$T4C_SERVER_PORT` with the port of your T4C server (default: `2036`). + - `$T4C_REPOSITORIES` with a comma-seperated list of repositories. These repositories show + up as default options on connection (e.g. `repo1,repo2`). + +When Capella has started, you should see the T4C models in the dropdown menu of +the connection dialog. + +### TeamForCapella Exporter + +!!! info + + The exporter can export a model from Git to a TeamForCapella repository with the `merge`-strategy of TeamForCapella. + +The T4C client exporter image imports a model from a git repository and exports +it to a T4C server. + +Run the following command to export from Git to T4C: + +```zsh +docker run -d \ + -e GIT_REPO_URL=https://github.com/example/example.git \ + -e GIT_REPO_BRANCH=main \ + -e GIT_USERNAME=user \ + -e GIT_PASSWORD=password \ + -e T4C_REPO_HOST=localhost \ + -e T4C_REPO_PORT=2036 \ + -e T4C_REPO_NAME=repoCapella \ + -e T4C_PROJECT_NAME=test \ + -e T4C_USERNAME=user \ + -e T4C_PASSWORD=password \ + -e LOG_LEVEL=DEBUG \ + t4c/client/base export +``` + +You can find the description for these values in the run instructions of the +[importer](#teamforcapella-importer). + +### TeamForCapella Importer + +!!! info + + The importer exports a model from a TeamForCapella repository to a Git repository. + +The T4C client backup image imports a model from a TeamForCapella server and +exports it to a Git repository. It can be used as a backup solution, for +example, in a scheduled job. + +## Run the container + +Please run the following command to run the backup from T4C to Git: + +```zsh +docker run -d \ + -e GIT_REPO_URL=https://github.com/example/example.git \ + -e GIT_REPO_BRANCH=main \ + -e GIT_USERNAME=user \ + -e GIT_PASSWORD=password \ + -e T4C_REPO_HOST=localhost \ + -e T4C_REPO_PORT=2036 \ + -e T4C_CDO_PORT=12036 \ + -e T4C_REPO_NAME=repoCapella \ + -e T4C_PROJECT_NAME=test \ + -e T4C_USERNAME=user \ + -e T4C_PASSWORD=password \ + -e LOG_LEVEL="DEBUG" \ + -e INCLUDE_COMMIT_HISTORY=false \ + t4c/client/base backup +``` + +Set the following values for the corresponding keys: + +- `GIT_REPO_URL`: URL to the target Git repository where the model will be + pushed to. All URI-formats supported by the `git clone` command will work. + You can provide HTTP credentials via the `GIT_USERNAME` and `GIT_PASSWORD` + variables (see below). +- `GIT_REPO_BRANCH`: branch of the Git repository. +- `GIT_USERNAME`: Git username if the repository is access protected. +- `GIT_PASSWORD`: Git password that is used during cloning from and pushing to + the Git repository. +- `T4C_REPO_HOST`: hostname to the T4C server. The same value that you enter in + Capella to connect to a remote repository. +- `T4C_REPO_PORT`: port to the T4C server. The same value that you enter in + Capella to connect to a remote repository. Defaults to 2036. +- `T4C_REPO_NAME`: T4C repository name. The same value that you enter in + Capella to connect to a remote repository. +- `T4C_PROJECT_NAME`: name of the Capella project. It's displayed in the + Capella project explorer and in the last step when connecting to a remote + repository. +- `T4C_USERNAME`: T4C username that is used during the import. The user needs + to have access to the repository. +- `T4C_PASSWORD`: T4C password that is used during the import. +- `LOG_LEVEL`: your preferred logging level (all Python logging levels are + supported). +- `INCLUDE_COMMIT_HISTORY`: `true` or `false` to define if the T4C commit + history should be exported. Important: Exporting the commit history can take + a few hours for large models. + +## Extract TeamForCapella commit messages to Git + +The importer extracts the commit messages from TeamForCapella and adds them to +the Backup commit description. The commit has the format: + +```yaml +Backup + +- user: admin + time: '2024-03-25T16:51:27.697000+00:00' + description: '' +- user: admin + time: '2024-03-25T16:51:20.523000+00:00' + description: null +- user: admin + time: '2024-03-25T16:51:09.755000+00:00' + description: Second Example commit +- user: admin + time: '2024-03-25T16:50:57.138000+00:00' + description: First example commit +``` + +The commit body is always in the YAML format. + +## Testing + +### Manual Testing + +For development purposes, you can test the importer / backup locally. + +!!! warning + + For the next steps, you need a running TeamForCapella server and a TeamForCapella license server. + +1. Start a lightweight local Git server with the following command: + + ```zsh + make run-local-git-server + ``` + +1. Create a new TeamForCapella repository via the REST API. If you want to + include the user information in the commits, you have to enable + authentication. +1. Set the `T4C_REPO_NAME` environment variable to the repository name that + you chose in the previous step: + + ```zsh + export T4C_REPO_NAME="repoCapella" + ``` + +1. Open Capella and create a new Capella project with the project name "test". +1. [Export the project to the TeamForCapella repository](https://dsd-dbs.github.io/capella-collab-manager/user/tools/capella/teamforcapella/export/export-to-t4c/) +1. Run the following command to start the importer: + + ```zsh + make run-t4c/client/backup + ``` + +1. Clone the sample repository: + + ```zsh + git clone 'http://localhost:10001/git/git-test-repo.git' + ``` + +1. Check the commit history and the model in the repository. + +## Testing + +### TeamForCapella Exporter + +For development purposes, you can test the exporter locally. + +!!! warning + + For the next steps, you need a running TeamForCapella server. + +1. Start a lightweight local Git server with the following command: + + ```zsh + make run-local-git-server + ``` + +1. Clone the sample repository:# + + ```zsh + git clone 'http://localhost:10001/git/git-test-repo.git' + ``` + +1. Copy the model to the newly created repository. Push the model you'd like + to export: + + ```zsh + git add . + git commit -m "Initial commit" + git push + ``` + +1. Set the `GIT_REPO_ENTRYPOINT` environment variable to the relative path + from the root of the repository to the aird file: + + ```zsh + export GIT_REPO_ENTRYPOINT="path/to/your/model.aird" + ``` + +1. Create a new TeamForCapella repository via the REST API. +1. Set the `T4C_REPO_NAME` environment variable to the repository name that + you chose in the previous step: + + ```zsh + export T4C_REPO_NAME="repoCapella" + ``` + +1. Run the following command to start the exporter: + + ```zsh + make run-t4c/client/exporter + ``` + +1. Connect to the repository from a Capella client to verify that the model + was exported correctly. diff --git a/docs/docs/git-hooks/egit-failed-git-hook.png b/docs/docs/hints/git-hooks/egit-failed-git-hook.png similarity index 100% rename from docs/docs/git-hooks/egit-failed-git-hook.png rename to docs/docs/hints/git-hooks/egit-failed-git-hook.png diff --git a/docs/docs/git-hooks/egit-failed-git-hook.png.license b/docs/docs/hints/git-hooks/egit-failed-git-hook.png.license similarity index 100% rename from docs/docs/git-hooks/egit-failed-git-hook.png.license rename to docs/docs/hints/git-hooks/egit-failed-git-hook.png.license diff --git a/docs/docs/git-hooks/git-hooks.md b/docs/docs/hints/git-hooks/index.md similarity index 100% rename from docs/docs/git-hooks/git-hooks.md rename to docs/docs/hints/git-hooks/index.md diff --git a/docs/docs/eclipse/memory-options.md b/docs/docs/hints/memory-options.md similarity index 93% rename from docs/docs/eclipse/memory-options.md rename to docs/docs/hints/memory-options.md index 5423db26..05fdf8b5 100644 --- a/docs/docs/eclipse/memory-options.md +++ b/docs/docs/hints/memory-options.md @@ -5,6 +5,10 @@ # Memory options for Eclipse +!!! info + + This section applies if using `eclipse`, `capella` or `papyrus`. + To specify fixed memory options for the JVM, you can pass the following environment variables to the `docker run` commands: diff --git a/docs/docs/index.md b/docs/docs/index.md index 9988b5dd..b098bb01 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -3,124 +3,105 @@ ~ SPDX-License-Identifier: Apache-2.0 --> -# Welcome +# Introduction Welcome to the MBSE Docker images repository. Initially started for our [Capella Collaboration Manager](https://github.com/DSD-DBS/capella-collab-manager) to run Capella in a browser, we now offer a variety of Docker images to -automate processes in the MBSE context. +automate processes in the MBSE context, which can be extended with compatible +extensions. -## Use prebuilt images from Github packages +

-For license reasons, we are only able to provide the following prebuilt public -images at this time: + +- ![Capella icon](./base/capella/logo.png){: .middle style="height:30px" } __Capella__ -- [`base`](base.md) -- [`capella/base`](capella/base.md) (without dropins or plugins) + --- -If you need another image, please follow the -[`Build images locally`](#build-images-locally) or -[`Build images in a CI/CD environment`](#build-images-in-a-cicd-environment) -instructions. + Eclipse Capella client. -## Build images locally + [:octicons-arrow-right-24: Build & Customize locally](./base/capella/index.md#build-it-yourself)
+ [:octicons-arrow-right-24: Use pre-built images from ghcr.io](./base/capella/index.md#use-the-prebuilt-image) -To get started, please clone this repository and include all submodules: + Available extensions: -```zsh -git clone https://github.com/DSD-DBS/capella-dockerimages.git -``` + [:material-tab-plus: Remote extension](./extensions/remote.md)
+ [:material-tab-plus: TeamForCapella extension](./extensions/t4c.md)
+ [:material-tab-plus: pure::variants extension](./extensions/pure-variants.md) -### Build images with GNU Make +- ![Papyrus icon](./base/papyrus/logo.png){: .middle style="height:30px" } __Papyrus__ -If you have [GNU Make](https://www.gnu.org/software/make/manual/make.html) -installed on your system, you can make use of our Makefile to build, run and -debug our Docker images. + --- - -!!! warning + Eclipse Papyrus client. - The minimum required GNU Make version is `3.82`, however we recommend version `4.X`. Use version `4.4` for the best experience. The **preinstalled Make version on macOS is `3.81` and is not supported by us**. Please update the version to >= `3.82`. + [:octicons-arrow-right-24: Build & Customize locally](./base/papyrus/index.md) +

- -!!! info + Available extensions: - When running the build targets with `PUSH_IMAGES=1`, they get pushed to your preferred registry after each build. + [:material-tab-plus: Remote extension](./extensions/remote.md) -For each image, please execute the steps described in the preparation section -in the documentation for each image. +- ![Eclipse icon](./base/eclipse/logo.png){: .middle style="height:30px" } __Eclipse__ -Then, just run the following command: + --- -```sh -make -``` + Eclipse IDE with EGit preinstalled. -to build the image and it's dependencies or + [:octicons-arrow-right-24: Build & Customize locally](./base/eclipse/index.md) -```sh -make run- -``` + Available extensions: + + [:material-tab-plus: Remote extension](./extensions/remote.md)
+ [:material-tab-plus: pure::variants extension](./extensions/pure-variants.md) + +- ![Jupyter icon](./base/jupyter/logo.png){: .middle style="height:30px" } __Jupyter__ + + --- + + Jupyter customized with Capella libraries. + + [:octicons-arrow-right-24: Build & Customize locally](./base/jupyter/index.md) -to build the images, all dependencies and run the image. +
-In case you executed the preparation sections for all images, you can run: +## Prerequisites for Local Image Building -```sh -make all +If you want to build the images locally, clone this repository: + +```zsh +git clone https://github.com/DSD-DBS/capella-dockerimages.git ``` -### Build images manually with Docker - -It's important to strictly follow the sequence. Several Docker images depend on -each other. The full dependency graph for the images looks like: - -```mermaid -flowchart LR - BASE(base) --> CAPELLA_BASE(capella/base) - BASE(base) --> JUPYTER(jupyter-notebook) - BASE(base) --> PAPYRUS_BASE(papyrus/base) --> PAPYRUS_REMOTE(papyrus/remote) - BASE(base) --> ECLIPSE_BASE(eclipse/base) --> ECLIPSE_REMOTE(eclipse/remote) --> ECLIPSE_REMOTE_PURE_VARIANTS(eclipse/remote/pure-variants) - CAPELLA_BASE(capella/base) --> T4C_CLIENT_BASE(t4c/client/base) - CAPELLA_BASE(capella/base) --> CAPELLA_REMOTE(capella/remote) --> CAPELLA_REMOTE_PURE_VARIANTS(capella/remote/pure-variants) - T4C_CLIENT_BASE(t4c/client/base) --> T4C_CLIENT_REMOTE(t4c/client/remote) --> T4C_CLIENT_REMOTE_PURE_VARIANTS(t4c/client/remote/pure-variants) - - style BASE fill:#b22222,color:#000000 - style CAPELLA_BASE fill:#8feb34,color:#000000 - style JUPYTER fill:#f5626c,color:#000000 - style PAPYRUS_BASE fill:#5f9ea0,color:#000000 - style ECLIPSE_BASE fill:#fafad2,color:#000000 - style T4C_CLIENT_BASE fill:#1e90ff,color:#000000 - - style CAPELLA_REMOTE fill:#228b22,color:#000000 - style T4C_CLIENT_REMOTE fill:#228b22,color:#000000 - style ECLIPSE_REMOTE fill:#228b22,color:#000000 - style PAPYRUS_REMOTE fill:#228b22,color:#000000 - - style CAPELLA_REMOTE_PURE_VARIANTS fill:#62f5f2,color:#000000 - style T4C_CLIENT_REMOTE_PURE_VARIANTS fill:#62f5f2,color:#000000 - style ECLIPSE_REMOTE_PURE_VARIANTS fill:#62f5f2,color:#000000 +We use buildpacks to build the images. To install the pack CLI, follow +[these instructions](https://buildpacks.io/docs/for-platform-operators/how-to/integrate-ci/pack/). +We use experimental features of buildpacks. To enable experimental support, +run: + +```zsh +pack config experimental true ``` -Each highlighted color indicates the Dockerfile which is used to build the -image: +If you want to learn why we use buildpacks, you can read more about it +[here](./development/buildpacks.md). - -:material-checkbox-blank-circle:{ style="color: #b22222 " } [Base](base.md)
-:material-checkbox-blank-circle:{ style="color: #8feb34 " } [Capella Base](capella/base.md)
-:material-checkbox-blank-circle:{ style="color: #fafad2 " } [Eclipse Base](eclipse/base.md)
-:material-checkbox-blank-circle:{ style="color: #5f9ea0 " } [Papyrus Base](papyrus/base.md)
-:material-checkbox-blank-circle:{ style="color: #1e90ff " } [T4C Client Base](capella/t4c/base.md)
-:material-checkbox-blank-circle:{ style="color: #228b22 " } [Remote](remote.md)
-:material-checkbox-blank-circle:{ style="color: #62f5f2 " } [pure::variants](pure-variants.md)
-:material-checkbox-blank-circle:{ style="color: #f5626c " } [Jupyter notebook](jupyter/index.md)
+## Build Images with GNU Make -**Make sure that all `docker` commands are executed in the root directory of -the repository.** +If you have [GNU Make](https://www.gnu.org/software/make/manual/make.html) +installed on your system, you can make use of our Makefile to build, run and +debug our Docker images. -For each image, you'll find documentation how to build & run the image -manually. +!!! warning + + The minimum required GNU Make version is `3.82`, however we recommend version `4.X`. Use version `4.4` for the best experience. The **preinstalled Make version on macOS is `3.81` and is not supported by us**. Please update the version to >= `3.82`. + +!!! info + + When running the build targets with `PUSH_IMAGES=1`, they get pushed to your preferred registry after each build. + +For each image, please execute the steps described in the preparation section +in the corresponding documentation. ## Build images in a CI/CD environment diff --git a/docs/docs/migration.md b/docs/docs/migration.md new file mode 100644 index 00000000..e2c5105c --- /dev/null +++ b/docs/docs/migration.md @@ -0,0 +1,22 @@ + + +# Migration from v2.0.0 to v3.0.0 + +- The `capella_loop.sh` and `CAPELLA_VERSIONS` environment variables were + dropped. To build images for different Capella versions, the the Make target + multiple times with different `CAPELLA_VERSION` environment variables. +- Bulidpacks is required as new dependency. +- The `t4c/client/base`, `t4c/client/remote`, + `t4c/client/remote/pure-variants`, `capella/remote/pure-variants`, + `eclipse/remote/pure-variants`, `eclipse/remote`, `capella/remote` and + `papyrus/remote` were removed. The other targets don't have the suffix + `/base` anymore. Instead, there are only the `capella`, `papyrus`, `eclipse` + and `jupyter` targets. The `t4c` and `pure-variants` extensions are + automatically installed if the archives are placed in the correct location. + Remote support is automatically added to the image. To disable it, set + `REMOTE_CONNECTION_SUPPORT=0`. +- The `builder` target was removed. It can be still accessed in the Git + history, but is no longer maintained. diff --git a/docs/docs/stylesheets/commercial.css b/docs/docs/stylesheets/commercial.css new file mode 100644 index 00000000..5ab5202f --- /dev/null +++ b/docs/docs/stylesheets/commercial.css @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +.md-typeset .admonition.commercial, +.md-typeset details.commercial { + border-color: rgb(43, 155, 70); +} +.md-typeset .commercial > .admonition-title, +.md-typeset .commercial > summary { + background-color: rgba(43, 155, 70, 0.1); +} +.md-typeset .commercial > .admonition-title::before, +.md-typeset .commercial > summary::before { + background-color: rgb(43, 155, 70); + -webkit-mask-image: var(--md-admonition-icon--commercial); + mask-image: var(--md-admonition-icon--commercial); +} diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 8bbd34e6..2f8e8c69 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -12,10 +12,25 @@ theme: features: - content.code.copy - content.code.annotate + - navigation.sections + icon: + admonition: + commercial: fontawesome/solid/dollar-sign + nav: - Introduction: index.md - - Base: base.md - - Git hooks (pre-commit): git-hooks/git-hooks.md + - Base: + - Capella: base/capella/index.md + - Papyrus: base/papyrus/index.md + - Eclipse: base/eclipse/index.md + - Jupyter: base/jupyter/index.md + - Extensions: + - TeamForCapella: extensions/t4c.md + - 'pure::variants': extensions/pure-variants.md + - Remote: extensions/remote.md + - Hints: + - Memory Options for JVM: hints/memory-options.md + - Git hooks (pre-commit): hints/git-hooks/index.md - CI/CD templates: - Index: ci-templates/index.md - Gitlab: @@ -27,29 +42,9 @@ nav: - Test container clean up: ci-templates/gitlab/cleanup.md - Github: - Diagram cache: ci-templates/github/diagram-cache.md - - Jupyter Notebooks: jupyter/index.md - - Capella: - - Introduction: capella/introduction.md - - Base: capella/base.md - - Remote: remote.md - - Provisioning: capella/provisioning.md - - 'pure::variants': pure-variants.md - - Build from source: capella/build-from-source.md - - Team4Capella client: - - Introduction: capella/t4c/introduction.md - - Base: capella/t4c/base.md - - Exporter: capella/t4c/exporter.md - - Importer: capella/t4c/importer.md - - Remote: remote.md - - Papyrus: - - Base: papyrus/base.md - - Eclipse: - - Base: eclipse/base.md - - Memory Options: eclipse/memory-options.md - - 'pure::variants': pure-variants.md - - Remote: remote.md - Development: - Testing: development/testing.md + - Buildpacks: development/buildpacks.md repo_url: https://github.com/DSD-DBS/capella-dockerimages edit_uri: edit/master/docs/docs @@ -62,6 +57,7 @@ markdown_extensions: - abbr - pymdownx.snippets - attr_list + - md_in_html - pymdownx.emoji: emoji_index: !!python/name:material.extensions.emoji.twemoji emoji_generator: !!python/name:material.extensions.emoji.to_svg @@ -80,4 +76,7 @@ markdown_extensions: extra: generator: false -copyright: Copyright © 2022-2023 DB InfraGO AG +extra_css: + - stylesheets/commercial.css + +copyright: Copyright © 2022-2024 DB InfraGO AG diff --git a/eclipse/set_memory_flags.py b/eclipse/set_memory_flags.py old mode 100644 new mode 100755 index cfdd5406..1f6fc4ca --- a/eclipse/set_memory_flags.py +++ b/eclipse/set_memory_flags.py @@ -1,3 +1,5 @@ +#!/usr/bin/python + # SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 """Set the memory flags in the *.ini file for Eclipse applications. @@ -80,8 +82,8 @@ def append_flag_to_file( if __name__ == "__main__": eclipse_executable = pathlib.Path(os.environ["ECLIPSE_EXECUTABLE"]) - _memory_min = os.environ["MEMORY_MIN"].strip() - _memory_max = os.environ["MEMORY_MAX"].strip() + _memory_min = os.environ.get("MEMORY_MIN", "70%").strip() + _memory_max = os.environ.get("MEMORY_MAX", "90%").strip() ini_path = eclipse_executable.with_suffix(".ini") diff --git a/remote/Dockerfile b/remote/Dockerfile deleted file mode 100644 index 36ba9019..00000000 --- a/remote/Dockerfile +++ /dev/null @@ -1,90 +0,0 @@ -# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -ARG BASE_IMAGE=capella/base -FROM ${BASE_IMAGE} - -# Port if xrdp is used as connection method. -EXPOSE 3389 - -# Port if xpra is used as connection method. -EXPOSE 10000 - -ARG DEBIAN_FRONTEND=noninteractive - -SHELL ["/bin/bash", "-euo", "pipefail", "-c"] -ENV SHELL=/bin/bash - -# Install RDP (XRDP with XORG) -USER root - -# Install xrdp and dependencies -RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends \ - xrdp \ - xserver-xorg-core \ - xorgxrdp \ - openbox \ - obconf \ - gettext-base \ - xprintidle \ - nitrogen && rm -rf /var/lib/apt/lists/* - -ARG XPRA_REGISTRY=https://xpra.org - -# Install xpra and dependencies -RUN wget -qO /usr/share/keyrings/xpra.asc ${XPRA_REGISTRY}/xpra.asc && \ - wget -qO /etc/apt/sources.list.d/xpra.sources https://raw.githubusercontent.com/Xpra-org/xpra/master/packaging/repos/bookworm/xpra.sources && \ - sed -i "s|https://xpra.org|${XPRA_REGISTRY}|" /etc/apt/sources.list.d/xpra.sources && \ - apt-get update && \ - apt-get install -y \ - --no-install-recommends \ - xpra \ - xpra-x11 \ - xpra-html5=10.1-r10-1 \ - apache2-utils \ - nginx && \ - rm -rf /var/lib/apt/lists/* - -COPY rc.xml /etc/xdg/openbox/rc.xml -COPY menu.xml /etc/xdg/openbox/menu.xml - -# Setup Nitrogen (Desktop background) -COPY wallpaper.png /tmp/wallpaper.png -COPY bg-saved.cfg /home/techuser/.config/nitrogen/bg-saved.cfg - -# Copy Supervisor Configuration -RUN pip install --no-cache-dir supervisor==4.2.5 -COPY supervisord.conf /etc/supervisord.conf -COPY supervisord.*.conf /tmp/supervisord/ - -# Copy nginx configuration for xpra -COPY nginx.conf /etc/nginx/nginx.conf -COPY error.html /usr/share/nginx/html/error.html - -# Allow any user to start the RDP server -# Depending on the base image used, Xwrapper.config may (not) be available and has to be created. -RUN sed -i 's/allowed_users=console/allowed_users=anybody/g' /etc/X11/Xwrapper.config \ - || echo "allowed_users=anybody" > /etc/X11/Xwrapper.config && \ - chmod 666 /etc/shadow - -# Set permissions -RUN mkdir -p /run/xrdp/sockdir && \ - chown -R techuser /etc/xrdp /run/xrdp /var/log/xrdp* && \ - chown techuser /var/log && \ - chown techuser /etc/supervisord.conf /var/log/nginx /var/log/nginx/* && \ - chown techuser /etc/nginx && \ - touch /etc/environment && chown techuser /etc/environment - -WORKDIR /home/techuser - -COPY startup.sh .startup.sh -RUN chmod 755 .startup.sh /home/techuser/.config/openbox/autostart - -# Prepare idletime metric endpoint -RUN pip install --no-cache-dir prometheus-client==0.17.1 - -COPY metrics.py .metrics.py -RUN chown techuser /home/techuser/.metrics.py - -USER techuser -ENTRYPOINT [ "/home/techuser/.startup.sh" ] diff --git a/remote/startup.sh b/remote/startup.sh deleted file mode 100755 index b5cb75d8..00000000 --- a/remote/startup.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/bash - -# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -set -exuo pipefail - -RESOLVED_CONECTION_METHOD=${CONNECTION_METHOD:-xrdp} - -if [ "$RESOLVED_CONECTION_METHOD" == "xrdp" ]; -then - if [ "$(whoami)" == "root" ] || [ "$(whoami)" == "techuser" ]; - then - salt=$(openssl rand -base64 16) - password_hash=$(openssl passwd -6 -salt ${salt} "${RMT_PASSWORD:?}") - line=$(grep techuser /etc/shadow); - echo ${line%%:*}:${password_hash}:${line#*:*:} > /etc/shadow; - else - echo "Only techuser and root are supported as users."; - exit 1; - fi -fi - -# Replace Variables in the nginx.conf -sed -i "s|__XPRA_SUBPATH__|${XPRA_SUBPATH:-/}|g" /etc/nginx/nginx.conf -sed -i "s|__XPRA_CSP_ORIGIN_HOST__|${XPRA_CSP_ORIGIN_HOST:-}|g" /etc/nginx/nginx.conf - -unset RMT_PASSWORD - -# Run preparation scripts -for filename in /opt/setup/*.py; do - [ -e "$filename" ] || continue - echo "Executing Python script '$filename'..." - python3 $filename -done - -for filename in /opt/setup/*.sh; do - [ -e "$filename" ] || continue - echo "Executing shell script '$filename'..." - source $filename -done - -# Load supervisord configuration for connection method -SUPERVISORD_CONFIG_PATH=/tmp/supervisord/supervisord.${RESOLVED_CONECTION_METHOD}.conf -if [ -f "$SUPERVISORD_CONFIG_PATH" ]; then - echo "Adding '$SUPERVISORD_CONFIG_PATH' to configuration." - cat $SUPERVISORD_CONFIG_PATH >> /etc/supervisord.conf -else - echo "No '$SUPERVISORD_CONFIG_PATH' found'. Falling back to xrdp configuration." - cat /tmp/supervisord/supervisord.xrdp.conf >> /etc/supervisord.conf -fi - -echo "---START_SESSION---" - -exec supervisord diff --git a/remote/supervisord.conf b/remote/supervisord.conf deleted file mode 100644 index 48f98c59..00000000 --- a/remote/supervisord.conf +++ /dev/null @@ -1,13 +0,0 @@ -# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -[program:idletime] -; Return idle time of xserver in seconds from xprintidle -command=python .metrics.py -user=techuser -autorestart=true -environment=DISPLAY=":10" - -[supervisord] -nodaemon=true -childlogdir=/var/log diff --git a/remote/tests/test_metrics.py b/remote/tests/test_metrics.py deleted file mode 100644 index 2b78a486..00000000 --- a/remote/tests/test_metrics.py +++ /dev/null @@ -1,68 +0,0 @@ -# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 -"""Unit tests for metrics.""" -import time -import typing as t -from unittest import mock - -import metrics -import pytest - - -@pytest.fixture(name="idler") -def fixture_idler() -> metrics.IdleTimer: - """Return an instance of `metrics.IdleTime`""" - return metrics.IdleTimer() - - -def print_no_display(*__: t.Any, **_: t.Any) -> mock.MagicMock: - """ - Just print: 'could not open display', the response of xprintidle - when there is no X server. - """ - return mock.MagicMock(stdout="couldn't open display") - - -def test_get_idletime_had_no_display_on_init_but_later() -> None: - with mock.patch("subprocess.run", print_no_display): - idler = metrics.IdleTimer() - _, init_idletime = idler.first_checkpoint - - idletime = idler.get_idletime() - - assert init_idletime == -1 - assert idletime > -1 - - -def test_get_idletime_never_had_a_display_and_returns_minus_one() -> None: - with mock.patch("subprocess.run", print_no_display): - idler = metrics.IdleTimer() - _, init_idletime = idler.first_checkpoint - - time.sleep(10) - - idletime = idler.get_idletime() - - assert init_idletime == -1 - assert idletime == -1 - - -def test_get_idletime_works(idler: metrics.IdleTimer) -> None: - idletime = idler.get_idletime() - - assert isinstance(idletime, float) - - -def test_get_idletime_increases_after_display_is_closed( - idler: metrics.IdleTimer, -) -> None: - idletime = idler.get_idletime() - - time.sleep(60) - - mock.patch("subprocess.run", print_no_display) - - second_idletime = idler.get_idletime() - - assert isinstance(idletime, float) and isinstance(second_idletime, float) - assert idletime < second_idletime diff --git a/remote/wallpaper.png b/remote/wallpaper.png deleted file mode 100644 index e69de29b..00000000 diff --git a/t4c/.dockerignore.template b/t4c/.dockerignore.template deleted file mode 100644 index 59fb8993..00000000 --- a/t4c/.dockerignore.template +++ /dev/null @@ -1,7 +0,0 @@ -# Please only edit the .dockerignore.template file! -# The .dockerignore file is gitignored and updated by the Makefile -# Environment variables are replaced before build with `envsubst` - -.dockerignore.template -updateSite/* -!updateSite/$CAPELLA_VERSION diff --git a/t4c/.dockerignore.template.license b/t4c/.dockerignore.template.license deleted file mode 100644 index 7ea22469..00000000 --- a/t4c/.dockerignore.template.license +++ /dev/null @@ -1,2 +0,0 @@ -SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors -SPDX-License-Identifier: Apache-2.0 diff --git a/t4c/Dockerfile b/t4c/Dockerfile deleted file mode 100644 index 5ed300c5..00000000 --- a/t4c/Dockerfile +++ /dev/null @@ -1,70 +0,0 @@ -# syntax=docker/dockerfile:1.4 - -# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -ARG BASE_IMAGE=capella/base -FROM ${BASE_IMAGE} - -ARG CAPELLA_VERSION="" -ENV CAPELLA_VERSION=${CAPELLA_VERSION} - -SHELL ["/bin/bash", "-euo", "pipefail", "-c"] -ENV SHELL=/bin/bash - -USER root - -RUN apt-get update && \ - apt-get install -y git-lfs xvfb xauth && \ - rm -rf /var/lib/apt/lists/* - -COPY docker_entrypoint.sh /docker_entrypoint.sh -RUN chmod 555 /docker_entrypoint.sh - -ENV DISPLAY :99 - -WORKDIR /opt/capella - -RUN chown -R techuser /opt/capella - -# Install T4C -COPY ./updateSite/$CAPELLA_VERSION /opt/updateSite -WORKDIR /opt/updateSite -RUN find /opt/updateSite -type f -name "*.zip" -exec chmod +r {} \; - -USER techuser -## Install T4C Plugins via the P2 API from Eclipse -RUN T4C_ZIP=$(find . -type f -iname "com.thalesgroup.mde.melody.team.license.update-*.zip" | head -n 1 | cut -c 3-); \ - /opt/capella/capella \ - -consoleLog \ - -application org.eclipse.equinox.p2.director \ - -noSplash \ - -repository jar:file:///opt/updateSite/$T4C_ZIP!/ \ - -installIU com.thalesgroup.mde.melody.collab.feature.feature.group,com.thalesgroup.mde.melody.collab.maintenance.feature.feature.group,com.thalesgroup.mde.melody.collab.licbranding.feature.feature.group && \ - chown -R techuser /opt/capella/configuration - -RUN PATCH_DIR=/opt/updateSite /opt/patch.sh - -USER root - -COPY setup_workspace_t4c.py /opt/setup/setup_workspace_t4c.py - -RUN cat >> /opt/capella/capella.ini <