├── .github └── workflows │ ├── latest.yml │ ├── lint.yml │ ├── release.yml │ ├── reuse.yaml │ └── tests.yml ├── .gitignore ├── .pipeline └── config.yml ├── .reuse └── dep5 ├── CONTRIBUTING.md ├── LICENSE ├── LICENSES └── Apache-2.0.txt ├── README.md ├── ci.sh ├── contrib └── perform-release.sh ├── cx-server-companion ├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── files │ ├── checkversion.js │ ├── cx-server-companion.sh │ ├── init-cx-server │ ├── init-nexus.js │ ├── nexus-init-repos.groovy │ ├── package-lock.json │ ├── package.json │ ├── server-default.cfg │ ├── status.js │ └── updateversion.js └── life-cycle-scripts │ ├── CX-SERVER-SECURITY.md │ ├── cx-server │ ├── cx-server-completion.bash │ ├── cx-server.bat │ └── server.cfg ├── docs ├── development │ ├── how-to-release.md │ └── running-cx-server-in-development.md └── operations │ ├── cx-server-operations-guide.md │ └── self-signed-tls.md ├── infrastructure-tests ├── Jenkinsfile ├── README.md ├── jenkins.yml ├── pipeline_config.yml ├── runTests.sh └── testing-jenkins.yml ├── jenkins-agent-k8s ├── Dockerfile └── Readme.md ├── jenkins-agent ├── Dockerfile └── Readme.md ├── jenkins-master ├── Dockerfile ├── README.md ├── init_jenkins.groovy ├── launch-jenkins-agent.sh └── plugins.txt ├── jenkinsfile-runner-github-action ├── Dockerfile └── README.md ├── jenkinsfile-runner ├── Dockerfile ├── Jenkinsfile ├── README.md └── docker-compose.test.yml └── project-piper-action-runtime ├── Dockerfile └── README.md /.github/workflows/latest.yml: -------------------------------------------------------------------------------- 1 | name: Build images and push (on master branch or on schedule) 2 | 3 | on: 4 | push: 5 | branches: 6 | - "**" 7 | schedule: 8 | - cron: '0 6 * * 1-4' 9 | 10 | jobs: 11 | push: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@master 15 | - name: Run CI 16 | run: | 17 | echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u ${{ secrets.DOCKERHUB_USER }} --password-stdin 18 | echo "${{ secrets.CR_PAT }}" | docker login https://ghcr.io -u ${{ secrets.CR_USER }} --password-stdin 19 | ./ci.sh latest 20 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | on: push 2 | name: Lint all Dockerfiles 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@master 8 | - name: Haskell Dockerfile Linter 9 | uses: docker://cdssnc/docker-lint-github-action 10 | with: 11 | # https://github.com/hadolint/hadolint/wiki/DL3008 12 | # https://github.com/hadolint/hadolint/wiki/DL3013 13 | # https://github.com/hadolint/hadolint/wiki/DL3016 14 | # https://github.com/hadolint/hadolint/wiki/DL3018 15 | args: --ignore DL3008 --ignore DL3013 --ignore DL3016 --ignore DL3018 16 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create new weekly Release (switched off) 2 | 3 | on: 4 | repository_dispatch: 5 | types: perform-release 6 | 7 | jobs: 8 | create-release: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Prepare Release 13 | run: | 14 | CURRENT_VERSION_LONG=$(curl --silent "https://api.github.com/repos/SAP/devops-docker-cx-server/releases" | jq -r '.[].tag_name' | head -n1) 15 | echo Current version: $CURRENT_VERSION_LONG 16 | CURRENT_VERSION=`echo $CURRENT_VERSION_LONG | cut -c 2- | cut -d. -f1` 17 | NEXT_VERSION=v$(expr $CURRENT_VERSION + 1) 18 | echo Next version: $NEXT_VERSION 19 | STATUS_CODE_FOR_NEXT_RELEASE=$(curl -s -o /dev/null -w "%{http_code}" "https://api.github.com/repos/SAP/devops-docker-cx-server/releases/tags/$NEXT_VERSION") 20 | if [ "$STATUS_CODE_FOR_NEXT_RELEASE" != "404" ]; then 21 | echo "Planned next release version ($NEXT_VERSION) already exists, aborting process" 22 | exit 1 23 | fi 24 | echo "PIPER_version=$NEXT_VERSION" >> $GITHUB_ENV 25 | 26 | - name: Build, test and push 27 | run: | 28 | echo "${{ secrets.CR_PAT }}" | docker login https://ghcr.io -u ${{ secrets.CR_USER }} --password-stdin 29 | echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u ${{ secrets.DOCKERHUB_USER }} --password-stdin 30 | ./ci.sh ${PIPER_version} 31 | 32 | - uses: SAP/project-piper-action@master 33 | with: 34 | piper-version: latest 35 | command: githubPublishRelease 36 | flags: --token ${{ secrets.GITHUB_TOKEN }} 37 | -------------------------------------------------------------------------------- /.github/workflows/reuse.yaml: -------------------------------------------------------------------------------- 1 | name: REUSE Compliance Check 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: REUSE Compliance Check 11 | uses: fsfe/reuse-action@v1.1 12 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Infrastructure Tests 2 | on: 3 | push: 4 | branches: [ master ] 5 | pull_request: 6 | branches: [ master ] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Infrastructure Tests 14 | working-directory: infrastructure-tests 15 | run: | 16 | chmod +x runTests.sh 17 | ./runTests.sh 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | infrastructure-tests/custom-environment.list 4 | infrastructure-tests/cx-server 5 | infrastructure-tests/jenkins-configuration/ 6 | infrastructure-tests/server.cfg 7 | *.DS_Store 8 | -------------------------------------------------------------------------------- /.pipeline/config.yml: -------------------------------------------------------------------------------- 1 | steps: 2 | githubPublishRelease: 3 | addClosedIssues: true 4 | addDeltaToLastRelease: true 5 | excludeLabels: 6 | - 'discussion' 7 | - 'duplicate' 8 | - 'invalid' 9 | - 'question' 10 | - 'wontfix' 11 | owner: 'SAP' 12 | repository: 'devops-docker-cx-server' 13 | releaseBodyHeader: '' 14 | -------------------------------------------------------------------------------- /.reuse/dep5: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: github.com/SAP/devops-docker-cx-server 3 | Upstream-Contact: The SAP Cloud SDK team 4 | Source: https://github.com/SAP/devops-docker-cx-server 5 | Disclaimer: The code in this project may include calls to APIs (“API Calls”) of 6 | SAP or third-party products or services developed outside of this project 7 | (“External Products”). 8 | “APIs” means application programming interfaces, as well as their respective 9 | specifications and implementing code that allows software to communicate with 10 | other software. 11 | API Calls to External Products are not licensed under the open source license 12 | that governs this project. The use of such API Calls and related External 13 | Products are subject to applicable additional agreements with the relevant 14 | provider of the External Products. In no event shall the open source license 15 | that governs this project grant any rights in or to any External Products,or 16 | alter, expand or supersede any terms of the applicable additional agreements. 17 | If you have a valid license agreement with SAP for the use of a particular SAP 18 | External Product, then you may make use of any API Calls included in this 19 | project’s code for that SAP External Product, subject to the terms of such 20 | license agreement. If you do not have a valid license agreement for the use of 21 | a particular SAP External Product, then you may only make use of any API Calls 22 | in this project for that SAP External Product for your internal, non-productive 23 | and non-commercial test and evaluation of such API Calls. Nothing herein grants 24 | you any rights to use or access any SAP External Product, or provide any third 25 | parties the right to use of access any SAP External Product, through API Calls. 26 | Please note that Docker images can contain other software which may be licensed under different licenses. 27 | For any usage of built Docker images please make sure to check the licenses of the artifacts contained in the images. 28 | 29 | Files: ** 30 | Copyright: 2017-2020 SAP SE or an SAP affiliate company and contributors 31 | License: Apache-2.0 32 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Guidance on how to contribute 2 | 3 | There are two primary ways to help: 4 | * using the issue tracker, and 5 | * changing the code-base. 6 | 7 | ## Developer Certificate of Origin (DCO) 8 | 9 | Due to legal reasons, contributors will be asked to accept a DCO before they submit the first pull request to this projects, this happens in an automated fashion during the submission process. SAP uses [the standard DCO text of the Linux Foundation](https://developercertificate.org/). 10 | 11 | ## Using the issue tracker 12 | 13 | Use the issue tracker to suggest feature requests, report bugs, and ask 14 | questions. This is also a great way to connect with the developers of the 15 | project as well as others who are interested in this solution. 16 | 17 | Use the issue tracker to find ways to contribute. Find a bug or a feature, 18 | mention in the issue that you will take on that effort, then follow the 19 | guidance below. 20 | 21 | ## Changing the code-base 22 | 23 | Generally speaking, you should fork this repository, make changes in your own 24 | fork, and then submit a pull-request. All new code should have been thoroughly 25 | tested end-to-end in order to validate implemented features and the presence or 26 | lack of defects. All new scripts and docker files _must_ come with automated (unit) 27 | tests. 28 | 29 | The contract of functionality exposed by docker files functionality needs 30 | to be documented, so it can be properly used. Implementation of a functionality 31 | and its documentation shall happen within the same commit(s). 32 | 33 | #### Consistent USER Instruction in the Dockerfile 34 | 35 | Set the user name (or UID) and the user group (or GID) to UID 1000 and GID 1000 to be consistent ith the Jenkins image. 36 | 37 | ```` 38 | USER 1000:1000 39 | ```` 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2017-2021 SAP SE or an SAP affiliate company and contributors 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSES/Apache-2.0.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | 3 | Version 2.0, January 2004 4 | 5 | http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, 6 | AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | 11 | 12 | "License" shall mean the terms and conditions for use, reproduction, and distribution 13 | as defined by Sections 1 through 9 of this document. 14 | 15 | 16 | 17 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 18 | owner that is granting the License. 19 | 20 | 21 | 22 | "Legal Entity" shall mean the union of the acting entity and all other entities 23 | that control, are controlled by, or are under common control with that entity. 24 | For the purposes of this definition, "control" means (i) the power, direct 25 | or indirect, to cause the direction or management of such entity, whether 26 | by contract or otherwise, or (ii) ownership of fifty percent (50%) or more 27 | of the outstanding shares, or (iii) beneficial ownership of such entity. 28 | 29 | 30 | 31 | "You" (or "Your") shall mean an individual or Legal Entity exercising permissions 32 | granted by this License. 33 | 34 | 35 | 36 | "Source" form shall mean the preferred form for making modifications, including 37 | but not limited to software source code, documentation source, and configuration 38 | files. 39 | 40 | 41 | 42 | "Object" form shall mean any form resulting from mechanical transformation 43 | or translation of a Source form, including but not limited to compiled object 44 | code, generated documentation, and conversions to other media types. 45 | 46 | 47 | 48 | "Work" shall mean the work of authorship, whether in Source or Object form, 49 | made available under the License, as indicated by a copyright notice that 50 | is included in or attached to the work (an example is provided in the Appendix 51 | below). 52 | 53 | 54 | 55 | "Derivative Works" shall mean any work, whether in Source or Object form, 56 | that is based on (or derived from) the Work and for which the editorial revisions, 57 | annotations, elaborations, or other modifications represent, as a whole, an 58 | original work of authorship. For the purposes of this License, Derivative 59 | Works shall not include works that remain separable from, or merely link (or 60 | bind by name) to the interfaces of, the Work and Derivative Works thereof. 61 | 62 | 63 | 64 | "Contribution" shall mean any work of authorship, including the original version 65 | of the Work and any modifications or additions to that Work or Derivative 66 | Works thereof, that is intentionally submitted to Licensor for inclusion in 67 | the Work by the copyright owner or by an individual or Legal Entity authorized 68 | to submit on behalf of the copyright owner. For the purposes of this definition, 69 | "submitted" means any form of electronic, verbal, or written communication 70 | sent to the Licensor or its representatives, including but not limited to 71 | communication on electronic mailing lists, source code control systems, and 72 | issue tracking systems that are managed by, or on behalf of, the Licensor 73 | for the purpose of discussing and improving the Work, but excluding communication 74 | that is conspicuously marked or otherwise designated in writing by the copyright 75 | owner as "Not a Contribution." 76 | 77 | 78 | 79 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 80 | of whom a Contribution has been received by Licensor and subsequently incorporated 81 | within the Work. 82 | 83 | 2. Grant of Copyright License. Subject to the terms and conditions of this 84 | License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, 85 | no-charge, royalty-free, irrevocable copyright license to reproduce, prepare 86 | Derivative Works of, publicly display, publicly perform, sublicense, and distribute 87 | the Work and such Derivative Works in Source or Object form. 88 | 89 | 3. Grant of Patent License. Subject to the terms and conditions of this License, 90 | each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, 91 | no-charge, royalty-free, irrevocable (except as stated in this section) patent 92 | license to make, have made, use, offer to sell, sell, import, and otherwise 93 | transfer the Work, where such license applies only to those patent claims 94 | licensable by such Contributor that are necessarily infringed by their Contribution(s) 95 | alone or by combination of their Contribution(s) with the Work to which such 96 | Contribution(s) was submitted. If You institute patent litigation against 97 | any entity (including a cross-claim or counterclaim in a lawsuit) alleging 98 | that the Work or a Contribution incorporated within the Work constitutes direct 99 | or contributory patent infringement, then any patent licenses granted to You 100 | under this License for that Work shall terminate as of the date such litigation 101 | is filed. 102 | 103 | 4. Redistribution. You may reproduce and distribute copies of the Work or 104 | Derivative Works thereof in any medium, with or without modifications, and 105 | in Source or Object form, provided that You meet the following conditions: 106 | 107 | (a) You must give any other recipients of the Work or Derivative Works a copy 108 | of this License; and 109 | 110 | (b) You must cause any modified files to carry prominent notices stating that 111 | You changed the files; and 112 | 113 | (c) You must retain, in the Source form of any Derivative Works that You distribute, 114 | all copyright, patent, trademark, and attribution notices from the Source 115 | form of the Work, excluding those notices that do not pertain to any part 116 | of the Derivative Works; and 117 | 118 | (d) If the Work includes a "NOTICE" text file as part of its distribution, 119 | then any Derivative Works that You distribute must include a readable copy 120 | of the attribution notices contained within such NOTICE file, excluding those 121 | notices that do not pertain to any part of the Derivative Works, in at least 122 | one of the following places: within a NOTICE text file distributed as part 123 | of the Derivative Works; within the Source form or documentation, if provided 124 | along with the Derivative Works; or, within a display generated by the Derivative 125 | Works, if and wherever such third-party notices normally appear. The contents 126 | of the NOTICE file are for informational purposes only and do not modify the 127 | License. You may add Your own attribution notices within Derivative Works 128 | that You distribute, alongside or as an addendum to the NOTICE text from the 129 | Work, provided that such additional attribution notices cannot be construed 130 | as modifying the License. 131 | 132 | You may add Your own copyright statement to Your modifications and may provide 133 | additional or different license terms and conditions for use, reproduction, 134 | or distribution of Your modifications, or for any such Derivative Works as 135 | a whole, provided Your use, reproduction, and distribution of the Work otherwise 136 | complies with the conditions stated in this License. 137 | 138 | 5. Submission of Contributions. Unless You explicitly state otherwise, any 139 | Contribution intentionally submitted for inclusion in the Work by You to the 140 | Licensor shall be under the terms and conditions of this License, without 141 | any additional terms or conditions. Notwithstanding the above, nothing herein 142 | shall supersede or modify the terms of any separate license agreement you 143 | may have executed with Licensor regarding such Contributions. 144 | 145 | 6. Trademarks. This License does not grant permission to use the trade names, 146 | trademarks, service marks, or product names of the Licensor, except as required 147 | for reasonable and customary use in describing the origin of the Work and 148 | reproducing the content of the NOTICE file. 149 | 150 | 7. Disclaimer of Warranty. Unless required by applicable law or agreed to 151 | in writing, Licensor provides the Work (and each Contributor provides its 152 | Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 153 | KIND, either express or implied, including, without limitation, any warranties 154 | or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR 155 | A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness 156 | of using or redistributing the Work and assume any risks associated with Your 157 | exercise of permissions under this License. 158 | 159 | 8. Limitation of Liability. In no event and under no legal theory, whether 160 | in tort (including negligence), contract, or otherwise, unless required by 161 | applicable law (such as deliberate and grossly negligent acts) or agreed to 162 | in writing, shall any Contributor be liable to You for damages, including 163 | any direct, indirect, special, incidental, or consequential damages of any 164 | character arising as a result of this License or out of the use or inability 165 | to use the Work (including but not limited to damages for loss of goodwill, 166 | work stoppage, computer failure or malfunction, or any and all other commercial 167 | damages or losses), even if such Contributor has been advised of the possibility 168 | of such damages. 169 | 170 | 9. Accepting Warranty or Additional Liability. While redistributing the Work 171 | or Derivative Works thereof, You may choose to offer, and charge a fee for, 172 | acceptance of support, warranty, indemnity, or other liability obligations 173 | and/or rights consistent with this License. However, in accepting such obligations, 174 | You may act only on Your own behalf and on Your sole responsibility, not on 175 | behalf of any other Contributor, and only if You agree to indemnify, defend, 176 | and hold each Contributor harmless for any liability incurred by, or claims 177 | asserted against, such Contributor by reason of your accepting any such warranty 178 | or additional liability. END OF TERMS AND CONDITIONS 179 | 180 | APPENDIX: How to apply the Apache License to your work. 181 | 182 | To apply the Apache License to your work, attach the following boilerplate 183 | notice, with the fields enclosed by brackets "[]" replaced with your own identifying 184 | information. (Don't include the brackets!) The text should be enclosed in 185 | the appropriate comment syntax for the file format. We also recommend that 186 | a file or class name and description of purpose be included on the same "printed 187 | page" as the copyright notice for easier identification within third-party 188 | archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | 194 | you may not use this file except in compliance with the License. 195 | 196 | You may obtain a copy of the License at 197 | 198 | http://www.apache.org/licenses/LICENSE-2.0 199 | 200 | Unless required by applicable law or agreed to in writing, software 201 | 202 | distributed under the License is distributed on an "AS IS" BASIS, 203 | 204 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 205 | 206 | See the License for the specific language governing permissions and 207 | 208 | limitations under the License. 209 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CX Server Dockerfiles 2 | 3 | [![REUSE status](https://api.reuse.software/badge/github.com/SAP/devops-docker-cx-server)](https://api.reuse.software/info/github.com/SAP/devops-docker-cx-server) 4 | 5 | ## Deprecation Note 6 | devops-docker-cx-server is deprecated and won't be developed or maintained further. The repository will be sunsetted soon. 7 | If somebody outside in the community would like to take over this repository, feel free to contact us. 8 | 9 | A manual, how to setup your Jenkins manually, can be found here: [Jenkins Setup](https://www.project-piper.io/infrastructure/customjenkins/) 10 | 11 | ## Description 12 | 13 | The CX Server is a collection of [_Dockerfiles_](https://docs.docker.com/engine/reference/builder/) for images that can be used in _Continuous Delivery_ (CD) pipelines for SAP development projects. 14 | The images are optimized for use with project ["Piper"](https://github.com/SAP/jenkins-library) on [Jenkins](https://jenkins.io/). 15 | Docker containers simplify your CD tool setup, encapsulating tools and environments that are required to execute pipeline steps. 16 | 17 | If you want to learn how to use project "Piper" please have a look at [the documentation](https://github.com/SAP/jenkins-library/blob/master/README.md). 18 | Introductory material and a lot of SAP scenarios not covered by project "Piper" are described in our [Continuous Integration Best Practices](https://developers.sap.com/tutorials/ci-best-practices-intro.html). 19 | 20 | This repository contains Dockerfiles that are designed to run project "Piper" pipelines. 21 | Nevertheless, they can also be used flexibly in any custom environment and automation process. 22 | 23 | For detailed usage information please check the README.md in the corresponding folder. 24 | 25 | ### Docker Images 26 | 27 | The following images are published on [hub.docker.com](https://hub.docker.com/search?q=ppiper&type=image): 28 | 29 | | Name | Description | Docker Image | 30 | |------|-------------|------| 31 | | Jenkins | Preconfigured Jenkins to run project "Piper" pipelines. | [ppiper/jenkins-master](https://hub.docker.com/r/ppiper/jenkins-master) | 32 | | Jenkinsfile Runner| [Jenkinsfile Runner](https://github.com/jenkinsci/jenkinsfile-runner) based on `ppiper/jenkins-master`, allows running a `Jenkinsfile` without a long-running, stateful Jenkins master. | [ppiper/jenkinsfile-runner](https://hub.docker.com/r/ppiper/jenkinsfile-runner) | 33 | | Jenkinsfile Runner GitHub Action | [GitHub Action](https://github.com/features/actions) for using the [Jenkinsfile runner](jenkinsfile-runner) | [jenkinsfile-runner-github-action](jenkinsfile-runner-github-action) | 34 | | Life Cycle Container| Sidecar image for life-cycle management of the cx-server|[ppiper/cx-server-companion](https://hub.docker.com/r/ppiper/cx-server-companion)| 35 | 36 | #### Versioning 37 | 38 | All images have a Docker tag `latest`. 39 | Individual images may provide additional tags corresponding to releases. 40 | 41 | Existing releases are listed on the [GitHub releases page](https://github.com/SAP/devops-docker-cx-server/releases). 42 | Official releases follow the pattern `v{VersionNumber}`. 43 | 44 | Developer documentation for releases is available in the [release documentation document](docs/development/how-to-release.md). 45 | 46 | Information on updating the Jenkins master including the bundled plugins is available in [the respective section of the operations guide](https://github.com/SAP/devops-docker-cx-server/blob/master/docs/operations/cx-server-operations-guide.md#update-image). 47 | 48 | ## General Requirements 49 | 50 | A [Docker](https://www.docker.com/) environment is needed to build and run Docker images. 51 | You should be familiar with basic Docker commands to build and run these images. 52 | In case you need to fetch the Dockerfiles and this project's sources to build them locally, a [Git client](https://git-scm.com/) is required. 53 | 54 | Each individual Dockerfile may have additional requirements. Those requirements are documented with each Dockerfile. 55 | 56 | ## Download and Installation 57 | 58 | To download and install Docker please follow the instructions at the [Docker website](https://www.docker.com/get-started) according your operating system. 59 | 60 | You can consume these images in three different flavors: 61 | 62 | 1. Build locally and run 63 | 64 | Clone this repository, change directories to the desired Dockerfile and build it: 65 | 66 | ```` 67 | git clone https://github.com/SAP/devops-docker-cx-server 68 | cd devops-docker-cx-server/ 69 | docker build . 70 | docker run ... 71 | ```` 72 | 73 | Specific instructions how to run the containers are stored within the same directory. 74 | 75 | 2. Pull from hub.docker.com 76 | 77 | We build the Dockerfiles for your convenience and store them on https://hub.docker.com/. 78 | 79 | ```` 80 | docker pull : 81 | docker run ... 82 | ```` 83 | 84 | 3. Via project "Piper" 85 | 86 | In case you are using [project "Piper"](https://sap.github.io/jenkins-library/) you can configure certain steps 87 | to use docker images instead of the local Jenkins environment. These steps will automatically pull and run these 88 | images. 89 | 90 | ### Setting up Jenkins Server 91 | The `cx-server` is a toolkit that is developed to manage the lifecycle of the Jenkins server. 92 | In order to use the toolkit, you need a file named `cx-server` and a configuration file `server.cfg`. 93 | You can generate these files using the docker command 94 | 95 | ```sh 96 | docker run -it --rm -u $(id -u):$(id -g) -v "${PWD}":/cx-server/mount/ ppiper/cx-server-companion:latest init-cx-server 97 | ``` 98 | 99 | Once the files are generated in the current directory, you can launch the below command to start the Jenkins server. 100 | 101 | ```sh 102 | ./cx-server start 103 | ``` 104 | 105 | If you would like to customize the Jenkins, [the operations guide](https://github.com/SAP/devops-docker-cx-server/blob/master/docs/operations/cx-server-operations-guide.md) will provide more information on this along with the lifecycle management of the Jenkins. 106 | 107 | ## How to obtain support 108 | 109 | Feel free to open new issues for feature requests, bugs or general feedback on 110 | the [GitHub issues page of this project][devops-docker-cx-server-issues]. 111 | 112 | ## Contributing 113 | 114 | Read and understand our [contribution guidelines][contribution] 115 | before opening a pull request. 116 | 117 | [devops-docker-cx-server-issues]: https://github.com/SAP/devops-docker-cx-server/issues 118 | [contribution]: https://github.com/SAP/devops-docker-cx-server/blob/master/CONTRIBUTING.md 119 | 120 | ## Licensing 121 | 122 | Copyright 2017-2021 SAP SE or an SAP affiliate company and devops-docker-cx-server contributors. Please see our [LICENSE](LICENSE) for copyright and license information. Detailed information including third-party components and their licensing/copyright information is available [via the REUSE tool](https://api.reuse.software/info/github.com/SAP/devops-docker-cx-server). 123 | -------------------------------------------------------------------------------- /ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o errexit 3 | set -o nounset 4 | 5 | if [[ "$#" -ne 1 ]]; then 6 | echo "Usage: $(basename "$0") (latest || v*)" 7 | exit 1 8 | fi 9 | 10 | VERSION=$1 11 | 12 | if [[ ${VERSION} != latest ]] && [[ ${VERSION} != "v"* ]]; then 13 | echo "Error: Version must be 'latest' or 'v*'" 14 | exit 1 15 | fi 16 | 17 | build_image() { 18 | local TAG=$1 19 | local DIR=$2 20 | 21 | if [ "${VERSION}" = latest ]; then 22 | # Create a backup of the image to allow rollback in case of failure 23 | docker pull "${TAG}":latest 24 | docker tag "${TAG}":latest "${TAG}":backup-of-latest 25 | fi 26 | 27 | # Build the Release Candidate 28 | docker build "${DIR}" --tag "${TAG}":"${VERSION}"-RC 29 | } 30 | 31 | smoke_test() { 32 | # Test Jenkins master image 33 | docker run --name cx-jenkins-master-test --detach --publish 8080:8080 \ 34 | -v /var/run/docker.sock:/var/run/docker.sock \ 35 | ppiper/jenkins-master:"${VERSION}"-RC 36 | # Wait up to 5 minutes for Jenkins to be up and fail if it does not return 200 after this time 37 | docker exec cx-jenkins-master-test timeout 300 bash -c "until curl --fail http://localhost:8080/api/json; do sleep 5; done" 38 | # Check for 'SEVERE' messages in Jenkins log (usually caused by a plugin or configuration issues) 39 | # Note: This will exit with code 1 if a finding of SEVERE exists 40 | docker logs cx-jenkins-master-test 2> >(grep --quiet SEVERE) 41 | } 42 | 43 | push_image() { 44 | local TAG=$1 45 | # Replace slash with dash for GitHub because "sap" is already the org and "ppiper" is a part of the name 46 | local GH_TAG=$(echo $TAG | sed -e 's/\//-/g') 47 | 48 | docker tag "${TAG}":"${VERSION}"-RC "${TAG}":"${VERSION}" 49 | docker tag "${TAG}":"${VERSION}"-RC "ghcr.io/sap/${GH_TAG}":"${VERSION}" 50 | 51 | if [ "${VERSION}" = latest ]; then 52 | docker push "${TAG}":backup-of-latest 53 | fi 54 | 55 | docker push "${TAG}":"${VERSION}" 56 | docker push "ghcr.io/sap/${GH_TAG}":"${VERSION}" 57 | } 58 | 59 | echo '::group::Pull Base Images' 60 | docker pull jenkins/jenkins:lts-slim 61 | docker pull node:11-alpine 62 | docker pull openjdk:8-jre-slim 63 | docker pull jenkins/jnlp-slave:3.27-1-alpine 64 | docker pull debian:buster-slim 65 | echo '::endgroup' 66 | 67 | echo '::group::Build Jenkins Master Image' 68 | build_image ppiper/jenkins-master jenkins-master 69 | echo '::endgroup' 70 | 71 | echo '::group::Build other Images' 72 | build_image ppiper/jenkins-agent jenkins-agent 73 | build_image ppiper/jenkins-agent-k8s jenkins-agent-k8s 74 | build_image ppiper/cx-server-companion cx-server-companion 75 | build_image ppiper/action-runtime project-piper-action-runtime 76 | echo '::endgroup' 77 | 78 | echo '::group::Smoketest' 79 | smoke_test 80 | echo '::endgroup' 81 | 82 | if [[ ${GITHUB_REF##*/} != master ]] && [[ ${GITHUB_REF##*/} != "v"* ]]; then 83 | echo "Not pushing on ref ${GITHUB_REF}" 84 | exit 0 85 | fi 86 | 87 | echo '::group::Push Images' 88 | push_image ppiper/jenkins-master 89 | push_image ppiper/jenkins-agent 90 | push_image ppiper/jenkins-agent-k8s 91 | push_image ppiper/cx-server-companion 92 | push_image ppiper/action-runtime 93 | echo '::endgroup' 94 | -------------------------------------------------------------------------------- /contrib/perform-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | # Manually trigger a release of project "Piper". 4 | # Usually we do release on a schedule, but sometimes you might need to trigger a release. 5 | # Invoke this script with PIPER_RELEASE_TOKEN set to your personal access token for GitHub with 'repo' scope. 6 | # This script is based on https://goobar.io/2019/12/07/manually-trigger-a-github-actions-workflow/ 7 | 8 | if [ -z "$PIPER_RELEASE_TOKEN" ] 9 | then 10 | echo "Required variable PIPER_RELEASE_TOKEN is not set, please set a personal access token for GitHub with 'repo' scope." 11 | exit 1 12 | fi 13 | 14 | curl -H "Accept: application/vnd.github.everest-preview+json" \ 15 | -H "Authorization: token ${PIPER_RELEASE_TOKEN}" \ 16 | --request POST \ 17 | --data '{"event_type": "perform-release"}' \ 18 | https://api.github.com/repos/SAP/devops-docker-cx-server/dispatches 19 | -------------------------------------------------------------------------------- /cx-server-companion/.dockerignore: -------------------------------------------------------------------------------- 1 | life-cycle-scripts/backup 2 | -------------------------------------------------------------------------------- /cx-server-companion/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | life-cycle-scripts/backup 3 | life-cycle-scripts/jenkins-configuration 4 | life-cycle-scripts/custom-environment.list -------------------------------------------------------------------------------- /cx-server-companion/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:11-alpine 2 | 3 | ARG npm_registry=https://registry.npmjs.org/ 4 | 5 | #Install dependencies for running the cx server script in this container 6 | RUN apk add --no-cache bash docker curl 7 | 8 | WORKDIR /cx-server 9 | 10 | COPY files/* ./ 11 | COPY life-cycle-scripts/* ./life-cycle-scripts/ 12 | 13 | RUN npm config set registry=$npm_registry && \ 14 | npm install && \ 15 | npm config delete registry && \ 16 | # If the repository was cloned on Windows, the script might have \CR\LF line endings. Ensure it has only \LF. 17 | dos2unix cx-server-companion.sh && \ 18 | dos2unix life-cycle-scripts/cx-server && \ 19 | dos2unix server-default.cfg && \ 20 | dos2unix init-cx-server && \ 21 | unix2dos life-cycle-scripts/cx-server.bat && \ 22 | chmod +x life-cycle-scripts/cx-server && \ 23 | chmod +x cx-server-companion.sh && \ 24 | chmod +x init-cx-server 25 | ENV PATH="/cx-server:${PATH}" 26 | -------------------------------------------------------------------------------- /cx-server-companion/README.md: -------------------------------------------------------------------------------- 1 | # cx-server-companion 2 | 3 | Dockerfile for an image with the utility scripts that helps with the lifecycle management of the [cx-server](https://github.com/SAP/devops-docker-images/blob/master/README.md#setting-up-jenkins-server). 4 | This image is to be used as described in the [operations guide](https://github.com/SAP/devops-docker-cx-server/blob/master/docs/operations/cx-server-operations-guide.md). 5 | 6 | ## Download 7 | 8 | This image is published to Docker Hub and can be pulled via the command 9 | 10 | ```sh 11 | docker pull ppiper/cx-server-companion 12 | ``` 13 | 14 | ## Build 15 | 16 | To build this image locally, open a terminal in the directory of the `Dockerfile` and run 17 | 18 | ```sh 19 | docker build -t ppiper/cx-server-companion . 20 | ``` 21 | 22 | ## Usage 23 | 24 | This image is to be used by the cx-server script as a [sidecar](https://docs.microsoft.com/en-us/azure/architecture/patterns/sidecar) container. In order to use this image to its complete potential, use the `cx-server` script. You can generate it using the below command. 25 | 26 | ```sh 27 | docker run -it --rm -u $(id -u):$(id -g) -v "${PWD}":/cx-server/mount/ ppiper/cx-server-companion:latest init-cx-server 28 | ``` 29 | 30 | ## License 31 | 32 | Copyright (c) 2018 SAP SE or an SAP affiliate company. All rights reserved. 33 | This file is licensed under the Apache Software License, v. 2 except as noted 34 | otherwise in the [LICENSE file](https://github.com/SAP/devops-docker-cx-server/blob/master/LICENSE). 35 | 36 | Please note that Docker images can contain other software which may be licensed under different licenses. This License file is also included in the Docker image. For any usage of built Docker images please make sure to check the licenses of the artifacts contained in the images. 37 | -------------------------------------------------------------------------------- /cx-server-companion/files/checkversion.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | /*** 5 | * This script consumes a docker image name in the form of ppiper/jenkins-master:v2 (where the tag is optional) 6 | * and checks whether a higher version is available on Docker Hub. If yes, it returns with exit code 3 and prints the 7 | * currently highest version to STDOUT. If the supplied value is the newest version, 0 is returned. 8 | */ 9 | 10 | const request = require('request'); 11 | 12 | const RETURN_CODES = { 13 | "GENERAL_ERROR": 1, 14 | "INVALID_IMAGE_NAME": 2, 15 | "NEWER_VERSION_AVAILABLE": 3 16 | }; 17 | 18 | if (!process.argv[2]) { 19 | exit("Missing parameter: Docker image name not specified"); 20 | } 21 | 22 | const dockerImageInfo = parseDockerImageInfo(process.argv[2]); 23 | 24 | const tagListUrl = `https://registry.hub.docker.com/v2/repositories/${dockerImageInfo.name}/tags`; 25 | 26 | request(tagListUrl, function (error, response, body) { 27 | if (!error && response && (response.statusCode !== 200)) { 28 | error = "HTTP status code " + response.statusCode; 29 | if (body) { 30 | error += '\n' + body; 31 | } 32 | } 33 | if (error) { 34 | exit(`Error while retrieving list of tags from '${tagListUrl}'. Original error message: ${error}`); 35 | } 36 | 37 | const aTagsWithVersionNumbers = extractVersionNumbers(JSON.parse(body).results); 38 | 39 | aTagsWithVersionNumbers.sort(sortNumberDescending); 40 | 41 | if (aTagsWithVersionNumbers.length == 0) { 42 | exit("List of tags is empty"); 43 | } 44 | 45 | const currentVersionNumber = getVersionNumberFromTagName(dockerImageInfo.tag); 46 | const newestAvailableTag = aTagsWithVersionNumbers[0]; 47 | if (currentVersionNumber && (currentVersionNumber < newestAvailableTag.cdToolkitVersionNumber)) { 48 | console.log(dockerImageInfo.name + ":" + newestAvailableTag.name); 49 | process.exit(RETURN_CODES.NEWER_VERSION_AVAILABLE) 50 | } 51 | else { 52 | console.log(`Docker image '${dockerImageInfo.name}:${dockerImageInfo.tag}' is up to date`); 53 | process.exit(0); 54 | } 55 | }); 56 | 57 | function sortNumberDescending(a, b) { 58 | return b.cdToolkitVersionNumber - a.cdToolkitVersionNumber 59 | } 60 | 61 | /*** 62 | * Extracts the tag and image name from the supplied docker image name. If the image name contains a URL, it fails. 63 | * @param strDockerImage 64 | * @returns Name and tag of docker image 65 | */ 66 | function parseDockerImageInfo(strDockerImage) { 67 | // image string might contain docker registry url 68 | const urlMatch = strDockerImage.match(/\//g); 69 | if (!urlMatch || (urlMatch.length > 1)) { 70 | exit(`Invalid image name: '${strDockerImage}'. Expected format: 'ppiper/jenkins-master:tag'`, RETURN_CODES.INVALID_IMAGE_NAME); 71 | } 72 | 73 | const match = strDockerImage.match(/(.*\/.*):(.*)/); 74 | if (!match) { 75 | return { 76 | "name": strDockerImage, 77 | "tag": "latest" 78 | } 79 | } 80 | else { 81 | return { 82 | "name": match[1], 83 | "tag": match[2] 84 | } 85 | } 86 | } 87 | 88 | /*** 89 | * Filters out tags whose names do not follow the schema 'v' followed by a natural number, e.g., 'v123'. 90 | * Furthermore, extracts the version number and stores it as the property 'cdToolkitVersionNumber'. 91 | * @param aTags The list of tags from Docker Hub 92 | */ 93 | function extractVersionNumbers(aTags) { 94 | const aFilteredResult = []; 95 | aTags.forEach(function (tag) { 96 | const versionNumber = getVersionNumberFromTagName(tag.name); 97 | if (versionNumber) { 98 | tag.cdToolkitVersionNumber = versionNumber; 99 | aFilteredResult.push(tag) 100 | } 101 | }); 102 | 103 | return aFilteredResult; 104 | } 105 | 106 | /*** 107 | * Extracts version number from tag or returns null if no version number is present (e.g. if tag is 'latest') 108 | * @param strTag The tag name, for example "v10" 109 | * @returns version number, or null if not present 110 | */ 111 | function getVersionNumberFromTagName(strTag) { 112 | const match = strTag.match(/^v(\d+)$/); 113 | if (match) { 114 | const versionNumber = Number.parseInt(match[1]); 115 | if (!(versionNumber.toString().length == strTag.length - 1)) { 116 | exit(`Sanity check failed. Expected ${versionNumber} to be one character shorter than ${strTag}.`) 117 | } 118 | return versionNumber; 119 | } 120 | else { 121 | return null; 122 | } 123 | } 124 | 125 | function exit(strErrorMessage, exitCode = RETURN_CODES.GENERAL_ERROR) { 126 | console.log(strErrorMessage); 127 | process.exit(exitCode); 128 | } 129 | -------------------------------------------------------------------------------- /cx-server-companion/files/cx-server-companion.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | readonly jenkins_container_name='cx-jenkins-master' 4 | readonly nexus_container_name='cx-nexus' 5 | readonly cache_docker_image='sonatype/nexus3:3.21.1' 6 | readonly cxserver_companion_docker_image='ppiper/cx-server-companion' 7 | readonly container_port_http=8080 8 | readonly container_port_https=8443 9 | readonly network_name='cx-network' 10 | # Taken from https://gist.github.com/nmarley/0ee3e2cf14dabfb868eb818b8b30e7e0#gistcomment-2318045 11 | readonly cxserver_companion_container_id="$(awk -F/ '{ print $NF }' /proc/1/cpuset)" 12 | 13 | readonly cxserver_mount=/cx-server/mount 14 | readonly backup_folder="${cxserver_mount}/backup" 15 | readonly tls_folder="${cxserver_mount}/tls" 16 | 17 | readonly alpine_docker_image='alpine:3.9' 18 | 19 | readonly tmp_dir='tmp' 20 | 21 | readonly bold_start="\033[1m" 22 | readonly bold_end="\033[0m" 23 | 24 | function die() 25 | { 26 | local msg=$1; shift 27 | local rc=${1:-1}; shift 28 | [ -n "${msg}" ] && log_error "${msg}" 29 | [ "${rc}" == "0" ] && log_warn "function \"die\" called with exit code \"${rc}\". This indicates a normal termination." 30 | exit ${rc} 31 | } 32 | 33 | function log_error() 34 | { 35 | echo -e "\033[1;31m[Error]\033[0m $1" >&2 36 | } 37 | 38 | function log_info() 39 | { 40 | echo -e "\033[1;33m[Info]\033[0m $1" >&2 41 | } 42 | 43 | function log_warn() 44 | { 45 | echo -e "\033[1;33m[Warning]\033[0m $1" >&2 46 | } 47 | 48 | function assert_config_set() 49 | { 50 | if [ -z "${!1}" ]; then 51 | log_error "Config parameter '$1' not set."; exit 1; 52 | fi 53 | } 54 | 55 | function get_container_id() 56 | { 57 | local container_id=$(docker ps --filter "name=${1}" -q) 58 | echo "${container_id}" 59 | } 60 | 61 | function is_container_status() 62 | { 63 | local container_id=$(docker ps --filter "name=${1}" --filter "status=${2}" -q) 64 | if [ -z "${container_id}" ]; then 65 | false 66 | else 67 | true 68 | fi 69 | } 70 | 71 | #checks whether the supplied container name is running 72 | function is_running() 73 | { 74 | local container_id="$(get_container_id "${1}")" 75 | if [ ! -z "$container_id" ]; then 76 | log_info "The ${1} container is already running with container id '${container_id}'." 77 | true 78 | else 79 | false 80 | fi 81 | } 82 | 83 | function wait_for_successful_start() 84 | { 85 | local attempts=$1 86 | shift 87 | local containerName=$1 88 | shift 89 | local docker_cmd=() 90 | docker_cmd=(docker exec "${containerName}" "$@") 91 | 92 | local delay=1 93 | local statusCodeSuccess=0 94 | local i 95 | 96 | for ((i=0; i < attempts; i++)); do 97 | 98 | is_container_status "${containerName}" "running" 99 | local isContainerRunning=$? 100 | 101 | if [ $isContainerRunning -ne "${statusCodeSuccess}" ]; then 102 | echo "" 103 | log_error "Container ${containerName} failed to start. Please check your server.cfg." 104 | exit 1 105 | fi 106 | 107 | echo -n "." 108 | #set +x 109 | "${docker_cmd[@]}" 1> /dev/null 110 | if [ $? -eq "${statusCodeSuccess}" ]; then 111 | echo " success." 112 | return 0 113 | fi 114 | 115 | sleep "${delay}" 116 | 117 | done 118 | 119 | log_error "Command '{$docker_cmd}' failed ${attempts} times." 120 | false 121 | } 122 | 123 | function retry() 124 | { 125 | local attempts=$1 126 | shift 127 | local delay=$1 128 | shift 129 | local expected_status_code=$1 130 | shift 131 | local i 132 | 133 | for ((i=0; i < attempts; i++)); do 134 | echo -n "." 135 | eval "${*} 1> /dev/null" 136 | if [ $? -eq "${expected_status_code}" ]; then 137 | echo " success." 138 | return 0 139 | fi 140 | sleep "${delay}" 141 | done 142 | 143 | log_error "Command \"$*\" failed ${attempts} times." 144 | false 145 | } 146 | 147 | function trace_execution() 148 | { 149 | echo -e -n "\033[1m>>\033[0m " >&2 150 | echo $* >&2 151 | } 152 | 153 | function run() 154 | { 155 | trace_execution "$@" 156 | "$@" 157 | return $? 158 | } 159 | 160 | # join , "${arr[@]}" => joins arr by commas to get comma separated string 161 | function join() 162 | { 163 | local IFS="$1" 164 | shift 165 | echo "$*" 166 | } 167 | 168 | function get_proxy_parameters() 169 | { 170 | local image_name="${1}" 171 | 172 | local proxy_params=() 173 | 174 | local behind_proxy=false 175 | 176 | local image_http_proxy=$(get_image_environment_variable "${image_name}" http_proxy) 177 | local image_https_proxy=$(get_image_environment_variable "${image_name}" https_proxy) 178 | local image_no_proxy=$(get_image_environment_variable "${image_name}" no_proxy) 179 | 180 | if [ ! -z "${http_proxy}" ]; then 181 | proxy_params+=(-e HTTP_PROXY="${http_proxy}" -e http_proxy="${http_proxy}") 182 | behind_proxy=true 183 | elif [ ! -z "${image_http_proxy}" ]; then 184 | behind_proxy=true 185 | fi 186 | 187 | 188 | if [ ! -z "${https_proxy}" ]; then 189 | proxy_params+=(-e HTTPS_PROXY="${https_proxy}" -e https_proxy="${https_proxy}") 190 | behind_proxy=true 191 | elif [ ! -z "${image_https_proxy}" ]; then 192 | behind_proxy=true 193 | fi 194 | 195 | local no_proxy_values=() 196 | if [ ! -z "${image_no_proxy}" ]; then 197 | # image default value 198 | no_proxy_values=("${image_no_proxy}") 199 | fi 200 | if [ ! -z "${no_proxy}" ]; then 201 | # overwrites potential image value 202 | no_proxy_values=("${no_proxy}") 203 | fi 204 | 205 | if [ ! -z "${x_no_proxy}" ]; then 206 | # append to whatever is there 207 | no_proxy_values+=("${x_no_proxy}") 208 | fi 209 | 210 | if [ "${behind_proxy}" = true ]; then 211 | no_proxy_values+=("${nexus_container_name}") 212 | fi 213 | 214 | if [ ${#no_proxy_values[@]} != 0 ]; then 215 | local no_proxy_values_string="$(join , "${no_proxy_values[@]}")" 216 | proxy_params+=(-e NO_PROXY="${no_proxy_values_string}" -e no_proxy="${no_proxy_values_string}") 217 | fi 218 | 219 | local dbg_info=() 220 | dbg_info+=("Proxy base settings of image '${image_name}':") 221 | if [ ! -z "${image_http_proxy}" ]; then dbg_info+=(" http_proxy='${image_http_proxy}'"); fi; 222 | if [ ! -z "${image_https_proxy}" ]; then dbg_info+=(" https_proxy='${image_https_proxy}'"); fi; 223 | if [ ! -z "${image_no_proxy}" ]; then dbg_info+=(" no_proxy='${image_no_proxy}'"); fi; 224 | 225 | dbg_info+=("Proxy settings of environment / server.cfg:") 226 | if [ ! -z "${http_proxy}" ]; then dbg_info+=(" http_proxy='${http_proxy}'"); fi; 227 | if [ ! -z "${https_proxy}" ]; then dbg_info+=(" https_proxy='${https_proxy}'"); fi; 228 | if [ ! -z "${no_proxy}" ]; then dbg_info+=(" no_proxy='${no_proxy}'"); fi; 229 | if [ ! -z "${x_no_proxy}" ]; then dbg_info+=(" x_no_proxy='${x_no_proxy}'"); fi; 230 | 231 | dbg_info+=("Actual Command line params:") 232 | dbg_info+=("$(join $'\n'' ' "${proxy_params[@]}")") 233 | dbg_info+=(" ") 234 | 235 | local dbg_info_str="$(join $'\n' "${dbg_info[@]}")" 236 | # remove comment to dump debug information 237 | #echo "${dbg_info_str}" > "proxy.dump" 238 | 239 | local IFS=$'\n' 240 | #echo "${proxy_params[*]}" > proxy_params 241 | echo "${proxy_params[*]}" 242 | } 243 | 244 | function wait_for_started() 245 | { 246 | echo -n "Waiting for the Cx server to start" 247 | wait_for_successful_start 180 "${jenkins_container_name}" curl --noproxy localhost --silent "http://localhost:${container_port_http}/api/json" 248 | } 249 | 250 | function stop_jenkins() 251 | { 252 | container_id="$(get_container_id "${jenkins_container_name}")" 253 | if [ -z "${container_id}" ]; then 254 | log_info "The Cx server is not running." 255 | else 256 | echo 'Stopping Jenkins' 257 | stop_jenkins_container 258 | fi 259 | } 260 | 261 | function stop_jenkins_container() 262 | { 263 | user_and_pass=() 264 | 265 | if [[ -n "${JENKINS_USERNAME}" && -n "${JENKINS_PASSWORD}" ]]; then 266 | user_and_pass=(-u "${JENKINS_USERNAME}:${JENKINS_PASSWORD}") 267 | else 268 | echo -n "Jenkins username (leave empty for unprotected server): " 269 | read -r user_name 270 | 271 | if [ ! -z "${user_name}" ]; then 272 | echo -n "Password for ${user_name}: " 273 | read -r -s password 274 | user_and_pass=(-u "${user_name}:${password}") 275 | fi 276 | fi 277 | 278 | if [ ! -e "${tmp_dir}" ]; then 279 | mkdir -p "${tmp_dir}" ||die "Cannot create tmp dir \"${tmp_dir}\"." 1 280 | fi 281 | 282 | cookies_file="${tmp_dir}/cookies-$(date +%s).jar" 283 | 284 | echo "" 285 | echo "Initiating safe shutdown..." 286 | 287 | crumb_header_snippet=`get_crumb_header_snippet "${jenkins_container_name}" "${container_port_http}" "${cookies_file}" "${user_and_pass[@]}"` 288 | [ "$?" != 0 ] && die "Cannot retrieve XSRF crumb. Check log for details" 1 289 | 290 | cookie_file_snippet="" 291 | if [ ! -z "${crumb_header_snippet}" ]; then 292 | cookie_file_snippet="--cookie ${cookies_file}" 293 | fi 294 | 295 | exitCmd=(docker exec ${jenkins_container_name} curl --noproxy localhost --write-out '%{http_code}' --output /dev/null --silent "${user_and_pass[@]}" ${cookie_file_snippet} ${crumb_header_snippet} -X POST "http://localhost:${container_port_http}/safeExit") 296 | 297 | if [ ! -z "${password}" ]; then 298 | trace_execution "${exitCmd[@]//${password}/******}" 299 | else 300 | trace_execution "${exitCmd[@]}" 301 | fi 302 | 303 | docker update --restart=no "${jenkins_container_name}" 304 | if [ $? != 0 ]; 305 | then 306 | log_warn "Setting restart behaviour to 'no' for jenkins container failed. Jenkins needs to be stopped forcefully (./cx-server stop --force})" 307 | fi 308 | 309 | echo -n "Waiting for Cx server to accept safeExit signal..." 310 | local attempts=180 311 | local i 312 | for ((i=0; i < attempts; i++)); do 313 | echo -n "." 314 | local result="$("${exitCmd[@]}")" 315 | if [ "${result}" = "503" ]; then 316 | sleep 1 317 | elif [ "${result}" = "200" ]; then 318 | echo " success." 319 | break 320 | elif [ "${result}" = "401" ]; then 321 | echo "" 322 | log_error "Wrong credentials." 323 | exit 1 324 | else 325 | echo "" 326 | log_error "Failed with code ${result}." 327 | exit 1 328 | fi 329 | done 330 | 331 | rm -rf "${cookies_file}" 332 | 333 | echo -n "Waiting for running jobs to finish..." 334 | retry 360 5 0 "is_container_status ${jenkins_container_name} 'exited'" 335 | } 336 | 337 | function stop_jenkins_unsafe() 338 | { 339 | echo 'Force-stopping Cx server' 340 | docker stop ${jenkins_container_name} 341 | } 342 | 343 | function stop_nexus() 344 | { 345 | nexus_container_id="$(get_container_id "${nexus_container_name}")" 346 | if [ -z "${nexus_container_id}" ]; then 347 | log_info "The cache server is not running." 348 | else 349 | echo 'Stopping nexus' 350 | run docker rm --force "${nexus_container_name}" 351 | remove_networks 352 | fi 353 | } 354 | 355 | function stop_nexus_unsafe() 356 | { 357 | echo 'Force-stopping nexus' 358 | docker rm --force "${nexus_container_name}" 359 | echo 'Remove networks' 360 | remove_networks 361 | } 362 | 363 | function get_image_environment_variable() 364 | { 365 | local cmd='echo $'"${2}" 366 | 367 | local env_value="$(docker run --rm --entrypoint /bin/sh "${1}" -c "${cmd}")" 368 | if [ $? -ne 0 ]; then 369 | log_error "Failed to extract environment variable '${2}' from image '${1}'" 370 | exit 1 371 | fi 372 | echo "${env_value}" 373 | } 374 | 375 | function print_proxy_config() 376 | { 377 | if [ ! -z "${http_proxy}" ]; then 378 | echo " - http_proxy=${http_proxy}" 379 | fi 380 | if [ ! -z "${https_proxy}" ]; then 381 | echo " - https_proxy=${https_proxy}" 382 | fi 383 | if [ ! -z "${no_proxy}" ]; then 384 | echo " - no_proxy=${no_proxy}" 385 | fi 386 | if [ ! -z "${x_no_proxy}" ]; then 387 | echo " - x_no_proxy=${x_no_proxy}" 388 | fi 389 | 390 | } 391 | 392 | function print_jenkins_config() 393 | { 394 | echo " - jenkins_home=${jenkins_home}" 395 | if [ ! -z "${x_java_opts}" ]; then 396 | echo " - x_java_opts=${x_java_opts}" 397 | fi 398 | print_proxy_config 399 | } 400 | 401 | function print_nexus_config() 402 | { 403 | echo "Starting docker container for download cache server." 404 | echo "Parameters:" 405 | if [ ! -z "${mvn_repository_url}" ]; then 406 | echo " - mvn_repository_url=${mvn_repository_url}" 407 | fi 408 | if [ ! -z "${npm_registry_url}" ]; then 409 | echo " - npm_registry_url=${npm_registry_url}" 410 | fi 411 | if [ ! -z "${x_nexus_java_opts}" ]; then 412 | echo " - x_nexus_java_opts=${x_nexus_java_opts}" 413 | fi 414 | print_proxy_config 415 | } 416 | 417 | function start_nexus() 418 | { 419 | if [ "${cache_enabled}" = true ] ; then 420 | if is_running "${nexus_container_name}" ; then 421 | stop_nexus 422 | fi 423 | 424 | if [ "$(docker network ls | grep -c "${network_name}")" -gt 1 ]; then 425 | remove_networks 426 | fi 427 | 428 | if [ "$(docker network ls | grep -c "${network_name}")" -eq 0 ]; then 429 | run docker network create "${network_name}" 430 | fi 431 | 432 | run docker network connect "${network_name}" "${cxserver_companion_container_id}" 433 | start_nexus_container 434 | else 435 | echo "Download cache disabled." 436 | fi 437 | } 438 | 439 | function start_nexus_container() 440 | { 441 | # check if exited and start if yes 442 | if is_container_status "${nexus_container_name}" 'exited'; then 443 | echo "Nexus container was stopped. Restarting stopped instance..." 444 | run docker start "${nexus_container_name}" 445 | if [ $? -ne "0" ]; then 446 | log_error "Failed to start existing nexus container." 447 | exit $?; 448 | fi 449 | run docker update --restart unless-stopped "${nexus_container_name}" 450 | if [ $? -ne "0" ]; then 451 | log_warn "Failed to set restart behaviour for existing nexus container." 452 | fi 453 | else 454 | run docker pull "${cache_docker_image}" 455 | if [ $? -ne "0" ]; then 456 | log_error "Failed to pull ${cache_docker_image}." 457 | exit $?; 458 | fi 459 | 460 | local environment_variable_parameters=() 461 | if [ ! -z "${x_nexus_java_opts}" ]; then 462 | local container_java_opts="$(get_image_environment_variable "${cache_docker_image}" INSTALL4J_ADD_VM_PARAMS)" 463 | environment_variable_parameters+=(-e "INSTALL4J_ADD_VM_PARAMS=${container_java_opts} ${x_nexus_java_opts}") 464 | fi 465 | 466 | # Read proxy parameters separated by new line 467 | local old_IFS=${IFS} 468 | local IFS=$'\n' 469 | environment_variable_parameters+=($(get_proxy_parameters "${cache_docker_image}")) 470 | IFS=${old_IFS} 471 | 472 | environment_variable_parameters+=(-e NEXUS_SECURITY_RANDOMPASSWORD=false) 473 | 474 | print_nexus_config 475 | 476 | local nexus_port_mapping=() 477 | if [ ! -z "${DEVELOPER_MODE}" ]; then 478 | nexus_port_mapping+=(-p 8081:8081) 479 | fi 480 | 481 | run docker run --name "${nexus_container_name}" --restart unless-stopped "${nexus_port_mapping[@]}" --network="${network_name}" -d "${environment_variable_parameters[@]}" "${cache_docker_image}" 482 | if [ $? -ne "0" ]; then 483 | log_error "Failed to start new nexus container." 484 | exit $?; 485 | fi 486 | fi 487 | 488 | wait_for_nexus_started 489 | init_nexus 490 | } 491 | 492 | function wait_for_nexus_started() 493 | { 494 | echo -n "Waiting for the nexus server to start" 495 | wait_for_successful_start 180 "${nexus_container_name}" curl --noproxy localhost --silent -X GET http://localhost:8081/service/rest/v1/script --header 'Authorization: Basic YWRtaW46YWRtaW4xMjM=' --header 'Content-Type: application/json' 496 | } 497 | 498 | function init_nexus() 499 | { 500 | if [ -z "${mvn_repository_url}" ]; then 501 | mvn_repository_url='https://repo.maven.apache.org/maven2/' 502 | fi 503 | if [ -z "${npm_registry_url}" ]; then 504 | npm_registry_url='https://registry.npmjs.org/' 505 | fi 506 | 507 | echo "Initializing Nexus" 508 | 509 | # set no_proxy for single command 510 | no_proxy="${nexus_container_name}" node /cx-server/init-nexus.js "{\"mvn_repository_url\": \"${mvn_repository_url}\", \"npm_registry_url\": \"${npm_registry_url}\", \"http_proxy\": \"${http_proxy}\", \"https_proxy\": \"${https_proxy}\", \"no_proxy\": \"${no_proxy}\"}" 511 | 512 | if [ $? -ne "0" ]; then 513 | log_error "Failed while initializing Nexus." 514 | exit 1; 515 | fi 516 | } 517 | 518 | function start_jenkins() 519 | { 520 | if ! is_running "${jenkins_container_name}" ; then 521 | start_jenkins_container 522 | else 523 | log_info "Cx Server is already running." 524 | fi 525 | 526 | if [ -z ${DEVELOPER_MODE} ]; then 527 | log_warn 'Please ensure your Jenkins instance is appropriatly secured, see: https://jenkins.io/doc/book/system-administration/security/ 528 | A random password was created for admin, if your Jenkins instance was not secured already. 529 | Run "./cx-server initial-credentials" to find the default credentials. 530 | This might take a few minutes to complete. 531 | 532 | We recommend to change the default password immediately.' 533 | fi 534 | 535 | } 536 | 537 | function start_jenkins_container() 538 | { 539 | # check if exited and start if yes 540 | if is_container_status ${jenkins_container_name} 'exited'; then 541 | echo "Cx Server container was stopped. Restarting stopped instance..." 542 | run docker start "${jenkins_container_name}" 543 | if [ $? -ne "0" ]; then 544 | log_error "Failed to start existing cx-server container." 545 | exit $?; 546 | fi 547 | run docker update --restart unless-stopped "${jenkins_container_name}" 548 | if [ $? -ne "0" ]; then 549 | log_warn "Failed to set restart behaviour for existing cx-server container." 550 | fi 551 | else 552 | customDockerfileDir="custom_image" 553 | local port_mapping 554 | if [ -e "${customDockerfileDir}/Dockerfile" ]; then 555 | echo "Custom Dockerfile in '${customDockerfileDir} 'detected..." 556 | echo "Starting **customized** docker container for Cx Server." 557 | echo "Parameters:" 558 | get_port_mapping port_mapping 559 | echo " - jenkins_home=${jenkins_home}" 560 | print_jenkins_config 561 | echo "" 562 | 563 | image_name="ppiper/jenkins-master-customized" 564 | 565 | run docker build --pull -t "${image_name}" "${customDockerfileDir}" 566 | if [ $? -ne "0" ]; then 567 | log_error "Failed to build custom Dockerfile." 568 | exit $?; 569 | fi 570 | else 571 | echo "Starting docker container for Cx Server." 572 | echo "Parameters:" 573 | get_port_mapping port_mapping 574 | echo " - docker_registry=${docker_registry}" 575 | echo " - docker_image=${docker_image}" 576 | print_jenkins_config 577 | echo "" 578 | 579 | assert_config_set "docker_image" 580 | assert_config_set "jenkins_home" 581 | 582 | if [ ! -z "${docker_registry}" ]; then 583 | image_name="${docker_registry}/${docker_image}" 584 | else 585 | image_name="${docker_image}" 586 | fi 587 | 588 | if [ -z ${DEVELOPER_MODE} ]; then 589 | run docker pull "${image_name}" 590 | if [ $? -ne "0" ]; then 591 | log_error "Failed to pull '$image_name'." 592 | exit $?; 593 | fi 594 | fi 595 | fi 596 | 597 | # determine docker gid 598 | docker_gid=$(stat -c '%g' /var/run/docker.sock) 599 | 600 | local user_parameters=() 601 | if [ -z "${docker_gid}" ]; then 602 | log_error "Failed to determine docker group id." 603 | exit 1 604 | else 605 | user_parameters+=(-u "1000:${docker_gid}") 606 | fi 607 | 608 | local effective_java_opts=() 609 | if [ ! -z "${x_java_opts}" ]; then 610 | local container_java_opts="$(get_image_environment_variable "${image_name}" JAVA_OPTS)" 611 | effective_java_opts+=(-e "JAVA_OPTS=${container_java_opts} ${x_java_opts}") 612 | fi 613 | 614 | local environment_variable_parameters=() 615 | if [ ${cache_enabled} = true ] ; then 616 | environment_variable_parameters+=(-e "DL_CACHE_NETWORK=${network_name}") 617 | environment_variable_parameters+=(-e "DL_CACHE_HOSTNAME=${nexus_container_name}") 618 | fi 619 | 620 | # Read proxy parameters separated by new line 621 | local old_IFS=${IFS} 622 | local IFS=$'\n' 623 | environment_variable_parameters+=($(get_proxy_parameters "${image_name}")) 624 | IFS=${old_IFS} 625 | 626 | environment_variable_parameters+=("${effective_java_opts[@]}") 627 | 628 | local mount_parameters=() 629 | mount_parameters+=(-v /var/run/docker.sock:/var/run/docker.sock) 630 | mount_parameters+=(-v "${jenkins_home}:/var/jenkins_home") 631 | 632 | if [ "${tls_enabled}" == true ]; then 633 | if [ ! -f "${tls_folder}/jenkins.crt" ] || [ ! -f "${tls_folder}/jenkins.key" ]; then 634 | log_error "TLS certificate or private key is not found in 'tls' folder." 635 | log_error "Ensure that the TLS certificate jenkins.crt and the private key jenkins.key exist inside the tls directory" 636 | exit 1 637 | fi 638 | if [ "${host_os}" = windows ] ; then 639 | log_error "TLS is not supported on Windows. For a productive usage please use Linux." 640 | exit 1 641 | fi 642 | mount_parameters+=(-v "${cx_server_path}/tls:/var/ssl/jenkins") 643 | environment_variable_parameters+=(-e "JENKINS_OPTS=--httpsCertificate=/var/ssl/jenkins/jenkins.crt --httpsPrivateKey=/var/ssl/jenkins/jenkins.key --httpsPort=${container_port_https} --httpPort=${container_port_http}") 644 | else 645 | environment_variable_parameters+=(-e "JENKINS_OPTS=--httpPort=${container_port_http} --httpsPort=-1") 646 | fi 647 | 648 | if [ -e /cx-server/mount/jenkins-configuration ]; then 649 | environment_variable_parameters+=(-e CASC_JENKINS_CONFIG=/var/cx-server/jenkins-configuration) 650 | fi 651 | 652 | # Pass custom environment variables prefixed by 'CX', may be used to pass values into Configuration as Code 653 | for var in $(env | grep ^CX | cut -d'=' -f1) 654 | do 655 | environment_variable_parameters+=(-e $var) 656 | done 657 | 658 | environment_variable_parameters+=(-e DEVELOPER_MODE) 659 | 660 | if [ ! -z "${cx_server_path}" ]; then 661 | if [ "${host_os}" = windows ] ; then 662 | # transform windows path like "C:\abc\abc" to "//C/abc/abc" 663 | cx_server_path="//$(echo "${cx_server_path}" | sed -e 's/://' -e 's/\\/\//g')" 664 | fi 665 | mount_parameters+=(-v "${cx_server_path}:/var/cx-server:ro") 666 | fi 667 | 668 | # start container 669 | 670 | local start_jenkins=() 671 | start_jenkins+=(docker run --restart unless-stopped "${user_parameters[@]}" --name "${jenkins_container_name}" -d -p ${port_mapping}) 672 | start_jenkins+=("${mount_parameters[@]}" "${environment_variable_parameters[@]}" "${image_name}") 673 | run "${start_jenkins[@]}" 674 | 675 | if [ $? -ne "0" ]; then 676 | log_error "Failed to start new cx-server container." 677 | exit $?; 678 | fi 679 | fi 680 | 681 | wait_for_started 682 | } 683 | 684 | function backup_volume() 685 | { 686 | local backup_temp_folder="${backup_folder}/temp" 687 | local backup_filepath="${backup_folder}/${backup_file_name}" 688 | 689 | #ensure that folder exists 690 | mkdir -p "${backup_folder}" 691 | 692 | # FNR will skip the header and awk will print only 4th column of the output. 693 | local free_space=$(df -h "${backup_folder}" | awk 'FNR > 1 {print $4}') 694 | local used_space=$(docker run --rm -v "${jenkins_home}":/jenkins_home_dir "${alpine_docker_image}" du -sh /jenkins_home_dir | awk '{print $1}') 695 | log_info "Available free space on the host is ${free_space} and the size of the volume is ${used_space}" 696 | 697 | local free_space_bytes=$(df "${backup_folder}" | awk 'FNR > 1 {print $4}') 698 | local used_space_bytes=$(docker run --rm -v "${jenkins_home}":/jenkins_home_dir "${alpine_docker_image}" du -s /jenkins_home_dir | awk '{print $1}') 699 | # Defensive estimation: Backup needs twice the volume size (copying + zipping) 700 | local estimated_free_space_after_backup=$(expr ${free_space_bytes} - $(expr ${used_space_bytes} \* 2)) 701 | 702 | if [[ ${estimated_free_space_after_backup} -lt 0 ]]; then 703 | log_error "Not enough disk space for creating a backup. We require twice the size of the volume." 704 | exit 1 705 | fi 706 | 707 | # Backup can be taken when Jenkins server is up 708 | # https://wiki.jenkins.io/display/JENKINS/Administering+Jenkins 709 | log_info "Backup of '${jenkins_home}' is in progress. It may take several minutes to complete." 710 | 711 | # Perform backup by first creating a temp copy and then taring the copy. 712 | # Reason: tar fails if files are changed during the runtime of the command. 713 | docker run --rm -v "${jenkins_home}":/jenkins_home_dir --volumes-from "${cxserver_companion_container_id}" "${alpine_docker_image}" \ 714 | sh -c "mkdir -p ${backup_temp_folder} && \ 715 | rm -rf ${backup_temp_folder} && \ 716 | cp -pr /jenkins_home_dir ${backup_temp_folder} && \ 717 | tar czf ${backup_filepath} -C ${backup_temp_folder} . /cx-server/mount/server.cfg &&\ 718 | chmod u=rw,g=rw,o= ${backup_filepath} && \ 719 | rm -rf ${backup_temp_folder}" 720 | if [ $? -ne "0" ] || [ ! -f "${backup_filepath}" ]; then 721 | log_error "Failed to take a backup of ${jenkins_home}." 722 | exit 1; 723 | fi 724 | log_info "Backup is completed and available in the backup directory. File name is ${backup_file_name}" 725 | log_info "Please note, this backup contains sensitive information." 726 | } 727 | 728 | function command_help_text() 729 | { 730 | # 14 is the field width of the command name column 731 | printf " ${bold_start}%-14s${bold_end} %s\n" "${1}" "${2}" 732 | } 733 | 734 | function display_help() 735 | { 736 | if [ ! -z "$1" ]; then 737 | echo "'cx-server $1', unknown command" 738 | fi 739 | echo "" 740 | echo "Usage: cx-server [COMMAND]" 741 | echo "" 742 | echo "Tool for controlling the lifecycle of the Cx server." 743 | echo "Use server.cfg for customizing parameters." 744 | echo "" 745 | echo "Commands:" 746 | command_help_text 'start' "Starts the server container using the configured parameters for jenkins_home, docker_registry, docker_image, http_port." 747 | command_help_text 'status' "Display status information about Cx Server" 748 | command_help_text 'stop' "Stops the running server container." 749 | command_help_text 'remove' "Removes a stopped server container. A subsequent call of 'start' will instantiate a fresh container." 750 | command_help_text 'backup' "Takes a backup of the configured 'jenkins_home' and stores it in the backup directory." 751 | command_help_text 'restore' "Restores the content of the configured 'jenkins_home' by the contents of the provided backup file. Usage: 'cx-server restore '." 752 | command_help_text 'update script' "Explicitly pull the Docker image containing this script to update to its latest version. Running this is not required, since the image is updated automatically." 753 | command_help_text 'update image' "Updates the configured 'docker_image' to the newest available version of Cx Server image on Docker Hub." 754 | command_help_text 'initial-credentials' "Shows initial admin credentials." 755 | command_help_text 'help' "Shows this help text." 756 | } 757 | 758 | function restore_volume() 759 | { 760 | if [[ -z "$1" ]]; then 761 | log_error "No argument supplied, restore requires a name of the backup file." 762 | exit 1 763 | fi 764 | 765 | local backup_filename 766 | local backup_filepath 767 | 768 | if [[ "${1}" != *"/"* ]]; then 769 | backup_filename="${1}" 770 | backup_filepath="${backup_folder}/${backup_filename}" 771 | 772 | else 773 | backup_filename="$(basename -- ${1})" 774 | backup_filepath="$(realpath ${1})" 775 | fi 776 | 777 | if [[ ! -r "${backup_filepath}" ]]; then 778 | log_error "Backup file '${backup_filename}' can not be read or does not exist in '${backup_filepath}'." 779 | 780 | exit 1 781 | fi 782 | 783 | 784 | log_warn "Restore will stop Jenkins, delete the content of '${jenkins_home}', and restore it with the content in '${backup_filename}'\nDo you really want to continue? (yes/no)" 785 | read answer 786 | 787 | if [ "$answer" == "Y" ] || [ "$answer" == "y" ] || [ "$answer" == "YES" ] || [ "$answer" == "yes" ]; then 788 | stop_jenkins 789 | log_info "Starting to restore the state of '${jenkins_home}' from '${backup_filename}'" 790 | 791 | docker run --rm -v "${jenkins_home}":/jenkins_home_dir --volumes-from "${cxserver_companion_container_id}" "${alpine_docker_image}" \ 792 | sh -c "rm -rf /jenkins_home_dir/* /jenkins_home_dir/..?* /jenkins_home_dir/.[!.]* \ 793 | && ls -al / \ 794 | && tar -C /jenkins_home_dir/ -zxf ${backup_filepath} \ 795 | && cp /jenkins_home_dir/cx-server/mount/server.cfg /cx-server/mount/server.cfg \ 796 | && chmod u=rw,g=rw,o=rw /cx-server/mount/server.cfg" 797 | 798 | if [ $? -eq 0 ]; then 799 | log_info "Restore completed" 800 | remove_containers 801 | log_info "Jenkins is stopped.\nWould you like to start it? (yes/no)" 802 | read answer 803 | if [ "$answer" == "Y" ] || [ "$answer" == "y" ] || [ "$answer" == "YES" ] || [ "$answer" == "yes" ]; then 804 | read_configuration 805 | start_nexus 806 | start_jenkins 807 | fi 808 | else 809 | log_error "There was an error while restoring the backup." 810 | exit 1 811 | fi 812 | elif [ "$answer" == "N" ] || [ "$answer" == "n" ] || [ "$answer" == "NO" ] || [ "$answer" == "no" ]; then 813 | log_info "Cancelling restore" 814 | else 815 | log_info "Invalid input" 816 | exit 1 817 | fi 818 | } 819 | 820 | function remove_containers() 821 | { 822 | run docker rm "${jenkins_container_name}" 823 | } 824 | 825 | function remove_networks() 826 | { 827 | # prints first column which corresponds to network id 828 | for network_id in $(docker network ls --no-trunc --filter name="${network_name}" --format '{{.ID}}') 829 | do 830 | run docker network remove "${network_id}" 831 | done 832 | } 833 | 834 | function read_configuration() 835 | { 836 | dos2unix /cx-server/mount/server.cfg 837 | source /cx-server/server-default.cfg 838 | source /cx-server/mount/server.cfg 839 | 840 | #TODO: Test that no outdated config properties are used 841 | 842 | if [ $? -ne 0 ]; then 843 | log_error 'Failed to load config from server.cfg file.' 844 | exit 1 845 | fi 846 | } 847 | 848 | function check_image_update() 849 | { 850 | if [ -z ${DEVELOPER_MODE} ]; then docker pull "${cxserver_companion_docker_image}"; fi 851 | 852 | echo -n "Checking for new version of Cx Server Docker image... " 853 | 854 | local return_code 855 | local stdout 856 | 857 | if [ ! -z ${docker_registry} ]; then 858 | echo "skipping update check because custom docker registry is used." 859 | else 860 | stdout="$(node /cx-server/checkversion.js "${docker_image}")" 861 | return_code=$? 862 | 863 | if [ ${return_code} -eq 0 ]; then 864 | echo "'${docker_image}' is up to date." 865 | elif [ ${return_code} -eq 3 ]; then 866 | echo "newer version available: '${stdout}' (current version '${docker_image}')." 867 | echo -e "${bold_start}Please run 'cx-server update image' to update Cx Server Docker image to its newest version.${bold_end}" 868 | else 869 | log_warn "check returned with error code (${return_code}). Error message: '${stdout}'." 870 | fi 871 | fi 872 | } 873 | 874 | function update_image() 875 | { 876 | if [ -z ${DEVELOPER_MODE} ]; then docker pull "${cxserver_companion_docker_image}"; fi 877 | if [ ! -z ${docker_registry} ]; then 878 | log_error "A custom Docker registry is configured. The `update image` command is only supported for Docker Hub. Please perform a manual update. Please refer to https://github.com/SAP/devops-docker-cx-server/blob/master/docs/operations/cx-server-operations-guide.md#performing-a-manual-update for more information." 879 | exit 1 880 | fi 881 | 882 | echo "Checking for newest version of Cx Server Docker image... " 883 | 884 | local newimagecmd_return_code 885 | local newimagecmd_stdout 886 | 887 | newimagecmd_stdout="$(node ../checkversion.js "${docker_image}")" 888 | newimagecmd_return_code=$? 889 | 890 | if [ ${newimagecmd_return_code} -eq 0 ]; then 891 | echo "Your current version '${docker_image}' is up to date." 892 | elif [ ${newimagecmd_return_code} -eq 3 ]; then 893 | echo "Newer version detected. Updating to '${newimagecmd_stdout}'." 894 | 895 | local replaceimgcmd_returncode 896 | local replaceimagecmd_stdout 897 | replaceimagecmd_stdout=$(node ../updateversion.js "${docker_image}" "${newimagecmd_stdout}") 898 | replaceimgcmd_returncode=$? 899 | 900 | if [ ${replaceimgcmd_returncode} -eq 0 ]; then 901 | echo "Success: ${replaceimagecmd_stdout}" 902 | else 903 | log_error "${replaceimagecmd_stdout}" 904 | exit ${replaceimgcmd_returncode} 905 | fi 906 | 907 | else 908 | log_error "Check returned with error code (${newimagecmd_return_code}). Error message: '${newimagecmd_stdout}'." 909 | fi 910 | } 911 | 912 | function update_cx_server_script() 913 | { 914 | # Bash 915 | if [ ! -f '/cx-server/life-cycle-scripts/cx-server' ]; then 916 | echo "" 917 | log_error 'Failed to read newest cx-server version for Bash. Skipping update.' 918 | else 919 | newest_version="$( '/cx-server/mount/cx-server' 925 | fi 926 | this_version="$( '/cx-server/mount/cx-server' 930 | fi 931 | fi 932 | 933 | 934 | # Windows 935 | if [ ! -f '/cx-server/life-cycle-scripts/cx-server.bat' ]; then 936 | echo "" 937 | log_error 'Failed to read newest cx-server version for Windows. Skipping update.' 938 | else 939 | newest_version_bat="$( '/cx-server/mount/cx-server.bat' 945 | fi 946 | this_version_bat="$( '/cx-server/mount/cx-server.bat' 950 | unix2dos /cx-server/mount/cx-server.bat 951 | fi 952 | fi 953 | exit 0 954 | } 955 | 956 | # With too little memory, containers might get killed without any notice to the user. 957 | # This is likely the case when running Docker on Windows or Mac, where a Virtual Machine is used which has 2 GB memory by default. 958 | # At least, we can indicate that memory might be an issue to the user. 959 | function check_memory() { 960 | memory=$(free -m | awk '/^Mem:/{print $2}'); 961 | # The "magic number" is an approximation based on setting the memory of the Linux VM to 4 GB on Docker for Mac 962 | if [ "${memory}" -lt "3900" ]; then 963 | echo "${memory}" 964 | fi 965 | } 966 | 967 | function warn_low_memory() { 968 | local memory=$(check_memory) 969 | if [ ! -z "${memory}" ]; then 970 | log_warn "Low memory assigned to Docker detected (${memory} MB). Please ensure Docker has at least 4 GB of memory, otherwise your builds are likely to fail." 971 | log_warn "Depending on the number of jobs running, much more memory might be required." 972 | log_warn "On Windows and Mac, check how much memory Docker can use in 'Preferences', 'Advanced'. See https://docs.docker.com/docker-for-windows/#advanced or https://docs.docker.com/docker-for-mac/#advanced for more details." 973 | fi 974 | } 975 | 976 | function warn_low_memory_with_confirmation() { 977 | warn_low_memory 978 | local memory=$(check_memory) 979 | if [ ! -z "${memory}" ]; then 980 | log_warn "Are you sure you want to continue starting Cx Server with this amount of memory? (Y/N)" 981 | 982 | read answer 983 | if [ "$answer" == "Y" ] || [ "$answer" == "y" ] || [ "$answer" == "YES" ] || [ "$answer" == "yes" ]; then 984 | log_warn "Please keep an eye on the available memory, for example, using 'docker stats'." 985 | else 986 | exit 1 987 | fi 988 | fi 989 | } 990 | 991 | function get_port_mapping(){ 992 | declare -n return_value=$1 993 | local mapping="" 994 | if [ "${tls_enabled}" == true ]; then 995 | echo " - https_port=${https_port}" 996 | assert_config_set "https_port" 997 | mapping="${https_port}:${container_port_https}" 998 | else 999 | echo " - http_port=${http_port}" 1000 | assert_config_set "http_port" 1001 | mapping="${http_port}:${container_port_http}" 1002 | fi 1003 | return_value=$mapping 1004 | } 1005 | 1006 | # Returns a valid header snippet containing the crumb. In case XSRF protection is disabled the empty string is returned. 1007 | function get_crumb_header_snippet { 1008 | 1009 | local JENKINS_CONTAINER_NAME=$1; shift 1010 | local PORT=$1; shift 1011 | local COOKIES_JAR=$1; shift 1012 | local USER_SNIPPET=$@; 1013 | 1014 | CRUMB_CMD=(docker exec ${JENKINS_CONTAINER_NAME} curl -w "\nHTTP_CODE:%{http_code}\n" ${USER_SNIPPET} --cookie-jar "${COOKIES_JAR}" --noproxy localhost http://localhost:${PORT}/crumbIssuer/api/xml?xpath=concat\(//crumbRequestField,%22:%22,//crumb\)) 1015 | 1016 | if [ ! -z "${password}" ]; then 1017 | trace_execution "${CRUMB_CMD[@]//${password}/******}" 1018 | else 1019 | trace_execution "${CRUMB_CMD[@]}" 1020 | fi 1021 | 1022 | local CRUMB_RESPONSE=$("${CRUMB_CMD[@]}" 2>/dev/null) 1023 | 1024 | local HTTP_CODE=$(echo "${CRUMB_RESPONSE}" |grep HTTP_CODE |sed 's/HTTP_CODE://g') 1025 | local CRUMB_SNIPPET="" 1026 | 1027 | case "${HTTP_CODE}" in 1028 | 200) 1029 | CRUMB=$(echo "${CRUMB_RESPONSE}" |grep -v HTTP_CODE) 1030 | log_info "XSRF-Protection enabled" 1031 | CRUMB_SNIPPET="-H ${CRUMB}" 1032 | ;; 1033 | 401) 1034 | die "Cannot get XSRF crumb. Unauthorized." 7 1035 | ;; 1036 | 404) 1037 | log_info "XSRF-Protection not enabled" 1038 | ;; 1039 | *) 1040 | log_warn "Response from retrieving crumb: \"${CRUMB_RESPONSE}\"" 1041 | die "Unexpected response code while retrieving crumb: \"${HTTP_CODE}\"" 8 1042 | ;; 1043 | esac 1044 | 1045 | echo "${CRUMB_SNIPPET}" 1046 | } 1047 | 1048 | ### Start of Script 1049 | read_configuration 1050 | 1051 | # ensure that docker is installed 1052 | command -v docker > /dev/null 2>&1 || { echo >&2 "Docker does not seem to be installed. Please install docker and ensure that the docker command is included in \$PATH."; exit 1; } 1053 | 1054 | if [ "$1" == "backup" ]; then 1055 | #TODO: Return code handling? 1056 | backup_volume 1057 | elif [ "$1" == "restore" ]; then 1058 | restore_volume "$2" 1059 | elif [ "$1" == "start" ]; then 1060 | warn_low_memory_with_confirmation 1061 | check_image_update 1062 | start_nexus 1063 | start_jenkins 1064 | elif [ "$1" == "stop" ]; then 1065 | if [ "$2" == "--force" ]; then 1066 | stop_jenkins_unsafe 1067 | stop_nexus_unsafe 1068 | else 1069 | stop_jenkins 1070 | stop_nexus 1071 | fi 1072 | elif [ "$1" == "start_cache" ]; then 1073 | start_nexus 1074 | elif [ "$1" == "stop_cache" ]; then 1075 | stop_nexus 1076 | elif [ "$1" == "remove" ]; then 1077 | remove_containers 1078 | remove_networks 1079 | elif [ "$1" == "update" ]; then 1080 | if [ "$2" == "script" ]; then 1081 | docker pull "${cxserver_companion_docker_image}" 1082 | elif [ "$2" == "image" ]; then 1083 | stop_nexus 1084 | stop_jenkins 1085 | remove_containers 1086 | remove_networks 1087 | backup_volume 1088 | update_image 1089 | read_configuration 1090 | start_nexus 1091 | start_jenkins 1092 | else 1093 | log_error "Missing qualifier. Please specify a update qualifier. Execute 'cx-server' for more information." 1094 | fi 1095 | elif [ "$1" == "help" ]; then 1096 | display_help 1097 | warn_low_memory 1098 | elif [ "$1" == "status" ]; then 1099 | node /cx-server/status.js "{\"cache_enabled\": \"${cache_enabled}\"}" 1100 | elif [ "$1" == "initial-credentials" ]; then 1101 | if docker logs cx-jenkins-master 2>&1 | grep "Default credentials for Jenkins" ; then 1102 | log_info "Note that this command only shows the default credentials. 1103 | Please change the password for admin as soon as you can. 1104 | Go to http://localhost/user/admin/configure (you might need to replace localhost with the IP or hostname) to change admin's password."; 1105 | else 1106 | log_info "Could not get initial credentials. It might take a few minutes to get them, or your Jenkins instance might not be secured."; 1107 | fi 1108 | else 1109 | display_help "$1" 1110 | warn_low_memory 1111 | fi 1112 | 1113 | if [ -z ${DEVELOPER_MODE} ]; then update_cx_server_script; fi 1114 | -------------------------------------------------------------------------------- /cx-server-companion/files/init-cx-server: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if ! cp -r /cx-server/life-cycle-scripts/* /cx-server/mount/ 3 | then 4 | echo "There was an error in generating cx-server file" 5 | echo "Please make sure that you have mounted your local directory to the companion" 6 | echo "Example:" 7 | echo " docker run -it --rm -u \$(id -u):\$(id -g) -v \${PWD}:/cx-server/mount/ ppiper/cx-server-companion:latest init-cx-server" 8 | else 9 | echo "The cx-server and the server.cfg files have been successfully generated." 10 | echo "Execute ./cx-server help command to know more about the cx-server script" 11 | fi 12 | 13 | echo "Make sure to secure your Jenkins instance, see CX-SERVER-SECURITY.md for details." 14 | -------------------------------------------------------------------------------- /cx-server-companion/files/init-nexus.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | /* 6 | * Run initialization script on nexus. 7 | * The actual script is in `nexus-init-repos.groovy`. 8 | */ 9 | 10 | const request = require('request') 11 | const fs = require('fs') 12 | const _ = require('underscore') 13 | 14 | if (process.argv.length < 3) { 15 | throw new Error("Usage: node init-nexus.js template-values-object [application-configuration-object]") 16 | } 17 | 18 | const templateValues = JSON.parse(process.argv[2]) 19 | let appConfig = {} 20 | if (!_.isUndefined(process.argv[3])) { 21 | appConfig = JSON.parse(process.argv[3]) 22 | } 23 | 24 | function verboseLog() { 25 | if (appConfig.verbose) { 26 | console.log.apply(null, arguments) 27 | } 28 | } 29 | 30 | verboseLog(templateValues) 31 | 32 | //The URL is accessible only by other containers that are connected to the same docker network 33 | let baseUrl = 'http://cx-nexus:8081/' 34 | let scriptTemplateFilePath = '/cx-server/nexus-init-repos.groovy' 35 | if (appConfig.runLocally) { 36 | baseUrl = 'http://localhost:8081/' 37 | scriptTemplateFilePath = appConfig.scriptTemplateFilePath 38 | } 39 | 40 | /* 41 | * The base64 encoded authorization is a default Nexus credentials. 42 | * The URL is accessible only by other containers that are connected to the same docker network 43 | */ 44 | const nexusRequest = request.defaults({ 45 | headers: { 46 | 'Authorization': 'Basic YWRtaW46YWRtaW4xMjM=', 47 | 'Content-Type': 'application/json' 48 | } 49 | }) 50 | 51 | const scriptTemplate = fs.readFileSync(scriptTemplateFilePath).toString() 52 | verboseLog("Script Template:", scriptTemplate) 53 | const compiled = _.template(scriptTemplate) 54 | const script = compiled(templateValues) 55 | verboseLog("Compiled Script:", script) 56 | 57 | const scriptRequestBody = { 58 | "name": "init-repos", 59 | "type": "groovy", 60 | "content": script 61 | } 62 | 63 | nexusRequest 64 | .get(`${baseUrl}service/rest/v1/script/init-repos`) 65 | .on('response', function (response) { 66 | if (response.statusCode !== 200) { 67 | console.log('Creating nexus initialization script...') 68 | nexusRequest 69 | .post(`${baseUrl}service/rest/v1/script`) 70 | .json(scriptRequestBody) 71 | .on('response', function (response) { 72 | if (isInSuccessFamily(response.statusCode)) { 73 | runScript() 74 | } else { 75 | console.log(`Unexpected status ${response.statusMessage} when creating nexus initialization script. Can't run script.`) 76 | } 77 | }) 78 | } else { 79 | console.log('Updating nexus initialization script...') 80 | nexusRequest 81 | .put(`${baseUrl}service/rest/v1/script/init-repos`) 82 | .json(scriptRequestBody) 83 | .on('response', function (response) { 84 | if (isInSuccessFamily(response.statusCode)) { 85 | runScript() 86 | } else { 87 | console.log(`Unexpected status ${response.statusMessage} when updating nexus initialization script. Can't run script.`) 88 | } 89 | }) 90 | } 91 | }) 92 | 93 | function runScript() { 94 | /* 95 | * The base64 encoded authorization is a default Nexus credentials. 96 | * The URL is accessible only by other containers that are connected to the same docker network 97 | */ 98 | request(`${baseUrl}service/rest/v1/script/init-repos/run`, { 99 | method: 'POST', 100 | headers: { 101 | 'Authorization': 'Basic YWRtaW46YWRtaW4xMjM=', 102 | 'Content-Type': 'text/plain' 103 | } 104 | }) 105 | .on('response', function (response) { 106 | console.log(`Run nexus initialization script, response: ${response.statusMessage}`) 107 | }) 108 | } 109 | 110 | function isInSuccessFamily(statusCode) { 111 | return statusCode.toString().startsWith("2") 112 | } 113 | -------------------------------------------------------------------------------- /cx-server-companion/files/nexus-init-repos.groovy: -------------------------------------------------------------------------------- 1 | synchronized (this) { 2 | def result = [created: [], deleted: []] 3 | def repoManager = repository.repositoryManager 4 | def existingRepoNames = repoManager.browse().collect { repo -> repo.name } 5 | def mvnProxyName = 'mvn-proxy' 6 | if (!existingRepoNames.contains(mvnProxyName)) { 7 | repository.createMavenProxy(mvnProxyName, '<%= mvn_repository_url %>') 8 | result.created.add(mvnProxyName) 9 | } 10 | def npmProxyName = 'npm-proxy' 11 | if (!existingRepoNames.contains(npmProxyName)) { 12 | repository.createNpmProxy(npmProxyName, '<%= npm_registry_url %>') 13 | result.created.add(npmProxyName) 14 | } 15 | def proxyRepos = [npmProxyName, mvnProxyName] 16 | def toDelete = existingRepoNames.findAll { !proxyRepos.contains(it) } 17 | for (def repoName : toDelete) { 18 | repoManager.delete(repoName) 19 | result.deleted.add(repoName) 20 | } 21 | 22 | if ('<%= http_proxy %>'?.trim()) { 23 | URL httpProxy = new URL('<%= http_proxy %>') 24 | println("httpProxy: ${httpProxy}") 25 | 26 | Map httpCredentials = extractCredentials(httpProxy) 27 | if(!httpCredentials) { 28 | core.httpProxy(httpProxy.host, httpProxy.port) 29 | } 30 | else { 31 | core.httpProxyWithBasicAuth(httpProxy.host, httpProxy.port, httpCredentials.username, httpCredentials.password) 32 | } 33 | } 34 | 35 | if ('<%= https_proxy %>'?.trim()) { 36 | URL httpsProxy = new URL('<%= https_proxy %>') 37 | println("httpsProxy: ${httpsProxy}") 38 | 39 | Map httpsCredentials = extractCredentials(httpsProxy) 40 | if(!httpsCredentials) { 41 | core.httpsProxy(httpsProxy.host, httpsProxy.port) 42 | } 43 | else { 44 | core.httpsProxyWithBasicAuth(httpsProxy.host, httpsProxy.port, httpsCredentials.username, httpsCredentials.password) 45 | } 46 | } 47 | 48 | if (('<%= http_proxy %>'?.trim() || '<%= https_proxy %>'?.trim()) && '<%= no_proxy %>'?.trim()) { 49 | String[] nonProxyHosts = '<%= no_proxy %>'.split(',') 50 | /* Insert wildcards to have a rough conversion between the unix-like no-proxy list and the java notation. 51 | * For example, `localhost,.corp,.maven.apache.org,x.y.,myhost` will be transformed to 52 | * `[*localhost,*.corp,*.maven.apache.org,*x.y,*myhost]`. */ 53 | .collect { it.replaceFirst('\\.$', '')} 54 | .collect { "*${it}" } 55 | println("nonProxyHosts: ${nonProxyHosts.join(',')}") 56 | core.nonProxyHosts(nonProxyHosts) 57 | } 58 | 59 | return result 60 | } 61 | 62 | def extractCredentials(URL proxyURL) { 63 | def userInfo = proxyURL.getUserInfo() 64 | if(!userInfo) { 65 | return null 66 | } 67 | 68 | String[] splitted = userInfo.split(":") 69 | if(splitted.length != 2) { 70 | throw new Error("Failed to extract network proxy credentials. Expected format: 'http://myuser:mypass@myproxy.corp:8080'") 71 | } 72 | 73 | return [ username: URLDecoder.decode(splitted[0], "UTF-8"), password: URLDecoder.decode(splitted[1], "UTF-8") ] 74 | } 75 | -------------------------------------------------------------------------------- /cx-server-companion/files/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cx-server-companion", 3 | "requires": true, 4 | "lockfileVersion": 1, 5 | "dependencies": { 6 | "ajv": { 7 | "version": "5.5.2", 8 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", 9 | "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", 10 | "requires": { 11 | "co": "^4.6.0", 12 | "fast-deep-equal": "^1.0.0", 13 | "fast-json-stable-stringify": "^2.0.0", 14 | "json-schema-traverse": "^0.3.0" 15 | } 16 | }, 17 | "asn1": { 18 | "version": "0.2.3", 19 | "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", 20 | "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" 21 | }, 22 | "assert-plus": { 23 | "version": "1.0.0", 24 | "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", 25 | "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" 26 | }, 27 | "asynckit": { 28 | "version": "0.4.0", 29 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 30 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 31 | }, 32 | "aws-sign2": { 33 | "version": "0.7.0", 34 | "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", 35 | "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" 36 | }, 37 | "aws4": { 38 | "version": "1.7.0", 39 | "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", 40 | "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==" 41 | }, 42 | "bcrypt-pbkdf": { 43 | "version": "1.0.1", 44 | "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", 45 | "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", 46 | "optional": true, 47 | "requires": { 48 | "tweetnacl": "^0.14.3" 49 | } 50 | }, 51 | "caseless": { 52 | "version": "0.12.0", 53 | "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", 54 | "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" 55 | }, 56 | "co": { 57 | "version": "4.6.0", 58 | "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", 59 | "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" 60 | }, 61 | "combined-stream": { 62 | "version": "1.0.6", 63 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", 64 | "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", 65 | "requires": { 66 | "delayed-stream": "~1.0.0" 67 | } 68 | }, 69 | "core-util-is": { 70 | "version": "1.0.2", 71 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 72 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 73 | }, 74 | "dashdash": { 75 | "version": "1.14.1", 76 | "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", 77 | "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", 78 | "requires": { 79 | "assert-plus": "^1.0.0" 80 | } 81 | }, 82 | "delayed-stream": { 83 | "version": "1.0.0", 84 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 85 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 86 | }, 87 | "ecc-jsbn": { 88 | "version": "0.1.1", 89 | "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", 90 | "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", 91 | "optional": true, 92 | "requires": { 93 | "jsbn": "~0.1.0" 94 | } 95 | }, 96 | "escape-string-regexp": { 97 | "version": "1.0.5", 98 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 99 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 100 | }, 101 | "extend": { 102 | "version": "3.0.2", 103 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", 104 | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" 105 | }, 106 | "extsprintf": { 107 | "version": "1.3.0", 108 | "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", 109 | "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" 110 | }, 111 | "fast-deep-equal": { 112 | "version": "1.1.0", 113 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", 114 | "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" 115 | }, 116 | "fast-json-stable-stringify": { 117 | "version": "2.0.0", 118 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 119 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" 120 | }, 121 | "forever-agent": { 122 | "version": "0.6.1", 123 | "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", 124 | "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" 125 | }, 126 | "form-data": { 127 | "version": "2.3.2", 128 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", 129 | "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", 130 | "requires": { 131 | "asynckit": "^0.4.0", 132 | "combined-stream": "1.0.6", 133 | "mime-types": "^2.1.12" 134 | } 135 | }, 136 | "getpass": { 137 | "version": "0.1.7", 138 | "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", 139 | "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", 140 | "requires": { 141 | "assert-plus": "^1.0.0" 142 | } 143 | }, 144 | "har-schema": { 145 | "version": "2.0.0", 146 | "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", 147 | "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" 148 | }, 149 | "har-validator": { 150 | "version": "5.0.3", 151 | "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", 152 | "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", 153 | "requires": { 154 | "ajv": "^5.1.0", 155 | "har-schema": "^2.0.0" 156 | } 157 | }, 158 | "http-signature": { 159 | "version": "1.2.0", 160 | "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", 161 | "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", 162 | "requires": { 163 | "assert-plus": "^1.0.0", 164 | "jsprim": "^1.2.2", 165 | "sshpk": "^1.7.0" 166 | } 167 | }, 168 | "is-typedarray": { 169 | "version": "1.0.0", 170 | "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", 171 | "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" 172 | }, 173 | "isstream": { 174 | "version": "0.1.2", 175 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 176 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" 177 | }, 178 | "jsbn": { 179 | "version": "0.1.1", 180 | "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", 181 | "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", 182 | "optional": true 183 | }, 184 | "json-schema": { 185 | "version": "0.2.3", 186 | "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", 187 | "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" 188 | }, 189 | "json-schema-traverse": { 190 | "version": "0.3.1", 191 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", 192 | "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" 193 | }, 194 | "json-stringify-safe": { 195 | "version": "5.0.1", 196 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 197 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" 198 | }, 199 | "jsprim": { 200 | "version": "1.4.1", 201 | "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", 202 | "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", 203 | "requires": { 204 | "assert-plus": "1.0.0", 205 | "extsprintf": "1.3.0", 206 | "json-schema": "0.2.3", 207 | "verror": "1.10.0" 208 | } 209 | }, 210 | "mime-db": { 211 | "version": "1.33.0", 212 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", 213 | "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" 214 | }, 215 | "mime-types": { 216 | "version": "2.1.18", 217 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", 218 | "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", 219 | "requires": { 220 | "mime-db": "~1.33.0" 221 | } 222 | }, 223 | "oauth-sign": { 224 | "version": "0.8.2", 225 | "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", 226 | "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" 227 | }, 228 | "performance-now": { 229 | "version": "2.1.0", 230 | "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", 231 | "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" 232 | }, 233 | "punycode": { 234 | "version": "1.4.1", 235 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", 236 | "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" 237 | }, 238 | "qs": { 239 | "version": "6.5.2", 240 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", 241 | "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" 242 | }, 243 | "request": { 244 | "version": "2.87.0", 245 | "resolved": "https://registry.npmjs.org/request/-/request-2.87.0.tgz", 246 | "integrity": "sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw==", 247 | "requires": { 248 | "aws-sign2": "~0.7.0", 249 | "aws4": "^1.6.0", 250 | "caseless": "~0.12.0", 251 | "combined-stream": "~1.0.5", 252 | "extend": "~3.0.1", 253 | "forever-agent": "~0.6.1", 254 | "form-data": "~2.3.1", 255 | "har-validator": "~5.0.3", 256 | "http-signature": "~1.2.0", 257 | "is-typedarray": "~1.0.0", 258 | "isstream": "~0.1.2", 259 | "json-stringify-safe": "~5.0.1", 260 | "mime-types": "~2.1.17", 261 | "oauth-sign": "~0.8.2", 262 | "performance-now": "^2.1.0", 263 | "qs": "~6.5.1", 264 | "safe-buffer": "^5.1.1", 265 | "tough-cookie": "~2.3.3", 266 | "tunnel-agent": "^0.6.0", 267 | "uuid": "^3.1.0" 268 | } 269 | }, 270 | "safe-buffer": { 271 | "version": "5.1.2", 272 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 273 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 274 | }, 275 | "sshpk": { 276 | "version": "1.14.1", 277 | "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", 278 | "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", 279 | "requires": { 280 | "asn1": "~0.2.3", 281 | "assert-plus": "^1.0.0", 282 | "bcrypt-pbkdf": "^1.0.0", 283 | "dashdash": "^1.12.0", 284 | "ecc-jsbn": "~0.1.1", 285 | "getpass": "^0.1.1", 286 | "jsbn": "~0.1.0", 287 | "tweetnacl": "~0.14.0" 288 | } 289 | }, 290 | "tough-cookie": { 291 | "version": "2.3.4", 292 | "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", 293 | "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", 294 | "requires": { 295 | "punycode": "^1.4.1" 296 | } 297 | }, 298 | "tunnel-agent": { 299 | "version": "0.6.0", 300 | "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", 301 | "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", 302 | "requires": { 303 | "safe-buffer": "^5.0.1" 304 | } 305 | }, 306 | "tweetnacl": { 307 | "version": "0.14.5", 308 | "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", 309 | "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", 310 | "optional": true 311 | }, 312 | "underscore": { 313 | "version": "1.9.1", 314 | "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", 315 | "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" 316 | }, 317 | "uuid": { 318 | "version": "3.2.1", 319 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", 320 | "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" 321 | }, 322 | "verror": { 323 | "version": "1.10.0", 324 | "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", 325 | "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", 326 | "requires": { 327 | "assert-plus": "^1.0.0", 328 | "core-util-is": "1.0.2", 329 | "extsprintf": "^1.2.0" 330 | } 331 | } 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /cx-server-companion/files/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cx-server-companion", 3 | "description": "The cx-server companion provides functionality which is used within the cx-server script.", 4 | "license": "Apache-2.0", 5 | "repository": "https://github.com/SAP/devops-docker-images", 6 | "dependencies": { 7 | "escape-string-regexp": "^1.0.5", 8 | "request": "^2.87.0", 9 | "underscore": "^1.9.1" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /cx-server-companion/files/server-default.cfg: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ############################################# 4 | ### Build server configuration ### 5 | ############################################# 6 | 7 | ### Name of the used docker image 8 | docker_image="ppiper/jenkins-master:latest" 9 | 10 | ### Port on which jenkins will be reachable with http 11 | ### Not used when tls_enabled is set to true 12 | http_port="80" 13 | 14 | ### Port on which jenkins will be reachable with https 15 | https_port="443" 16 | 17 | ### Name of the Jenkins Home docker volume 18 | jenkins_home="jenkins_home_volume" 19 | 20 | ### Name of the backup file 21 | backup_file_name="jenkins_home_$(date -u +%Y-%m-%dT%H%M%Z).tar.gz" 22 | 23 | ############################################# 24 | ### Download cache configuration ### 25 | ############################################# 26 | 27 | ### local caching for node and maven dependencies 28 | cache_enabled=true 29 | -------------------------------------------------------------------------------- /cx-server-companion/files/status.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | if (!process.argv[2]) { 6 | console.error("Missing parameter: Configuration object is not specified"); 7 | process.exit(-1); 8 | } 9 | 10 | const configString = process.argv[2]; 11 | const appConfig = JSON.parse(configString); 12 | 13 | const expectDownloadCacheIsRunning = (appConfig.cache_enabled === true) || (appConfig.cache_enabled === 'true') || (appConfig.cache_enabled === ''); 14 | 15 | const { 16 | spawnSync 17 | } = require('child_process'); 18 | const ps = spawnSync('docker', ['ps', '--no-trunc', '--format', '{{ json . }}', '--filter', 'name=cx']); 19 | 20 | const containers = ps.stdout.toString().split('\n').filter(line => line.length > 3).map(jsonLine => JSON.parse(jsonLine)) 21 | 22 | if (containers.length === 0) { 23 | console.log('Cx Server is not running.') 24 | } else { 25 | if (expectDownloadCacheIsRunning) { 26 | if (containers.filter(c => c.Names.includes("cx-nexus")).length === 0) { 27 | console.error("⚠️ Expected Download cache to be running, but it is not. Most likely, this is caused by low memory in Docker." + 28 | "To fix this, please ensure that Docker has at least 4 GB memory, and restart Cx Server.") 29 | } 30 | } 31 | console.log('Running Cx Server containers:') 32 | console.log(spawnSync('docker', ['ps', '--filter', 'name=cx']).stdout.toString()) 33 | } 34 | -------------------------------------------------------------------------------- /cx-server-companion/files/updateversion.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | /*** 5 | * This script consumes an old value and new value for the config parameter docker_image. 6 | * It then updates all occurrences in the provided server.cfg file. 7 | */ 8 | 9 | const fs = require('fs'); 10 | const escapeRegex = require('escape-string-regexp'); 11 | 12 | const RETURN_CODES = { 13 | "GENERAL_ERROR": 1, 14 | "DOCKER_IMAGE_NOT_FOUND": 2, 15 | "CURRENT_DOCKER_IMAGE_NOT_SPECIFIED": 3, 16 | "NEW_DOCKER_IMAGE_NOT_SPECIFIED": 4 17 | }; 18 | 19 | if (!process.argv[2]) { 20 | exit("Missing parameter: Current docker image is not specified", RETURN_CODES.CURRENT_DOCKER_IMAGE_NOT_SPECIFIED) 21 | } 22 | 23 | if (!process.argv[3]) { 24 | exit("Missing parameter: New docker image is not specified", RETURN_CODES.NEW_DOCKER_IMAGE_NOT_SPECIFIED) 25 | } 26 | 27 | const currentImage = process.argv[2]; 28 | const newImage = process.argv[3]; 29 | 30 | const matches = []; 31 | 32 | const serverCfgPath = '/cx-server/mount/server.cfg'; 33 | fs.readFile(serverCfgPath, 'utf8', function (err, data) { 34 | if (err) { 35 | exit("Failed to read server.cfg file") 36 | } 37 | else { 38 | const strInitialFile = data; 39 | 40 | 41 | const regex = new RegExp(`docker_image=(\"|'|)${escapeRegex(currentImage)}(\"|'|)`, 'gm'); 42 | const strNewFile = strInitialFile.replace(regex, function (match, p1, p2, offset) { 43 | const newDockerImage = `docker_image="${newImage}"`; 44 | 45 | matches.push(offset); 46 | 47 | return newDockerImage 48 | }); 49 | 50 | if (matches.length === 0) { 51 | exit("Failed to find docker_image attribute in server.cfg file", RETURN_CODES.DOCKER_IMAGE_NOT_FOUND) 52 | } 53 | 54 | fs.writeFileSync(serverCfgPath, strNewFile); 55 | 56 | console.log("Replaced", matches.length, "occurences of docker_image at offsets", matches) 57 | } 58 | }); 59 | 60 | 61 | function exit(strErrorMessage, exitCode = RETURN_CODES.GENERAL_ERROR) { 62 | console.log(strErrorMessage); 63 | process.exit(exitCode); 64 | } 65 | -------------------------------------------------------------------------------- /cx-server-companion/life-cycle-scripts/CX-SERVER-SECURITY.md: -------------------------------------------------------------------------------- 1 | # Notes on running CX Server in Production 2 | 3 | Please be aware that running a Jenkins in production securely is not a trivial task. 4 | 5 | Some recommendations: 6 | 7 | Jenkins has a default password for the `admin` account set. 8 | You can get it by running `./cx-server initial-credentials` once your Jenkins is up and running. 9 | Please change it as soon as you can to a secure password. 10 | Go to http://localhost/user/admin/configure (you might need to replace localhost with the IP or hostname) to do that. 11 | 12 | The `latest` tag of the CX Server Docker images are rebuilt on a regular basis. 13 | To benefit from updated Jenkins core and plugin versions, you should stop, remove, and restart your CX Server instance on a regular basis. 14 | Part of this procedure should be to take backups (see `backup` and `restore` commands of `cx-server`). 15 | The commands to perform an update of the CX Server are: 16 | 17 | ``` 18 | ./cx-server stop 19 | ./cx-server remove 20 | ./cx-server start 21 | ``` 22 | 23 | If you use released versions of the CX Server, look for new releases on https://github.com/SAP/devops-docker-cx-server/releases 24 | 25 | In addition, we recomment to [read about "Securing Jenkins"](https://jenkins.io/doc/book/system-administration/security/) in the Jenkins manual. 26 | -------------------------------------------------------------------------------- /cx-server-companion/life-cycle-scripts/cx-server: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Ensure that Docker is installed 4 | command -v docker > /dev/null 2>&1 || { echo >&2 "Docker does not seem to be installed. Please install Docker and ensure that the 'docker' command is included in \$PATH."; exit 1; } 5 | 6 | source server.cfg 7 | 8 | if [ -z "${DEVELOPER_MODE}" ]; then 9 | docker pull ppiper/cx-server-companion; 10 | fi 11 | 12 | docker_arguments=(--rm 13 | --mount "source=${PWD},target=/cx-server/mount,type=bind" 14 | --workdir /cx-server/mount 15 | --volume /var/run/docker.sock:/var/run/docker.sock 16 | --env DEVELOPER_MODE 17 | --env JENKINS_USERNAME 18 | --env JENKINS_PASSWORD 19 | --env "host_os=unix" 20 | --env "cx_server_path=${PWD}") 21 | 22 | # Run Docker without '-it' flag on Jenkins to prevent our integration test from getting stuck 23 | if docker run --rm -it hello-world > /dev/null 2>&1 ; then 24 | docker_arguments+=('-it') 25 | else 26 | echo No interactive terminal, run Docker without '-it' flag 27 | fi 28 | 29 | 30 | # Environment file is required for integration tests 31 | readonly env_file='custom-environment.list' 32 | if [ -f ${env_file} ]; then 33 | docker_arguments+=(--env-file "${env_file}") 34 | fi 35 | 36 | # These braces ensure that the block in them cannot be overridden by updating this file on the fly. 37 | # The exit inside protects against unintended effect when the new script file is bigger than its old version. 38 | # See: https://stackoverflow.com/questions/3398258/edit-shell-script-while-its-running 39 | { 40 | docker run "${docker_arguments[@]}" ppiper/cx-server-companion /cx-server/cx-server-companion.sh "$@" 41 | exit $? 42 | } 43 | -------------------------------------------------------------------------------- /cx-server-companion/life-cycle-scripts/cx-server-completion.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | complete -W "start status stop remove backup restore update image script help initial-credentials" cx-server 3 | -------------------------------------------------------------------------------- /cx-server-companion/life-cycle-scripts/cx-server.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | ECHO Please note that Cx Server on Windows is for convenient development and evaluation. 4 | ECHO For running an productive Cx Server, please use a Linux System. 5 | 6 | IF "%~1"==backup GOTO NOT_SUPPORTED 7 | IF "%~1"==restore GOTO NOT_SUPPORTED 8 | IF "%~1"==update ( 9 | IF "%~2"=="image" GOTO NOT_SUPPORTED 10 | ) 11 | 12 | IF "%CD%"=="" GOTO WORKING_DIR_EMPTY 13 | 14 | IF "%DEVELOPER_MODE%"=="" docker pull ppiper/cx-server-companion 15 | 16 | ( 17 | docker run --rm -it --workdir /cx-server/mount --volume //var/run/docker.sock:/var/run/docker.sock --mount source="%CD%",target=/cx-server/mount,type=bind --env DEVELOPER_MODE --env host_os=windows --env cx_server_path="%CD%" ppiper/cx-server-companion /cx-server/cx-server-companion.sh %~1 %~2 18 | IF NOT %ERRORLEVEL% == 0 GOTO RUN_ERROR 19 | GOTO END 20 | ) 21 | 22 | :NOT_SUPPORTED 23 | ECHO "backup", "restore" and "update image" are currently not supported on Windows. 24 | GOTO END 25 | 26 | :RUN_ERROR 27 | ECHO Could not run the Cx Server Docker container. 28 | ECHO Please ensure that docker is running (with "docker run hello-world") 29 | ECHO Also, please make sure that the Windows drive where your project is located (usually C:) is shared with Docker as described in https://docs.docker.com/docker-for-windows/#shared-drives. 30 | GOTO END 31 | 32 | :WORKING_DIR_EMPTY 33 | ECHO The environment variable CD is not set. It is required that this variable points to the directory where cx-server.bat resides. 34 | GOTO END 35 | 36 | :END -------------------------------------------------------------------------------- /cx-server-companion/life-cycle-scripts/server.cfg: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #---------------------------------------------# 3 | #-- General configuration --------------------# 4 | #---------------------------------------------# 5 | 6 | #>> Specify effective value of 'http_proxy' / 'https_proxy' environment variables. 7 | #>> The specified proxy will also be used for the Jenkins and Nexus (Download Cache) processes. 8 | #>> Jenkins only supports one proxy server. In this case, https_proxy will have precedence over http_proxy. 9 | # http_proxy="http://username:password@proxy:8080" 10 | # https_proxy="http://username:password@proxy:8080" 11 | 12 | #>> Specify effective value of 'no_proxy' environment variable (overwrites value in base image) 13 | # no_proxy="localhost,.corp" 14 | 15 | #>> Specify additional no values of 'no_proxy' environment variable (appended to value in base image) 16 | # x_no_proxy="localhost,.corp" 17 | 18 | #---------------------------------------------# 19 | #-- Build server configuration ---------------# 20 | #---------------------------------------------# 21 | 22 | #>> Address of the used docker registry. Override if you do not want to use Docker's default registry. 23 | # docker_registry="your-custom-registry.corp" 24 | 25 | #>> Name of the used docker image 26 | # docker_image="ppiper/jenkins-master:latest" 27 | 28 | #>> Enable TLS encryption 29 | # tls_enabled=true 30 | 31 | #>> Port on which jenkins will be reachable via http. 32 | #>> This port will be only opened when TLS is not active. 33 | # http_port="80" 34 | 35 | #>> Port on which jenkins will be reachable via https when TLS is activated. 36 | # https_port="443" 37 | 38 | #>> Name of the docker volume holding the jenkins_home folder. 39 | # jenkins_home="jenkins_home_volume" 40 | 41 | #>> Name of the backup file in backup directory. 42 | # backup_file_name="jenkins_home_$(date -u +%Y-%m-%dT%H%M%Z).tar.gz" 43 | 44 | #>> Additional JAVA_OPTS for the jenkins container. The value will be appended to the standard value. 45 | # x_java_opts="-Xmx1024m" 46 | 47 | 48 | #---------------------------------------------# 49 | #-- Download cache configuration -------------# 50 | #---------------------------------------------# 51 | 52 | #>> Toggle for turning the download cache on and off. 53 | # cache_enabled=false 54 | 55 | #>> Maven repository that will be cached via the download cache (default is maven central) 56 | # mvn_repository_url="https://your-local-maven-repo.corp/maven2/" 57 | 58 | #>> npm repository that will be cached via the download cache (default is central npm registy) 59 | # npm_registry_url="https://your-local-npm-registry.corp/" 60 | 61 | #>> Additional JAVA_OPTS for the download cache server. The value will be appended to the standard value. 62 | # x_nexus_java_opts="-Xms256m -Xmx2048m" 63 | -------------------------------------------------------------------------------- /docs/development/how-to-release.md: -------------------------------------------------------------------------------- 1 | # Release Documentation 2 | 3 | See the [list of existing releases on GitHub](https://github.com/SAP/devops-docker-images/releases). 4 | 5 | A release of [devops-docker-images](https://github.com/SAP/devops-docker-images) is defined by a git tag and cohesive image tags on [Docker Hub](https://hub.docker.com/u/ppiper). 6 | Release version numbers are defined as matching the expression `/^v(\d+)$/`, so `v17` is a valid example, and `v17.3` is invalid. 7 | 8 | ## Testing 9 | 10 | * Ensure all [automated tests are 'green' in the current `master` branch](https://github.com/SAP/devops-docker-images/commits/master) 11 | 12 | ## How to perform a release 13 | 14 | * Go to the [releases page on GitHub](https://github.com/SAP/devops-docker-images/releases) 15 | * Select an appropriate tag name like `v17` (by default increase the last release to the next major version) 16 | * GitHub will create the tag 17 | * Fill out release notes (see template below) informing the user about actions they need to take when using this release or new features 18 | * Publish the release 19 | * Observe the release builds on [DockerHub](https://hub.docker.com/u/ppiper) 20 | * Check that all expected builds are triggered, and the builds succeed. This will take some time. 21 | 22 | ### Release notes template 23 | 24 | ```markdown 25 | # Release `vX` 26 | 27 | ## New Functionality 28 | * 29 | 30 | ## Improvements 31 | * 32 | 33 | ## Fixes 34 | * 35 | 36 | ## Incompatible Changes 37 | * 38 | ``` 39 | -------------------------------------------------------------------------------- /docs/development/running-cx-server-in-development.md: -------------------------------------------------------------------------------- 1 | # Cx Server Development Guide 2 | 3 | The Cx Server script was moved into the companion Docker image, the remaining script is only a wrapper to invoke Docker. 4 | This allows us to make the Cx Server work on Windows easily. 5 | 6 | This has a few consequences for developing the script, which are described in this document. 7 | 8 | When you make changes to `cx-server-companion/cx-server-companion.sh`, you need to build the `ppiper/cx-server-companion` image locally. 9 | From this directory (`jenkins-master/cx-server`), the command to do so is: 10 | 11 | ```bash 12 | docker build [--build-arg cx_server_base_uri=https://github.some.domain/raw/path/to/cx-server] -t ppiper/cx-server-companion ../../cx-server-companion 13 | ``` 14 | 15 | The build argument `cx_server_base_uri` is optional and only required if you don't want to use the `cx-server` version from GitHub.com. 16 | 17 | When you make changes to `ppiper/jenkins-master`, you also need to build the image locally. 18 | The important part is that you tag the image after building it. 19 | Assuming you changed an image, configured the `docker_registry` and `image_name` in the `server.cfg`, then you have to tag your image locally with `docker_registry/image_name`. 20 | 21 | Usually, when running `cx-server`, the companion and Jenkins images are automatically pulled from Docker Hub. 22 | This is designed for simple usage, but if you've built the images yourself, it will overwrite your changes. 23 | To prevent this, set the environment variable `DEVELOPER_MODE` to _any_ value. 24 | 25 | Run in developer mode on Bash 26 | ```bash 27 | export DEVELOPER_MODE=1 28 | ./cx-server [command] 29 | ``` 30 | 31 | Run in developer mode in `cmd.exe` 32 | ``` 33 | set DEVELOPER_MODE=1 34 | cx-server.bat [command] 35 | ``` 36 | 37 | Run in developer mode in Powershell 38 | ``` 39 | $env:DEVELOPER_MODE = 1 40 | cx-server.bat [command] 41 | ``` 42 | 43 | To switch back to the "normal" behavior, unset the environment variable `DEVELOPER_MODE`. 44 | 45 | Also __please note__ that the `cx-server` script always worked with the assumption that it was invoked from its directory, which is still the case. 46 | 47 | For more convenient usage of `cx-server`, you can source the `cx-server-completion.bash` script, which makes the sub-commands of `cx-server` known. 48 | -------------------------------------------------------------------------------- /docs/operations/cx-server-operations-guide.md: -------------------------------------------------------------------------------- 1 | ## Operations Guide for Cx Server 2 | 3 | This guide describes life-cycle management of the Cx Server for Continuous Integration and Delivery. The server is controlled with the `cx-server` script. 4 | 5 | #### Introduction 6 | 7 | The `cx-server` and the `server.cfg` files will help to manage the complete lifecycle of Jenkins server. You can generated these file by using the below docker command. 8 | 9 | ```bash 10 | docker run -it --rm -u $(id -u):$(id -g) -v "${PWD}":/cx-server/mount/ ppiper/cx-server-companion:latest init-cx-server 11 | ``` 12 | 13 | For the convenient usage of the script, a [completion script](https://raw.githubusercontent.com/SAP/devops-docker-cx-server/master/cx-server-companion/life-cycle-scripts/cx-server-completion.bash) for `cx-server` is provided. 14 | Source it in your shell, or refer to the documentation of your operating system for information on how to install this script system wide. 15 | 16 | #### System requirement 17 | 18 | ##### Productive usage 19 | 20 | In order to setup the cx-server for the productive purpose, we recommend the minimum hardware and software requirements as mentioned below. 21 | As of now, we do not support the productive setup on a Windows operating system. 22 | 23 | | Property | Recommendation | 24 | | --- | --- | 25 | | Operating System | `Ubuntu 16.04.4 LTS` | 26 | | Docker | `18.06.1-ce` | 27 | | Memory | `4GB` reserved for docker | 28 | | Available Disk Space | `4GB` | 29 | 30 | ##### Development usage 31 | 32 | The `cx-server` can also run on a Windows or MacOs. But, only for the development purposes. 33 | In order to run the `cx-server` on Windows, you need to share the `C` drive with a docker demon as explained [here](https://docs.docker.com/docker-for-windows/#shared-drives). 34 | Set the docker memory to at least 4GB, you can [configure](https://docs.docker.com/docker-for-windows/#advanced) this under the `Advanced` settings tab. 35 | 36 | #### Configuring the `cx-server` 37 | 38 | The `cx-server` can be customized to fit your use case. The `server.cfg` file contains the configuration details of your `cx-server`. 39 | 40 | | Property | Mandatory | Default Value | Description | 41 | | --- | --- | --- | --- | 42 | |`docker_image` | X | `ppiper/jenkins-master:latest`| Jenkins docker image name with the version to be used| 43 | |`docker_registry` | | Default docker registry used by the docker demon on the host machine | Docker registry to be used to pull docker images from| 44 | |`jenkins_home`| X|`jenkins_home_volume`| The volume to be used as a `jenkins_home`. Please ensure, this volume is accessible by the user with id `1000` | 45 | |`http_port`| X (If `tls_enabled` is `false`) |`80`| The HTTP port on which the server listens for incoming connections.| 46 | |`tls_enabled`| |`false`| Use Transport Layer Security encryption for additional security. The `jenkins.key` and `jenkins.crt` files are expected in a directory named `tls` inside the `cx-server` directory.| 47 | |`https_port`| X (If `tls_enabled` is `true`)|`443`| The HTTPS port on which the server listens for incoming connections.| 48 | |`http_proxy`| | | Effective value of `http_proxy` environment variable wich is automatically passed on to all containers in the CI/CD setup. The Java proxy configuration of Jenkins and the download cache are automatically derived from this value. Proxy authentication is supported by the syntax `http://username:password@myproxy.corp:8080`. | 49 | |`https_proxy`| | | Same as `http_proxy` but for https URLs. Jenkins only supports one proxy URL. Therefore, if `https_proxy` and `http_proxy` are defined, the URL of `https_proxy` takes precedence for initializing the Jenkins proxy settings. | 50 | |`no_proxy`| | | Whitelisting of hosts from the proxy. It will be appended to any previous definition of `no_proxy`| 51 | |`backup_directory`| | `(pwd)/backup` | Directory where the backup of the jenkins home directory contents are stored| 52 | |`backup_file_name`| |`jenkins_home_YYYY-MM-DDThhmmUTC.tar.gz`| Name of the backup file to be created| 53 | |`x_java_opts`| | | Additional `JAVA_OPTS` that need to be passed to the Jenkins container| 54 | |`cache_enabled`| |`true` | Flag to enable or disable the caching mechanism for `npm` and `maven` dependencies| 55 | |`mvn_repository_url`| | Maven central repository URL| It will be used if you need to configure a custom maven repository| 56 | |`npm_registry_url`| | Central NPM registry| It will be used if you need to configure a custom npm registry| 57 | |`x_nexus_java_opts`| | | You can configure the JAVA_OPTS of the download cache server using this option| 58 | 59 | #### Life-cycle of `cx-server` 60 | 61 | ##### start 62 | 63 | You can start the Jenkins server by launching the `start` command. 64 | 65 | ```bash 66 | ./cx-server start 67 | ``` 68 | 69 | When launched, it checks if the Docker container named `cx-jenkins-master` already exists. 70 | If yes, it restarts the stopped container. Otherwise, it spawns a new Docker container based on the configuration in `server.cfg`. 71 | 72 | Please make sure to secure your Jenkins after starting. 73 | By default Jenkins already comes with a default user admin. 74 | You can get the initial password by running the command `./cx-server initial-credentials`. 75 | Further details can be found [here](../../cx-server-companion/life-cycle-scripts/CX-SERVER-SECURITY.md). 76 | 77 | ##### status 78 | 79 | The status command provides basic overview about your Cx Server instance. 80 | 81 | ```bash 82 | ./cx-server status 83 | ``` 84 | 85 | ##### stop 86 | 87 | The Cx Server can be stopped with the `stop` command. 88 | 89 | ```bash 90 | ./cx-server stop 91 | ``` 92 | This stops the Jenkins Docker container if it is running. 93 | A subsequent `start` command restores the container. 94 | 95 | ##### remove 96 | 97 | This command removes the Jenkins container from the host if it is not running. 98 | 99 | ```bash 100 | ./cx-server remove 101 | ``` 102 | 103 | ##### backup 104 | 105 | The `jenkins_home` contains the state of the Jenkins which includes important details such as settings, Jenkins workspace, and job details. 106 | Considering the importance of it, taking regular backup of the `jenkins_home` is **highly recommended**. 107 | 108 | ```bash 109 | ./cx-server backup 110 | ``` 111 | 112 | This command creates a backup file and stores it on a host machine inside a directory named `backup`. In order to store the backup on external storage, you can customize the location and name of the backup file in the `server.cfg`. 113 | 114 | > **Note:** Administrator of the Jenkins must ensure that the backup is stored in a safe storage. 115 | 116 | ##### restore 117 | 118 | In an event of a server crash, the state of the Jenkins can be restored to a previously saved state if there is a backup file available. You need to execute the `restore` command along with the name of the backup file that you want to restore the state to. 119 | 120 | Example: 121 | 122 | ```bash 123 | ./cx-server restore jenkins_home_2018-03-07T1528UTC.tar.gz 124 | ``` 125 | 126 | > **Warning:** In order to restore the Jenkins home directory, this command stops the Jenkins server first and **delete the content of the Jenkins home directory**. 127 | > After the completion of the restore operation, it starts the Jenkins server upon user confirmation. 128 | 129 | ##### update script 130 | 131 | The `cx-server` script can be updated via the `update script` command, if a new version is available. 132 | ```bash 133 | ./cx-server update script 134 | ``` 135 | 136 | ##### update image 137 | 138 | By default, the Cx Server image defined by `docker_image` in `server.cfg` always points to the latest version, which is rebuilt with each change in the GitHub repository. 139 | 140 | In productive environments, you will however likely want to fix the Cx Server image to a specific version. 141 | By defining `docker_image` with a version tag (e.g. `docker_image=ppiper/jenkins-master:v3`), you avoid unintended updates as a side-effect of restarting the Continuous Delivery server. 142 | 143 | However, this introduces the risk of getting stuck on an outdated version. Therefore, if you are using an outdated Cx Server version, the `cx-server` script will warn you and recommend to run the `cx-server update image` command. 144 | 145 | The `cx-server update image` command updates the Cx Server to the newest available version. 146 | If `v6` is the newest released version, running an update with `docker_image=ppiper/jenkins-master:v3` will update the configuration to `docker_image=ppiper/jenkins-master:v6`. 147 | 148 | For this, it executes the following sequence of steps: 149 | 150 | * Stop potentially running Cx Server instance 151 | * Perform full backup of home directory 152 | * Update `docker_image` value in `server.cfg` to newest version 153 | * Start Cx Server 154 | 155 | Note: The command only works if you use the default image from Docker Hub. 156 | 157 | ```bash 158 | ./cx-server update image 159 | ``` 160 | 161 | ###### Performing a manual update 162 | 163 | In case you cannot use images from Docker Hub, the steps to do the same manually are: 164 | 165 | ```bash 166 | ./cx-server stop 167 | ./cx-server remove 168 | Manually update `docker_image` value in `server.cfg` to newest version, or leave it as `latest` 169 | ./cx-server backup 170 | ./cx-server start 171 | ``` 172 | 173 | #### Caching mechanism 174 | 175 | The `cx-server` provides the local cache for maven and node dependencies. This is enabled by default. A Docker image of [Sonatype Nexus OSS 3.x](https://www.sonatype.com/download-oss-sonatype) is used for this. 176 | 177 | By default the caching service makes use of maven central and npm registries for downloading the dependencies. This can be customized in `server.cfg`. 178 | 179 | In a distributed build environment, the Nexus server is started on each agent. 180 | The agent initializer script `launch-jenkins-agent.sh` takes care of the automatic start of the caching server. 181 | However, when the agent is disconnected, download cache service **will NOT be stopped** automatically. 182 | It is the responsibility of an admin to stop the Nexus service. 183 | This can be achieved by stopping the Docker image on the agent server. 184 | 185 | Example: 186 | 187 | ```bash 188 | ssh my-user@agent-server 189 | docker stop cx-nexus 190 | docker network remove cx-network 191 | ``` 192 | 193 | If you prefer to use different caching mechanism or not using any, you can disable the caching mechanism in the `server.cfg`. 194 | 195 | ```bash 196 | cache_enabled=false 197 | ``` 198 | 199 | #### TLS encryption 200 | 201 | The `cx-server` can be configured to use the TLS certificate for additional security. 202 | In order to enable this, set the `tls_enabled` flag to true in the `server.cfg`. 203 | It is also important to provide the certificates and a private key to `cx-server`. 204 | Store the certificate and private key(RSA) files in the `cx-server` directory. 205 | [Here](self-signed-tls.md) you can find a guide to create your self-signed certificate. 206 | Please note that currently the TLS encryption is not supported for the Windows environment. 207 | 208 | Example: 209 | 210 | ```bash 211 | tls_enabled=true 212 | https_port="443" 213 | ``` 214 | 215 | >**Note:** If you are enabling the TLS for already existing `cx-server`, then please remove the old container so that the new changes can take effect. 216 | You can do it by executing below commands. 217 | ```bash 218 | ./cx-server stop 219 | ./cx-server remove 220 | ./cx-server start 221 | ``` 222 | 223 | #### Plugins 224 | 225 | All the plugins that are required to run the SAP Cloud SDK Continuous Delivery Pipeline and the Piper steps are already pre-installed. 226 | If you update or downgrade them to a specific version, it will be lost every time the `cx-server` image is updated. 227 | All the plugins are updated with the latest version. 228 | If there is a need, the user can install additional plugins and configure them. 229 | However, the `cx-server update` will not update the plugins that are custom installed. 230 | 231 | #### Troubleshooting 232 | 233 | ##### Disk space cleanup 234 | 235 | If you encounter an issue related to disk space on a cx-server, you can free up space by launching [system prune](https://docs.docker.com/engine/reference/commandline/system_prune/) command. 236 | 237 | ***WARNING*** 238 | Do not launch this command when cx-server is not running. Because the command will remove all the containers that are stopped. In addition, it also removes the cache and docker images that are not used anymore. 239 | 240 | ```bash 241 | docker system prune --all 242 | ``` 243 | 244 | ##### Logs 245 | 246 | You can find the logs of the `cx-server` and the caching server as part of the Docker logs on the host machine. 247 | 248 | ```bash 249 | docker logs cx-jenkins-master 250 | docker logs cx-nexus 251 | ``` 252 | -------------------------------------------------------------------------------- /docs/operations/self-signed-tls.md: -------------------------------------------------------------------------------- 1 | ### TLS Support in `cx-server` 2 | 3 | The TLS protocols provide an encryption of a traffic exchange in the transport layer. 4 | The `cx-server` can be set up to use this encryption for additional security measures. 5 | In order to enable TLS in your cx-server, you need a certificate either self-signed or issued by a CA along with an RSA private key which was used to generate the certificate signing request. 6 | In the following guide, you will learn how to generate a self-signed certificate. 7 | 8 | ##### Install OpenSSL 9 | OpenSSL is a general purpose cryptography library which we will use in the following steps to generate the self-signed certificate. 10 | Please download and install it from the [downloads](https://www.openssl.org/source/) page. For Windows binaries, please check this [wiki](https://wiki.openssl.org/index.php/Binaries) page. 11 | In order to ensure that OpenSSL is successfully installed, execute the below command and verify. 12 | 13 | ``` 14 | $ openssl version 15 | OpenSSL 1.0.2g 1 Mar 2016 16 | ``` 17 | 18 | ##### Generate private key 19 | The first step is to create your RSA private key. 20 | ``` 21 | $ openssl genrsa -out jenkins.key 2048 22 | Generating RSA private key, 2048 bit long modulus 23 | ......................+++ 24 | ..........................................+++ 25 | e is 65537 (0x10001) 26 | ``` 27 | ##### Generate Certificate Signing Request [(CSR)](https://en.wikipedia.org/wiki/Certificate_signing_request) 28 | Once you have the private key, the next step is to create the CSR. Enter the below command to generate a CSR. 29 | ``` 30 | openssl req -new -key jenkins.key -out jenkins.csr 31 | ``` 32 | You will be prompted to provide multiple pieces of information regarding the certificate that you will be creating. 33 | 34 | ``` 35 | You are about to be asked to enter information that will be incorporated 36 | into your certificate request. 37 | What you are about to enter is what is called a Distinguished Name or a DN. 38 | There are quite a few fields but you can leave some blank 39 | For some fields there will be a default value, 40 | If you enter '.', the field will be left blank. 41 | ----- 42 | Country Name (2 letter code) [AU]:DE 43 | State or Province Name (full name) [Some-State]:Berlin 44 | Locality Name (eg, city) []:Berlin 45 | Organization Name (eg, company) [Internet Widgits Pty Ltd]:Example pvt ltd 46 | Organizational Unit Name (eg, section) []:myUnit 47 | Common Name (e.g. server FQDN or YOUR name) []:jenkins.example.com 48 | Email Address []:my.email@example.com 49 | 50 | Please enter the following 'extra' attributes 51 | to be sent with your certificate request 52 | A challenge password []:. 53 | An optional company name []: 54 | ``` 55 | Once you enter all the details, a `jenkins.csr` file will be created. 56 | 57 | ##### Generating a Self-Signed Certificate 58 | You can now create a self-signed certificate using the CSR. Run the command given below. This will generate a certificate `jenkins.crt` with a validity of 1 year. 59 | 60 | ``` 61 | $ openssl x509 -req -days 365 -in jenkins.csr -signkey jenkins.key -out jenkins.crt 62 | Signature ok 63 | subject=/C=DE/ST=Berlin/L=Berlin/O=Example pvt Ltd/OU=myOrgUnit/CN=jenkins.example.com/emailAddress=my_email@example.com 64 | Getting Private key 65 | ``` 66 | -------------------------------------------------------------------------------- /infrastructure-tests/Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | import com.sap.piper.testutils.JenkinsController 3 | 4 | @Library(['jenkins-library-test']) _ 5 | 6 | timeout(time: 5, unit: 'HOURS') { 7 | 8 | node("master") { 9 | stage('Init') { 10 | deleteDir() 11 | checkout scm 12 | if (!env.HOST) { 13 | error "Violated assumption: Environment variable env.HOST is not set. This must be set as the hostname." 14 | } 15 | } 16 | 17 | JenkinsController jenkins 18 | 19 | stage('Start Jenkins') { 20 | jenkins = new JenkinsController(this, "http://${env.HOST}") 21 | jenkins.waitForJenkinsStarted() 22 | } 23 | 24 | stage('Trigger Job') { 25 | jenkins.buildJob('validation-job') 26 | } 27 | 28 | stage('Wait for successful build') { 29 | jenkins.waitForSuccess('validation-job', 'validate-cx-server') 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /infrastructure-tests/README.md: -------------------------------------------------------------------------------- 1 | # Infrastructure testing 2 | 3 | This directory contains testing code for the infrastructure that is defined in the Dockerfiles. 4 | 5 | ## What it does 6 | 7 | * Build the images from source 8 | * Boot a Jenkins master using the life-cycle management script, with the built images 9 | * Create and trigger a Jenkins job, wait for it to be green 10 | 11 | ## Running as a Service 12 | 13 | See `.github/workflows/tests.yml` file for configuration. 14 | 15 | ## Running locally 16 | 17 | Docker is required, and at least 4 GB of memory assigned to Docker. 18 | 19 | ```bash 20 | ./runTests.sh 21 | ``` 22 | -------------------------------------------------------------------------------- /infrastructure-tests/jenkins.yml: -------------------------------------------------------------------------------- 1 | # Configuration for the Build-orchestrator Jenkins 2 | unclassified: 3 | globallibraries: 4 | libraries: 5 | - defaultVersion: "master" 6 | name: "jenkins-library-test" 7 | retriever: 8 | modernSCM: 9 | scm: 10 | git: 11 | remote: "https://github.com/SAP/jenkins-library-test.git" 12 | -------------------------------------------------------------------------------- /infrastructure-tests/pipeline_config.yml: -------------------------------------------------------------------------------- 1 | general: 2 | productiveBranch: master 3 | -------------------------------------------------------------------------------- /infrastructure-tests/runTests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | # Output docker version in case any api incompatibilies come up 4 | docker version 5 | 6 | # Start a local registry, to which we push the images built in this test, and from which they will be consumed in the test 7 | docker run -d -p 5000:5000 --restart always --name registry registry:2 || true 8 | find ../cx-server-companion -type f -exec sed -i -e 's/ppiper/localhost:5000\/ppiper/g' {} \; 9 | 10 | # Copy over life cycle script for testing 11 | cp ../cx-server-companion/life-cycle-scripts/{cx-server,server.cfg} . 12 | mkdir -p jenkins-configuration 13 | cp testing-jenkins.yml jenkins-configuration 14 | 15 | docker build -t localhost:5000/ppiper/jenkins-master:latest ../jenkins-master 16 | docker build -t localhost:5000/ppiper/cx-server-companion:latest ../cx-server-companion 17 | 18 | docker tag localhost:5000/ppiper/jenkins-master:latest ppiper/jenkins-master:latest 19 | docker tag localhost:5000/ppiper/cx-server-companion:latest ppiper/cx-server-companion:latest 20 | 21 | docker push localhost:5000/ppiper/jenkins-master:latest 22 | docker push localhost:5000/ppiper/cx-server-companion:latest 23 | 24 | # Boot our unit-under-test Jenkins master instance using the `cx-server` script 25 | TEST_ENVIRONMENT=(CX_INFRA_IT_CF_USERNAME CX_INFRA_IT_CF_PASSWORD) 26 | for var in "${TEST_ENVIRONMENT[@]}" 27 | do 28 | export $var 29 | echo $var >> custom-environment.list 30 | done 31 | chmod +x cx-server 32 | ./cx-server start 33 | 34 | # Use Jenkinsfile runner to orchastrate the example project build. 35 | # See `Jenkinsfile` in this directory for details on what is happening. 36 | docker run -v //var/run/docker.sock:/var/run/docker.sock -v $(pwd):/workspace \ 37 | -e CASC_JENKINS_CONFIG=/workspace/jenkins.yml -e HOST=$(hostname) \ 38 | ppiper/jenkinsfile-runner:v2 39 | 40 | # Test Jenkins default admin credentials 41 | INITIAL_CREDENTIALS=$(docker logs cx-jenkins-master 2>&1 | grep "Default credentials for Jenkins") 42 | ADMIN_PASSWORD=$(echo ${INITIAL_CREDENTIALS} | cut -c 63-) 43 | # File where web session cookie is saved 44 | COOKIEJAR="$(mktemp)" 45 | CRUMB=$(curl -u "admin:$ADMIN_PASSWORD" --cookie-jar "$COOKIEJAR" "http://localhost/crumbIssuer/api/xml?xpath=concat(//crumbRequestField,%22:%22,//crumb)") 46 | 47 | # Expect code 403 Forbidden because we are not authorized 48 | ACTUAL_STATUS_CODE_WITHOUT_AUTH=$(curl -s -o /dev/null --cookie "$COOKIEJAR" -H "$CRUMB" -w "%{http_code}" -X POST http://localhost/createItem) 49 | if [ "$ACTUAL_STATUS_CODE_WITHOUT_AUTH" != "403" ]; then 50 | echo Expected status 403, but got $ACTUAL_STATUS_CODE_WITHOUT_AUTH 51 | exit 122 52 | fi 53 | # Expect code 400 Bad Request because we are authorized but don't pass the required parameters 54 | ACTUAL_STATUS_CODE_WITH_AUTH=$(curl --user admin:$ADMIN_PASSWORD -X POST -s -o /dev/null --cookie "$COOKIEJAR" -H "$CRUMB" -w "%{http_code}" http://localhost/createItem) 55 | if [ "$ACTUAL_STATUS_CODE_WITH_AUTH" != "400" ]; then 56 | echo Expected status 400, but got $ACTUAL_STATUS_CODE_WITH_AUTH 57 | exit 123 58 | fi 59 | 60 | JENKINS_USERNAME=admin JENKINS_PASSWORD=$ADMIN_PASSWORD ./cx-server stop 61 | ./cx-server remove 62 | 63 | # cleanup 64 | if [ ! "$GITHUB_ACTIONS" = true ] ; then 65 | rm -f cx-server server.cfg custom-environment.list 66 | echo "Modified your git repo, you might want to do a git checkout before re-running." 67 | fi 68 | -------------------------------------------------------------------------------- /infrastructure-tests/testing-jenkins.yml: -------------------------------------------------------------------------------- 1 | jenkins: 2 | numExecutors: 10 3 | jobs: 4 | - script: > 5 | multibranchPipelineJob('validation-job') { 6 | branchSources { 7 | git { 8 | id('validation-job') 9 | remote("https://github.com/piper-validation/cloud-s4-sdk-book") 10 | includes('validate-cx-server') 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /jenkins-agent-k8s/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jenkins/jnlp-slave:3.27-1-alpine 2 | USER root 3 | 4 | RUN apk add --no-cache curl 5 | 6 | # add group & user 7 | RUN adduser -D -h /home/piper -u 1000 piper -s /bin/bash 8 | 9 | USER piper 10 | WORKDIR /home/piper 11 | -------------------------------------------------------------------------------- /jenkins-agent-k8s/Readme.md: -------------------------------------------------------------------------------- 1 | # Jenkins agent k8s 2 | 3 | This is a docker version of Jenkins JNLP agent which is an extension to [jenkinsci/jnlp-slave](https://hub.docker.com/r/jenkins/jnlp-slave/). 4 | 5 | The `ppiper/jenkins-master` runs with a user id `1000`. 6 | Hence, a user `piper` with uid `1000` has been added to the JNLP agent. This will avoid the possible issue with the access rights of the files that needs to be accessed by both master and the agent. 7 | 8 | This image is intended to be used in Jenkins pipelines. 9 | 10 | ## Download 11 | 12 | This image is published to Docker Hub and can be pulled via the command 13 | 14 | ``` 15 | docker pull ppiper/jenkins-agent-k8s 16 | ``` 17 | 18 | ## Build 19 | 20 | To build this image locally, open a terminal in the directory of the Dockerfile and run 21 | 22 | ``` 23 | docker build -t ppiper/jenkins-agent-k8s . 24 | ``` 25 | 26 | ## Usage 27 | 28 | Recommended usage of this image is via [`dockerExecuteOnKubernetes`](https://sap.github.io/jenkins-library/steps/dockerExecuteOnKubernetes/) pipeline step. 29 | 30 | ## License 31 | 32 | Copyright (c) 2018 SAP SE or an SAP affiliate company. All rights reserved. 33 | This file is licensed under the Apache Software License, v. 2 except as noted 34 | otherwise in the [LICENSE file](https://github.com/SAP/devops-docker-cx-server/blob/master/LICENSE). 35 | 36 | Please note that Docker images can contain other software which may be licensed under different licenses. This License file is also included in the Docker image. For any usage of built Docker images please make sure to check the licenses of the artifacts contained in the images. 37 | -------------------------------------------------------------------------------- /jenkins-agent/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8-jre-slim 2 | 3 | ENV JENKINS_HOME /var/jenkins_home 4 | ENV MAVEN_VERSION 3.6.3 5 | 6 | # https://github.com/hadolint/hadolint/wiki/DL4006 7 | SHELL ["/bin/bash", "-o", "pipefail", "-c"] 8 | 9 | RUN apt-get update && \ 10 | apt-get install -y --no-install-recommends \ 11 | git \ 12 | openssh-client \ 13 | apt-transport-https \ 14 | ca-certificates \ 15 | curl \ 16 | gnupg2 \ 17 | software-properties-common && \ 18 | curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - && \ 19 | apt-key fingerprint 0EBFCD88 && \ 20 | add-apt-repository \ 21 | "deb [arch=amd64] https://download.docker.com/linux/debian \ 22 | $(lsb_release -cs) \ 23 | stable" && \ 24 | apt-get update && \ 25 | apt-get -y --no-install-recommends install docker-ce && \ 26 | rm -rf /var/lib/apt/lists/* /var/cache/apt/* && \ 27 | mkdir -p /usr/share/maven && pushd /usr/share/maven && \ 28 | curl https://apache.lauf-forum.at/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz | tar xz --strip-components=1 && \ 29 | popd && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn 30 | 31 | # add group & user 32 | RUN addgroup -gid 1000 jenkins && \ 33 | useradd jenkins -d "$JENKINS_HOME" --uid 1000 --gid 1000 --shell /bin/bash --create-home 34 | 35 | VOLUME /var/jenkins_home 36 | 37 | USER jenkins 38 | -------------------------------------------------------------------------------- /jenkins-agent/Readme.md: -------------------------------------------------------------------------------- 1 | # Jenkins agent 2 | 3 | Jenkins agent image for use with `ppiper/jenkins-master`. 4 | 5 | This image is intended to be used in Jenkins pipelines. 6 | 7 | ## Download 8 | 9 | This image is published to Docker Hub and can be pulled via the command 10 | 11 | ``` 12 | docker pull ppiper/jenkins-agent 13 | ``` 14 | 15 | ## Build 16 | 17 | To build this image locally, open a terminal in the directory of the Dockerfile and run 18 | 19 | ``` 20 | docker build -t ppiper/jenkins-agent . 21 | ``` 22 | 23 | ## Usage 24 | 25 | When adding a new node to Jenkins, use the `./var/jenkins_home/launch-jenkins-agent.sh [sshuser] [sshhost]` script as _Launch command_. 26 | 27 | ## License 28 | 29 | Copyright (c) 2018 SAP SE or an SAP affiliate company. All rights reserved. 30 | This file is licensed under the Apache Software License, v. 2 except as noted 31 | otherwise in the [LICENSE file](https://github.com/SAP/devops-docker-cx-server/blob/master/LICENSE). 32 | 33 | Please note that Docker images can contain other software which may be licensed under different licenses. This License file is also included in the Docker image. For any usage of built Docker images please make sure to check the licenses of the artifacts contained in the images. 34 | -------------------------------------------------------------------------------- /jenkins-master/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jenkins/jenkins:lts-slim 2 | 3 | ENV MAVEN_VERSION 3.6.3 4 | 5 | USER root 6 | 7 | COPY init_jenkins.groovy /usr/share/jenkins/ref/init.groovy.d/init_jenkins.groovy.override 8 | COPY plugins.txt /usr/share/jenkins/ref/plugins.txt 9 | COPY launch-jenkins-agent.sh /usr/share/jenkins/ref/launch-jenkins-agent.sh.override 10 | 11 | # https://github.com/hadolint/hadolint/wiki/DL4006 12 | SHELL ["/bin/bash", "-o", "pipefail", "-c"] 13 | 14 | RUN apt-get update && \ 15 | apt-get install -y --no-install-recommends \ 16 | apt-transport-https \ 17 | ca-certificates \ 18 | curl \ 19 | gnupg2 \ 20 | software-properties-common && \ 21 | curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - && \ 22 | apt-key fingerprint 0EBFCD88 && \ 23 | add-apt-repository \ 24 | "deb [arch=amd64] https://download.docker.com/linux/debian \ 25 | $(lsb_release -cs) \ 26 | stable" && \ 27 | apt-get update && \ 28 | apt-get -y --no-install-recommends install docker-ce && \ 29 | rm -rf /var/lib/apt/lists/* /var/cache/apt/* && \ 30 | /usr/local/bin/install-plugins.sh < /usr/share/jenkins/ref/plugins.txt && \ 31 | chown -R jenkins /usr/share/jenkins/ref && \ 32 | echo 2.0 > /usr/share/jenkins/ref/jenkins.install.UpgradeWizard.state && \ 33 | mkdir -p /usr/share/maven && pushd /usr/share/maven && \ 34 | curl https://apache.lauf-forum.at/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz | tar xz --strip-components=1 && \ 35 | popd && ln -s /usr/share/maven/bin/mvn /usr/bin/mvn 36 | 37 | USER jenkins 38 | 39 | ENV JAVA_OPTS $JAVA_OPTS -Djenkins.install.runSetupWizard=false -Dhudson.model.DirectoryBrowserSupport.CSP= 40 | 41 | EXPOSE 8080 8443 42 | -------------------------------------------------------------------------------- /jenkins-master/README.md: -------------------------------------------------------------------------------- 1 | # Jenkins master 2 | 3 | Jenkins master image for use with project ["Piper"](https://github.com/SAP/jenkins-library). 4 | 5 | This image is intended to be used with the Cx Server life-cycle management. 6 | Please refer to the [Operations Guide for Cx Server](https://github.com/SAP/devops-docker-images/blob/master/docs/operations/cx-server-operations-guide.md) for detailed usage. 7 | 8 | ## Download 9 | 10 | This image is published to Docker Hub and can be pulled via the command 11 | 12 | ``` 13 | docker pull ppiper/jenkins-master 14 | ``` 15 | 16 | ## Build 17 | 18 | To build this image locally, open a terminal in the directory of the Dockerfile and run 19 | 20 | ``` 21 | docker build -t ppiper/jenkins-master . 22 | ``` 23 | 24 | ## Test 25 | 26 | This image has a very simple test case to check if Jenkins is able to boot with the given plugin configuration. 27 | The tests are run by DockerHub's "Autotest" feature. 28 | 29 | Run the following commands 30 | 31 | ```shell 32 | DOCKERFILE_PATH=Dockerfile IMAGE_NAME=ppiper/jenkins-master:that hooks/build 33 | IMAGE_NAME=ppiper/jenkins-master:that hooks/test 34 | ``` 35 | 36 | If the test passes, the exit code of the command should be `0`, and another value otherwise. 37 | 38 | ## License 39 | 40 | Copyright (c) 2018 SAP SE or an SAP affiliate company. All rights reserved. 41 | This file is licensed under the Apache Software License, v. 2 except as noted 42 | otherwise in the [LICENSE file](https://github.com/SAP/devops-docker-cx-server/blob/master/LICENSE). 43 | 44 | Please note that Docker images can contain other software which may be licensed under different licenses. This License file is also included in the Docker image. For any usage of built Docker images please make sure to check the licenses of the artifacts contained in the images. 45 | -------------------------------------------------------------------------------- /jenkins-master/init_jenkins.groovy: -------------------------------------------------------------------------------- 1 | import groovy.transform.Field 2 | 3 | import hudson.model.* 4 | import hudson.ProxyConfiguration 5 | import jenkins.model.* 6 | import jenkins.plugins.git.GitSCMSource 7 | import org.jenkinsci.plugins.workflow.libs.* 8 | import java.net.URLDecoder 9 | import java.util.logging.Logger 10 | import hudson.security.* 11 | import jenkins.install.InstallState 12 | 13 | @Field 14 | Jenkins instance = Jenkins.instance 15 | 16 | @Field 17 | Logger logger = Logger.getLogger("com.sap.piper.init") 18 | 19 | try { 20 | // Migrate old setups by deleting old init files 21 | // TODO: Remove after Q2 2019 and assert that files are deleted 22 | deleteOldInitFiles() 23 | 24 | initProxy() 25 | initExecutors() 26 | initLibraries() 27 | 28 | println("Value of `instance.isUseSecurity()`: ${instance.isUseSecurity()}") 29 | println("Value of `System.getenv()['DEVELOPER_MODE']`: ${System.getenv()['DEVELOPER_MODE']}") 30 | 31 | if (System.getenv()['DEVELOPER_MODE']) { 32 | println("Running in developer mode, no security is enforced.") 33 | } else if (!instance.isUseSecurity()) { 34 | initAdminUser() 35 | } 36 | } 37 | catch(Throwable t) { 38 | throw new Error("Failed to properly initialize Piper Jenkins. Please check the logs for more details.", t); 39 | } 40 | 41 | def initAdminUser() { 42 | // cf https://github.com/jenkinsci/docker/issues/310 43 | instance.setSecurityRealm(new HudsonPrivateSecurityRealm(false)) 44 | String username = 'admin' 45 | String password = java.util.UUID.randomUUID() 46 | def user = instance.getSecurityRealm().createAccount(username, password) 47 | // This println is API for the cx-server script and for the infra tests. Don't change. 48 | println(">>> Default credentials for Jenkins: Username ${username}, Password ${password}") 49 | user.save() 50 | def strategy = new FullControlOnceLoggedInAuthorizationStrategy() 51 | instance.setAuthorizationStrategy(strategy) 52 | instance.save() 53 | } 54 | 55 | def initExecutors(){ 56 | int numberOfExecutors = 6 57 | if(instance.getNumExecutors() < numberOfExecutors) { 58 | logger.info("Initializing ${numberOfExecutors} executors") 59 | instance.setNumExecutors(numberOfExecutors) 60 | } 61 | } 62 | 63 | def initLibraries(){ 64 | def env = System.getenv() 65 | 66 | String piperLibraryOsUrl = env.PIPER_LIBRARY_OS_URL ?: "https://github.com/SAP/jenkins-library.git" 67 | String piperLibraryOsBranch = env.PIPER_LIBRARY_OS_BRANCH ?: "master" 68 | createLibIfMissing("piper-lib-os", piperLibraryOsUrl, piperLibraryOsBranch) 69 | 70 | String s4sdkLibraryUrl = env.S4SDK_LIBRARY_URL ?: "https://github.com/SAP/cloud-s4-sdk-pipeline-lib.git" 71 | String s4sdkLibraryBranch = env.S4SDK_LIBRARY_BRANCH ?: "master" 72 | createLibIfMissing("s4sdk-pipeline-library", s4sdkLibraryUrl, s4sdkLibraryBranch) 73 | } 74 | 75 | def createLibIfMissing(String libName, String gitUrl, String defaultBranch) { 76 | GitSCMSource gitScmSource = new GitSCMSource(null, gitUrl, "", "origin", "+refs/heads/*:refs/remotes/origin/*", "*", "", true) 77 | LibraryConfiguration lib = new LibraryConfiguration(libName, new SCMSourceRetriever(gitScmSource)) 78 | lib.defaultVersion = defaultBranch 79 | lib.implicit = false 80 | lib.allowVersionOverride = true 81 | 82 | GlobalLibraries globalLibraries = GlobalLibraries.get() 83 | List libs = globalLibraries.getLibraries() 84 | 85 | boolean exists = false 86 | for (LibraryConfiguration libConfig : libs) { 87 | if (libConfig.getName() == libName) { 88 | exists = true 89 | break 90 | } 91 | } 92 | 93 | if (!exists) { 94 | logger.info("Initializing pipline library ${libName} (${gitUrl}:${defaultBranch})") 95 | List newLibs = new ArrayList(libs) 96 | newLibs.add(lib) 97 | globalLibraries.setLibraries(newLibs) 98 | } 99 | } 100 | 101 | def initProxy() { 102 | String httpProxyEnv = System.getenv("http_proxy") 103 | String httpsProxyEnv = System.getenv("https_proxy") 104 | String noProxyEnv = System.getenv("no_proxy") 105 | 106 | // delete potentially existing proxy.xml (we only use transient values derived from env vars) 107 | File proxyFile = new File(instance.getRootDir(), "proxy.xml") 108 | if(proxyFile.exists()) { 109 | logger.warning("Unexpected proxy.xml file detected. Trying to delete it...") 110 | deleteOrFail(proxyFile) 111 | } 112 | 113 | URL proxyUrl = null; 114 | if (httpsProxyEnv?.trim()) { 115 | proxyUrl = new URL(httpsProxyEnv) 116 | } 117 | 118 | // prefer https_proxy server over http_proxy 119 | if (!proxyUrl && httpProxyEnv?.trim()) { 120 | proxyUrl = new URL(httpProxyEnv) 121 | } 122 | 123 | String[] nonProxyHosts = null 124 | if ( noProxyEnv?.trim() && proxyUrl ) { 125 | /* 126 | Insert wildcards to have a rough conversion between the unix-like no-proxy list and the java notation. 127 | For example, `localhost,.corp,.maven.apache.org,x.y.,myhost` will be transformed to 128 | `[*localhost,*.corp,*.maven.apache.org,*x.y,*myhost]`. 129 | */ 130 | 131 | nonProxyHosts = noProxyEnv.split(',') 132 | .collect { it.replaceFirst('\\.$', '')} 133 | .collect { "*${it}" }; 134 | } 135 | 136 | if(proxyUrl) { 137 | Map credentials = extractCredentials(proxyUrl) 138 | String host = proxyUrl.getHost() 139 | int port = proxyUrl.getPort() 140 | 141 | logger.info("Setting Jenkins network proxy to ${host}:${port} ${!credentials ? "without" : ""} using credentials. No proxy patterns: ${nonProxyHosts}") 142 | instance.proxy = new ProxyConfiguration(host, port, credentials?.username, credentials?.password, nonProxyHosts.join("\n")) 143 | } 144 | else { 145 | logger.fine("No network proxy configured.") 146 | instance.proxy = null 147 | } 148 | } 149 | 150 | def extractCredentials(URL proxyURL) { 151 | def userInfo = proxyURL.getUserInfo() 152 | if(!userInfo) { 153 | return null 154 | } 155 | 156 | String[] splitted = userInfo.split(":") 157 | if(splitted.length != 2) { 158 | throw new Error("Failed to extract network proxy credentials. Expected format: 'http://myuser:mypass@myproxy.corp:8080'") 159 | } 160 | 161 | return [ username: URLDecoder.decode(splitted[0], "UTF-8"), password: URLDecoder.decode(splitted[1], "UTF-8") ] 162 | } 163 | 164 | def deleteOldInitFiles(){ 165 | List oldFiles= [ 166 | 'init_s4sdk_library.groovy', 167 | 'init_executors.groovy', 168 | 'init_proxy.groovy' 169 | ] 170 | 171 | String initDirectory = "${System.getenv("JENKINS_HOME")}/init.groovy.d/" 172 | for (String fileName: oldFiles){ 173 | File fileToDelete = new File(initDirectory, fileName) 174 | if(fileToDelete.exists()){ 175 | logger.warning("Found old init file ${fileToDelete}, trying to delete file.") 176 | deleteOrFail(fileToDelete) 177 | } 178 | } 179 | } 180 | 181 | def deleteOrFail(File fileToDelete) { 182 | boolean success = fileToDelete.delete() 183 | if(success) { 184 | logger.info("Successfully deleted ${fileToDelete}") 185 | } 186 | else { 187 | throw new Error("Failed to delete ${fileToDelete}") 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /jenkins-master/launch-jenkins-agent.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function log_error() 4 | { 5 | echo -e "\033[1;31m[Error]\033[0m $1" >&2 6 | } 7 | 8 | if [ $# -lt 2 ]; then 9 | echo "Please provide the following parameters: $0 USER HOST [DOCKER_IMAGE]" 10 | exit 1 11 | fi 12 | 13 | cd ~ 14 | 15 | SCP_COMMAND="scp -B -o StrictHostKeyChecking=no" 16 | SSH_COMMAND="ssh -x" 17 | 18 | SSH_USER=$1 19 | SSH_HOST=$2 20 | REMOTE_DIR=ppiper_jenkins_agent 21 | 22 | if [ -n "$3" ]; then 23 | DOCKER_IMAGE=$3 24 | else 25 | DOCKER_IMAGE='ppiper/jenkins-agent:latest' 26 | fi 27 | 28 | echo "Download slave.jar" 29 | curl http://localhost:8080/jnlpJars/slave.jar -o slave.jar 30 | 31 | echo "Create directory on agent: $REMOTE_DIR" 32 | $SSH_COMMAND $SSH_USER@$SSH_HOST "mkdir -p $REMOTE_DIR" /dev/null 51 | 52 | if [ -n "$http_proxy" ]; then 53 | PROXY_ENV=" -e http_proxy=$http_proxy" 54 | fi 55 | 56 | if [ -n "$https_proxy" ]; then 57 | PROXY_ENV="$PROXY_ENV -e https_proxy=$https_proxy" 58 | fi 59 | 60 | if [ -n "$no_proxy" ]; then 61 | PROXY_ENV="$PROXY_ENV -e no_proxy=$no_proxy" 62 | fi 63 | 64 | $SSH_COMMAND $SSH_USER@$SSH_HOST "mkdir -p $REMOTE_DIR/cx-server" /dev/null 69 | $SSH_COMMAND $SSH_USER@$SSH_HOST "docker run --rm --workdir /cx-server/mount --mount source=\$(pwd)/$REMOTE_DIR/cx-server,target=/cx-server/mount,type=bind --volume /var/run/docker.sock:/var/run/docker.sock $PROXY_ENV --env host_os=unix --env cx_server_path=\$(pwd)/$REMOTE_DIR/cx-server ppiper/cx-server-companion /cx-server/cx-server-companion.sh start_cache" println("${plugin.getShortName()}: ${plugin.getVersion()}") 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /jenkinsfile-runner/README.md: -------------------------------------------------------------------------------- 1 | # Jenkinsfile Runner 2 | 3 | Dockerfile for an image with [Jenkinsfile Runner](https://github.com/jenkinsci/jenkinsfile-runner) based on `ppiper/jenkins-master`. 4 | 5 | This allows to execute a `Jenkinsfile` from the command-line, thus without the need for a long-running, stateful Jenkins master instance. 6 | 7 | ## Download 8 | 9 | This image is published to Docker Hub and can be pulled via the command 10 | 11 | ``` 12 | docker pull ppiper/jenkinsfile-runner 13 | ``` 14 | 15 | ## Build 16 | 17 | To build this image locally, open a terminal in the directory of the Dockerfile an run 18 | 19 | ``` 20 | docker build -t ppiper/jenkinsfile-runner . 21 | ``` 22 | 23 | ## Usage 24 | 25 | Place a `Jenkinsfile` in the current directory, for example 26 | 27 | ``` 28 | node("master") { 29 | stage('Hello World') { 30 | sh 'echo Hello from Jenkins' 31 | } 32 | } 33 | ``` 34 | 35 | Now you can run it via this command (on Linux and macOS), where the current working directory is mounted to `/workspace`: 36 | 37 | ``` 38 | docker run -v $(pwd):/workspace ppiper/jenkinsfile-runner 39 | ``` 40 | 41 | You should see the message `Hello from Jenkins`, along with more log output. 42 | 43 | ## Test 44 | 45 | This image has a very simple test case. 46 | The tests are run by DockerHub's "Autotest" feature. 47 | 48 | To run them locally, you need [Docker Compose](https://docs.docker.com/compose/). 49 | 50 | Run the following commands 51 | 52 | ```shell 53 | docker-compose --file docker-compose.test.yml build 54 | docker-compose --file docker-compose.test.yml run sut 55 | ``` 56 | 57 | If the test passes, the exit code of the command should be `0` and you should see some log output ending in 58 | 59 | ``` 60 | [Pipeline] End of Pipeline 61 | Finished: SUCCESS 62 | ``` 63 | 64 | If the command exits with code `1`, the test failed. 65 | 66 | ## License 67 | 68 | Copyright (c) 2018 SAP SE or an SAP affiliate company. All rights reserved. 69 | This file is licensed under the Apache Software License, v. 2 except as noted 70 | otherwise in the [LICENSE file](https://github.com/SAP/devops-docker-cx-server/blob/master/LICENSE). 71 | 72 | Please note that Docker images can contain other software which may be licensed under different licenses. This License file is also included in the Docker image. For any usage of built Docker images please make sure to check the licenses of the artifacts contained in the images. 73 | 74 | This image contains [Jenkinsfile Runner](https://github.com/jenkinsci/jenkinsfile-runner), which is licensed under the [MIT license](https://github.com/jenkinsci/jenkinsfile-runner/blob/9f41f51b6dc320b9dd5c0fa6d81f179518597d37/pom.xml#L43). 75 | -------------------------------------------------------------------------------- /jenkinsfile-runner/docker-compose.test.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | sut: 4 | build: . 5 | -------------------------------------------------------------------------------- /project-piper-action-runtime/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG JVM_VERSION=11.0.7 2 | ARG MAVEN_VERSION=3.6.3 3 | ARG NODE_VERSION=v12.16.2 4 | 5 | FROM debian:buster-slim as builder 6 | 7 | ARG JVM_VERSION 8 | ARG MAVEN_VERSION 9 | ARG NODE_VERSION 10 | 11 | ADD https://github.com/SAP/SapMachine/releases/download/sapmachine-${JVM_VERSION}/sapmachine-jdk-${JVM_VERSION}_linux-x64_bin.tar.gz /jdk.tgz 12 | RUN tar xzf jdk.tgz -C /opt 13 | ADD http://apache.lauf-forum.at/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz /maven.tgz 14 | RUN tar xzf /maven.tgz -C /opt 15 | ADD https://nodejs.org/dist/${NODE_VERSION}/node-${NODE_VERSION}-linux-x64.tar.gz /node.tgz 16 | RUN tar xzf /node.tgz -C /opt 17 | 18 | FROM debian:buster-slim 19 | 20 | ARG JVM_VERSION 21 | ARG MAVEN_VERSION 22 | ARG NODE_VERSION 23 | 24 | # hadolint ignore=DL3008 25 | RUN apt-get -y update \ 26 | && apt-get -y dist-upgrade \ 27 | && apt-get -y --no-install-recommends install wget curl ca-certificates unzip \ 28 | && apt-get clean \ 29 | && rm -rf /var/lib/apt/lists/* \ 30 | && mkdir -p /home/actions && echo "actions:x:1000:1000:actions:/home/actions:/bin/bash" >> /etc/passwd \ 31 | && chown -R actions /home/actions \ 32 | && echo "PATH=/opt/apache-maven-$MAVEN_VERSION/bin:/opt/node-$NODE_VERSION-linux-x64/bin:/opt/sapmachine-jdk-$JVM_VERSION/bin:$PATH" >> /home/actions/.bashrc 33 | 34 | WORKDIR /home/actions 35 | 36 | ENV PATH=/opt/apache-maven-$MAVEN_VERSION/bin:/opt/node-$NODE_VERSION-linux-x64/bin:/opt/sapmachine-jdk-$JVM_VERSION/bin:$PATH 37 | ENV RUNNER_TEMP=/tmp 38 | 39 | COPY --from=builder /opt /opt 40 | -------------------------------------------------------------------------------- /project-piper-action-runtime/README.md: -------------------------------------------------------------------------------- 1 | # Project "Piper" GitHub Actions Runtime Environment 2 | 3 | Dockerfile for running the [Project "Piper" Action](https://github.com/SAP/project-piper-action) locally using [act](https://github.com/nektos/act) 4 | 5 | **NOTE:** This is a Proof of Concept, depending on what your GitHub Actions workflow does, the image will not work out of the box. 6 | The image tries to mirror [GitHub's hosted runner image](https://github.com/actions/virtual-environments/blob/master/images/linux/Ubuntu1804-README.md) to a very limited scope. 7 | The goal is to provide an environment capable of running workflows based on project "Piper", not more. 8 | It contains a user called `actions` with uid `1000`. 9 | Additonally, it includes a JDK, Maven and NodeJs. 10 | Extend the Dockerfile as you see fit and build your own image. 11 | 12 | ## Usage 13 | 14 | * Install Docker Desktop, if you don't have already 15 | * Install act from https://github.com/nektos/act 16 | * Build the image: `docker build -t myruntime .` 17 | * Run using: `act -P ubuntu-18.04=myruntime` in the root of your project with a project "Piper"-based workflow 18 | 19 | The version of the provided JDK, Maven and NodeJs may be modified using [build arguments](https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables---build-arg). 20 | As an example, using another NodeJs version can be done using `docker build --build-arg=NODE_VERSION=v13.0.0 -t myruntime .`. 21 | 22 | Available arguments: 23 | 24 | - `JVM_VERSION` 25 | - `MAVEN_VERSION` 26 | - `NODE_VERSION` 27 | 28 | ## License 29 | 30 | Copyright (c) 2018 SAP SE or an SAP affiliate company. All rights reserved. 31 | This file is licensed under the Apache Software License, v. 2 except as noted 32 | otherwise in the [LICENSE file](https://github.com/SAP/devops-docker-cx-server/blob/master/LICENSE). 33 | 34 | Please note that Docker images can contain other software which may be licensed under different licenses. This License file is also included in the Docker image. For any usage of built Docker images please make sure to check the licenses of the artifacts contained in the images. 35 | 36 | Amongst others, this image includes: 37 | 38 | - [SapMachine](https://sap.github.io/SapMachine/) 39 | - [Maven](http://maven.apache.org/) 40 | - [Node.js](https://nodejs.org/en/) 41 | --------------------------------------------------------------------------------