├── .gitignore ├── .gitlab-ci.yml ├── .travis.yml ├── GUIDELINES.md ├── LICENSE ├── README.md ├── Vagrantfile ├── doc ├── docs │ ├── config.md │ ├── config_env.md │ ├── config_example.md │ ├── config_lin.md │ ├── config_net.md │ ├── css │ │ └── codehilite.css │ ├── img │ │ └── sheep-logo.png │ ├── index.md │ ├── internals.md │ ├── launching.md │ ├── pxe-pilot.md │ ├── quickstart.md │ └── sheep-live.md └── mkdocs.yml ├── img └── sheep-logo.png ├── sheep └── tests ├── integration_test ├── centOS7_legacy_CID_winterfell.yml ├── centOS7_legacy_CIE_winterfell.yml ├── centOS7_uefi_CID_leopard.yml ├── debian9_legacy_CID_leopard.yml ├── debian9_legacy_CID_winterfell.yml ├── debian9_legacy_CIE_winterfell.yml ├── network.bats ├── openSUSE15_1_legacy_CID_winterfell.yml ├── openSUSE15_1_legacy_CIE_winterfell.yml ├── openSUSE15_1_uefi_CID_leopard.yml ├── run ├── shell.bats ├── system_test.yml ├── test_tool.bash ├── ubuntu16_04_legacy_CID_winterfell.yml ├── ubuntu16_04_legacy_CIE_winterfell.yml ├── ubuntu16_04_uefi_CID_leopard.yml ├── ubuntu16_04_uefi_leopard__boot_order_once.yml └── uname.bats └── unit_tests ├── check_filesystem_label.bats ├── config_userdata_ci_disable.bats ├── config_variable.bats ├── get_serial_device.bats ├── log.bats ├── run ├── search_kernel_parameter.bats ├── search_mandatory_value.bats ├── search_value.bats ├── sheepCfgConfigVariable1.yml ├── sheepCfgConfigVariable2.yml ├── sheepCfgConfigVariable3.yml ├── sheepCfgConfigVariable4.yml ├── sheepCfgConfigVariable5.yml ├── sheepCfgTestSearch_m_v1.yml ├── sheepCfgTestSearch_m_v2.yml ├── sheepCfgTestSearch_m_v3.yml ├── sheepCfgTestSearch_m_v4.yml ├── sheepCfgUnitTest.yml ├── sheepCfgUserDataCID1.yml ├── sheepCfgUserDataCID2.yml ├── sheepCfgUserDataCID3.yml ├── sheepCfgUserDataCID4.yml └── test_helper.bash /.gitignore: -------------------------------------------------------------------------------- 1 | tests/unit_tests/*.log 2 | .vscode/ 3 | .vagrant/ 4 | *.log 5 | doc/site 6 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: sesame-ubuntu-16.04:1.0 2 | 3 | variables: 4 | SHEEP_CI_RUNNER_IP: "17.18.99.1" 5 | SYS_TEST_FILE: "${CI_PROJECT_DIR}/tests/integration_test/system_test.yml" 6 | 7 | stages: 8 | - test-unit 9 | - code-style 10 | - test-integ 11 | 12 | unit-test: 13 | stage: test-unit 14 | script: 15 | - cd ${CI_PROJECT_DIR} 16 | - tests/unit_tests/run 17 | tags: 18 | - unit-tests 19 | 20 | code-formatting: 21 | stage: code-style 22 | script: 23 | - cd ${CI_PROJECT_DIR} 24 | - shfmt -sr -i 0 -d sheep tests/unit_tests/run tests/integration_test/run 25 | tags: 26 | - unit-tests 27 | 28 | ubuntu16_04_qcow2_uefi_boot: 29 | stage: test-integ 30 | variables: 31 | CONFIG_FILE_NAME: ubuntu16_04_uefi_CID_leopard.yml 32 | EXTRA_KERNEL_CMDLINE: console=ttyS1,57600n8 33 | script: 34 | - cd ${CI_PROJECT_DIR} 35 | - tests/integration_test/run 36 | artifacts: 37 | when: always 38 | paths: 39 | - "*.cast" 40 | tags: 41 | - integration 42 | 43 | centos7_qcow2_uefi_boot: 44 | stage: test-integ 45 | variables: 46 | CONFIG_FILE_NAME: centOS7_uefi_CID_leopard.yml 47 | EXTRA_KERNEL_CMDLINE: console=ttyS1,57600n8 48 | script: 49 | - cd ${CI_PROJECT_DIR} 50 | - tests/integration_test/run 51 | artifacts: 52 | when: always 53 | paths: 54 | - "*.cast" 55 | tags: 56 | - integration 57 | 58 | debian9_qcow2_uefi_boot: 59 | stage: test-integ 60 | variables: 61 | CONFIG_FILE_NAME: debian9_legacy_CID_leopard.yml 62 | EXTRA_KERNEL_CMDLINE: console=ttyS1,57600n8 63 | script: 64 | - cd ${CI_PROJECT_DIR} 65 | - tests/integration_test/run 66 | artifacts: 67 | when: always 68 | paths: 69 | - "*.cast" 70 | tags: 71 | - integration 72 | 73 | opensuse15_1_qcow2_uefi_boot: 74 | stage: test-integ 75 | variables: 76 | CONFIG_FILE_NAME: openSUSE15_1_uefi_CID_leopard.yml 77 | EXTRA_KERNEL_CMDLINE: console=ttyS1,57600n8 78 | script: 79 | - cd ${CI_PROJECT_DIR} 80 | - tests/integration_test/run 81 | artifacts: 82 | when: always 83 | paths: 84 | - "*.cast" 85 | tags: 86 | - integration 87 | 88 | ubuntu16_04_qcow2_legacy_boot: 89 | stage: test-integ 90 | variables: 91 | CONFIG_FILE_NAME: ubuntu16_04_legacy_CID_winterfell.yml 92 | EXTRA_KERNEL_CMDLINE: console=ttyS1,115200n8 iomem=relaxed ethdevice=enp12s0 module_blacklist=mei,mei_me sheep.log.level=DEBUG sheep.delay=10 93 | script: 94 | - cd ${CI_PROJECT_DIR} 95 | - tests/integration_test/run 96 | artifacts: 97 | when: always 98 | paths: 99 | - "*.cast" 100 | tags: 101 | - integration-legacy 102 | 103 | centos7_qcow2_legacy_boot: 104 | stage: test-integ 105 | variables: 106 | CONFIG_FILE_NAME: centOS7_legacy_CID_winterfell.yml 107 | EXTRA_KERNEL_CMDLINE: console=ttyS1,115200n8 iomem=relaxed ethdevice=enp12s0 module_blacklist=mei,mei_me 108 | script: 109 | - cd ${CI_PROJECT_DIR} 110 | - tests/integration_test/run 111 | artifacts: 112 | when: always 113 | paths: 114 | - "*.cast" 115 | tags: 116 | - integration-legacy 117 | 118 | debian9_qcow2_legacy_boot: 119 | stage: test-integ 120 | variables: 121 | CONFIG_FILE_NAME: debian9_legacy_CID_winterfell.yml 122 | EXTRA_KERNEL_CMDLINE: console=ttyS1,115200n8 iomem=relaxed ethdevice=enp12s0 module_blacklist=mei,mei_me 123 | script: 124 | - cd ${CI_PROJECT_DIR} 125 | - tests/integration_test/run 126 | artifacts: 127 | when: always 128 | paths: 129 | - "*.cast" 130 | tags: 131 | - integration-legacy 132 | 133 | opensuse15_1_qcow2_legacy_boot: 134 | stage: test-integ 135 | variables: 136 | CONFIG_FILE_NAME: openSUSE15_1_legacy_CID_winterfell.yml 137 | EXTRA_KERNEL_CMDLINE: console=ttyS1,115200n8 iomem=relaxed ethdevice=enp12s0 module_blacklist=mei,mei_me 138 | script: 139 | - cd ${CI_PROJECT_DIR} 140 | - tests/integration_test/run 141 | artifacts: 142 | when: always 143 | paths: 144 | - "*.cast" 145 | tags: 146 | - integration-legacy 147 | 148 | ubuntu16_04_qcow2_legacy_boot_CIE: 149 | stage: test-integ 150 | variables: 151 | CONFIG_FILE_NAME: ubuntu16_04_legacy_CIE_winterfell.yml 152 | EXTRA_KERNEL_CMDLINE: console=ttyS1,115200n8 iomem=relaxed ethdevice=enp12s0 module_blacklist=mei,mei_me 153 | script: 154 | - cd ${CI_PROJECT_DIR} 155 | - tests/integration_test/run 156 | artifacts: 157 | when: always 158 | paths: 159 | - "*.cast" 160 | tags: 161 | - integration-legacy 162 | 163 | centos7_qcow2_legacy_boot_CIE: 164 | stage: test-integ 165 | variables: 166 | CONFIG_FILE_NAME: centOS7_legacy_CIE_winterfell.yml 167 | EXTRA_KERNEL_CMDLINE: console=ttyS1,115200n8 iomem=relaxed ethdevice=enp12s0 module_blacklist=mei,mei_me 168 | script: 169 | - cd ${CI_PROJECT_DIR} 170 | - tests/integration_test/run 171 | artifacts: 172 | when: always 173 | paths: 174 | - "*.cast" 175 | tags: 176 | - integration-legacy 177 | 178 | debian9_qcow2_legacy_boot_CIE: 179 | stage: test-integ 180 | variables: 181 | CONFIG_FILE_NAME: debian9_legacy_CIE_winterfell.yml 182 | EXTRA_KERNEL_CMDLINE: console=ttyS1,115200n8 iomem=relaxed ethdevice=enp12s0 module_blacklist=mei,mei_me 183 | script: 184 | - cd ${CI_PROJECT_DIR} 185 | - tests/integration_test/run 186 | artifacts: 187 | when: always 188 | paths: 189 | - "*.cast" 190 | tags: 191 | - integration-legacy 192 | 193 | opensuse15_1_qcow2_legacy_boot_CIE: 194 | stage: test-integ 195 | variables: 196 | CONFIG_FILE_NAME: openSUSE15_1_legacy_CIE_winterfell.yml 197 | EXTRA_KERNEL_CMDLINE: console=ttyS1,115200n8 iomem=relaxed ethdevice=enp12s0 module_blacklist=mei,mei_me 198 | script: 199 | - cd ${CI_PROJECT_DIR} 200 | - tests/integration_test/run 201 | artifacts: 202 | when: always 203 | paths: 204 | - "*.cast" 205 | tags: 206 | - integration-legacy 207 | 208 | ubuntu16_04_uefi_leopard__boot_order_once: 209 | stage: test-integ 210 | variables: 211 | CONFIG_FILE_NAME: ubuntu16_04_uefi_leopard__boot_order_once.yml 212 | EXTRA_KERNEL_CMDLINE: console=ttyS1,57600n8 213 | script: 214 | - cd ${CI_PROJECT_DIR} 215 | - tests/integration_test/run 216 | artifacts: 217 | when: always 218 | paths: 219 | - "*.cast" 220 | tags: 221 | - integration 222 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | install: 3 | - sudo apt update 4 | - sudo apt install -y python3-pip jq 5 | - sudo pip3 install yq 6 | - git clone https://github.com/sstephenson/bats.git 7 | - cd bats 8 | - sudo ./install.sh /usr/local 9 | - cd .. 10 | - pip3 install mkdocs Pygments 11 | - wget -O $HOME/shfmt https://github.com/mvdan/sh/releases/download/v3.0.1/shfmt_v3.0.1_linux_amd64 12 | - chmod +x $HOME/shfmt 13 | 14 | script: 15 | - cd tests/unit_tests 16 | - ./run 17 | - cd $TRAVIS_BUILD_DIR 18 | - $HOME/shfmt -sr -i 0 -d sheep tests/unit_tests/run tests/integration_test/run 19 | 20 | after_success: 21 | - sudo apt install -y python-pip 22 | - sudo pip install mkdocs 23 | - cd $TRAVIS_BUILD_DIR/doc 24 | - mkdocs build 25 | - cp site/index.html site/index 26 | 27 | deploy: 28 | - provider: pages 29 | skip_cleanup: true 30 | github_token: $GITHUB_TOKEN 31 | keep_history: true 32 | local_dir: doc/site 33 | on: 34 | branch: master 35 | 36 | env: 37 | global: 38 | secure: eTrGBimGc5SZ58msRI5he3i09nAf/aeIO7GbIMjhbX0ixbm1I+glefhz3HAKQ1rLT7OIeLjskMGEAuxuUV8WzOc0myEYpL9eBCb4n6eX2i9/K3yfG29AZrOVhT3H1BV9KplrnTlLVjJPiMO4Mf79MOOgqBa3zz0xuTswMgh7/aoPnOArYnsjgHxaVpkmWw+T5LDrm7h+9XSAtcy3PFH6zrWqier/oTtoOeRBs7QiCWjTsct00YBiKEJXI7GHZ0/uMis6q+0SUXWr4+JkpUTji0GU1nq0gByd1CWs2qHZpWfnuR7ZgJwy0BiP9V0jZSQORrfhYvsXWhuJyKQ2jrhIKtDcCOWClYgEuEOCqQ74CTy4uSu9snUPo0SKj65DAWRtLk2YFxESI4ZQGtUHeKeUOxiZX9SPcSjvEocwszKlVqASC+sPngwtXTQOZcBhBxDK5v2VpS+f8DPCf024CVvST0/UbbtsPJd3DUusfOwO3JT9AMU625dFCXlTplY5t6L51KPEU7+fi/GGKB6n5R3i8ncy4JM6JjyAYaAp6X8bZHujQGajtrdm41PXDB2fwzCjm64Y9vr/bGmVxlmP8nk0leK1pKYX+dplRNFGQCSDwwOs9fAIoYjm0cslFP+zKE7lCXWuNKLoDFHLV7zBjuH/1rQGgsJ0HGs+tcQ/VKMGYkA= 39 | -------------------------------------------------------------------------------- /GUIDELINES.md: -------------------------------------------------------------------------------- 1 | # Coding guidelines 2 | 3 | Bash scripiting is very permissive, this guide is here to describe rules to follow by everyone 4 | during development. Some rules are here to prevent potential errors while some others are here 5 | to ensure code style consistency. 6 | 7 | Every single contribution has to follow these rules in order to be accepted. 8 | 9 | ## Variables 10 | 11 | ### Variable reference 12 | 13 | In code, a variable reference is done using the `${}` notation. For example, we write `${myVariable}` instead of `$myVariable`. 14 | 15 | ### Variable into functions 16 | 17 | Everytime it is possible, a variable is declared locally when used inside a function using the `local` keyword. We try to avoid global variable as much as we can. 18 | 19 | ### Variable namming 20 | 21 | Some rules to follow: 22 | 23 | - Variable representing script input parameters (consider like constants) are in *screaming snake case* (e.g. `MY_VARIABLE`) 24 | - Other variables and function name are in *snake case* (e.g. `my_function`) 25 | 26 | ## Functions 27 | 28 | ### Function documentation 29 | 30 | Function documentation is optionnal but strongly encouraged and should be present every time when we 31 | think it is necessary. A function documentation is located immediately before the function declaration 32 | and respects the following format. 33 | 34 | ``` 35 | # 36 | # This function is useful for *** *** 37 | # *** *** *** *** **. 38 | # 39 | # $1 - Input parameter 1 description 40 | # $2 - Input parameter 2 description 41 | # ... 42 | # $n - Input parameter n description 43 | # 44 | ``` 45 | 46 | ## Control structures 47 | 48 | Control structures like `if`, `for` and `while` are written avoiding using extra new lines before 49 | keywords `then` or `do`. We use semicolon instead. 50 | 51 | For instance, we write 52 | 53 | ``` 54 | if [ condition1 ]; then 55 | cmd1 56 | elif [ condition2 ]; then 57 | cmd2 58 | else 59 | cmd3 60 | fi 61 | ``` 62 | 63 | instead of 64 | 65 | ``` 66 | if [ condition1 ] 67 | then 68 | cmd1 69 | elif [ condition2 ] 70 | then 71 | cmd2 72 | else 73 | cmd3 74 | fi 75 | ``` 76 | 77 | ## Conditions 78 | 79 | ### Prefer `test` single bracket 80 | 81 | We prefer to uniforme `test` syntax using always single bracket `[` `]`. 82 | We write `[ condition1 ] || [ condition2 ]` instead of `[[ condition1 || condition2 ]] `. 83 | 84 | ### Prefer `test` flags 85 | 86 | Every time it is possible we prefer to use dash flags from the `test` command rather than merely compare using `=` operator. e.g. we write `[ -z "${my_var}" ]` instead of `[ "${my_var}" = "" ]`. And we write 87 | `[ ${my_var} -ne 0 ]` instead of `[ ${my_var} != 0 ]`. 88 | 89 | ## Code indentation 90 | 91 | Indentation is done using `Tab`. 92 | 93 | ## Misc 94 | 95 | ### Output capture 96 | 97 | To capture command output in a variable we use the `$()` notation. We write `foo="$(bar)"` instead of `foo="``bar``"`. 98 | 99 | ## Testing 100 | 101 | ### About unit test 102 | 103 | Code must be structured in a way that we are able to write unit tests for functions every time we 104 | think it is necessary. 105 | 106 | ### About integration test 107 | 108 | CI works using sheep configuration files contained in `sheep/tests/integration_test` repository. 109 | These files are named following this model : `distribution_bootMode_CIEouCID_hardware.yml`. 110 | 111 | * `hardware` is the type of server targeted by this configuration. 112 | * `distribution` is always written this way : `centOS7`, `openSUSE15_1`, `debian9` and `ubuntu16_04`. 113 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sheep 2 | 3 | [![Build Status](https://api.travis-ci.org/sheeplinux/sheep.svg?branch=master)](https://travis-ci.org/sheeplinux/sheep) 4 | 5 | *Install any Linux distribution in seconds with Sheep* 6 | 7 | ![](img/sheep-logo.png) 8 | 9 | The aim of Sheep is to provide tooling to install any Linux distribution from a running Linux operating system in an automated way implementing a standard process. It also provide good efficiency in term of execution time. 10 | 11 | ## Motivation 12 | 13 | Installing a Linux distribution in an automated way has been a concern for system administrators for a long time. Of course, distribution vendors themselves provide mechanism to run their own installers in an automated way. The thing is, every single distribution comes with its own installation process and its own tools. Sheep proposes to uniform the installation process to easily install any Linux system whatever the distribution is. 14 | 15 | Tools provided by distribution vendors are powerful and highly configurable. However, they come with some important downsides : 16 | 17 | * Configuration mechanisms and tools are specific to each distribution (or at least specific to the distribution family) 18 | * Creating configuration is painful beacuse of file format constraints and the amount of possible configurations 19 | * The installation process is quite long 20 | * Installing a distribution offline - without relying on an internet connection - requires additionnal work (e.g. setup a local distribution package repository) 21 | 22 | Sheep goal is to get rid of all those painpoints. Looking at how Linux works and what are the necessary efforts to install it on a drive, it appears that it is quite easy to describe a process that is always 23 | the regardless the distribution. See [Internals Section](internals.md) to get more details about this 24 | process. The main idea of Sheep is to avoid using vendor installer programs and execute instead necessary steps to install the Linux operating system starting from a filesystem image. 25 | 26 | A central concept in Sheep is to install Linux starting from a cloud image. Today, most of the distribution 27 | vendors are publishing cloud images in various formats for the most popular cloud platforms. Sheep focuses 28 | on `qcow2` images. As this is a widespread open format, it's provided by all vendors. That said, Sheep is able to handle compressed `tar` archives for the root filesystem which can be very useful in many situations. 29 | 30 | To summarize, here are the advantages of Sheep over the standard vendors installers : 31 | 32 | * Sheep is very easy to use with a minimal configuration 33 | * Sheep is able to install any Linux distribution 34 | * Using official cloud images from vendors makes easy to find images for Sheep 35 | * Execution time is really fast. In the most advantageous situation, Sheep installs Linux in about 30 seconds 36 | 37 | Lastly, as any solution, Sheep comes with a couple of drawbacks. Sheep does not offer as much options as the 38 | vendors installers. That said, Sheep source code is quite simple and easy to customize. 39 | 40 | Don't hesitate to open issues to ask for help or request features. 41 | 42 | ## Supported Linux distributions 43 | 44 | Sheep provide a process able to install any Linux operating system. However, Sheep uses cloud-init to configuration the operating system. 45 | 46 | Sheep have been successfully tested with the following distributions 47 | 48 | - Ubuntu 16.04 49 | - Ubuntu 18.04 50 | - CentOS 7 51 | - Debian 9 52 | - Fedora 28 53 | - Fedora 29 54 | - Fedora 30 55 | - OpenSuse Leap 15.0 56 | - OpenSuse Leap 15.1 57 | 58 | ## License 59 | 60 | ```text 61 | Copyright 2020 Mathilde Hermet 62 | Copyright 2020 Guillaume Giamarchi 63 | 64 | Licensed under the Apache License, Version 2.0 (the "License"); 65 | you may not use this file except in compliance with the License. 66 | You may obtain a copy of the License at 67 | 68 | http://www.apache.org/licenses/LICENSE-2.0 69 | 70 | Unless required by applicable law or agreed to in writing, software 71 | distributed under the License is distributed on an "AS IS" BASIS, 72 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 73 | See the License for the specific language governing permissions and 74 | limitations under the License. 75 | ``` 76 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | ENV["LC_ALL"] = "en_US.UTF-8" 5 | 6 | Vagrant.configure(2) do |config| 7 | 8 | config.vm.box = "ubuntu/xenial64" 9 | config.vm.hostname = 'sheep' 10 | 11 | config.vm.provider 'virtualbox' do |vb| 12 | vb.customize ['modifyvm', :id, '--memory', '1024'] 13 | vb.customize ['modifyvm', :id, '--chipset', 'ich9'] 14 | end 15 | 16 | config.vm.provision "shell", privileged: false, inline: <<-SHELL 17 | set -ex 18 | 19 | # 20 | # Install yq 21 | # 22 | sudo apt update 23 | sudo apt install -y python3-pip jq 24 | sudo pip3 install yq 25 | 26 | # 27 | # Install bats 28 | # 29 | git clone https://github.com/sstephenson/bats.git 30 | cd bats 31 | sudo ./install.sh /usr/local 32 | cd .. 33 | rm -rf bats 34 | 35 | # 36 | # Install mkdocs 37 | # 38 | pip3 install mkdocs Pygments 39 | SHELL 40 | 41 | config.vm.network "forwarded_port", guest: 8000, host: 9000 42 | end 43 | -------------------------------------------------------------------------------- /doc/docs/config.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | ## Sheep Configuration File 4 | 5 | The configuration file is in yaml. 6 | If it isn't included in the sheep-live OS after a build, it will be downloaded by the program if you give the path to the file in the kernel parameters of sheep-live boot configuration. 7 | 8 | As we decided to rely on cloud-init to configure the environment, there are two ways to write sheep configuration file: one is for users who want to setup the environment of the OS without any knowledge about cloud-init, and the second one is for users who know how to use cloud-init in NoCloud mode and want to have access to all cloud-init features via the sheep configuration file. 9 | -------------------------------------------------------------------------------- /doc/docs/config_env.md: -------------------------------------------------------------------------------- 1 | # Environment 2 | 3 | ## Simplified mode 4 | 5 | Configure your future OS environment. 6 | This has to be done this way when cloud-init is `disable`. Otherwise this part won't be taken into account. 7 | This configuration will be turned into cloud-init user-data & meta-data files by sheep during execution. 8 | 9 | * `users` : List users you want to create giving at least the `name` 10 | * `name` : User name 11 | * `sudoer` : Add this user to sudoer 12 | **NB : It will be created as a no password sudoer** 13 | * `password` : User password 14 | * `ssh_authorized_key` : Machine public key from where you want to access this server 15 | * `shell` : User type of shell 16 | * `local_hostname` : Machine hostname 17 | 18 | ```yaml 19 | environment: 20 | users: 21 | - name: linux 22 | sudoer: true 23 | password: linux 24 | ssh_authorized_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDdXnRJVWf7OvFa0UZPkvDBave2BWhr29HFlO/bI/98rmPc0zn24a8Wplo/Sts4SrL3xZNATH5tWwNpPulBThPqnjdMU4Rw2Jf/mjlQXiT7+w3w60/HrMd62J/d/dyYrIuvuog3OAEi1vsiKCRm/9ptpbNA4E34ZUBSOpT3bx0b4NszYB2g7VdcmgHHXSY16AVCv3I3ZN0UmWphw1hpjpxfHTinE2pR5L0HVMikxqaxjCZI7DSpi8f4gQJn7gjLTh905o751Z3s7Y4L/v9NTEXmCPF425krwxDD4EMSMJ6BXgAExvPolWV0/W9HUtKX7XtEJUKWLUlikb7qTRWR1sld ubuntu@dev-01 25 | shell: bash 26 | local_hostname: sheep 27 | 28 | ``` 29 | 30 | !!! Note 31 | 32 | If users are configured but none of them can access the OS because neither the `ssh_authorized_key` nor a `password` is configured, the program will exit with an error 33 | 34 | If this section is left empty: 35 | 36 | * User **linux** with password **linux** will be given. 37 | * Machine hostname will be **sheep**. 38 | 39 | ## Advanced mode (cloud-init) 40 | 41 | We can use more features enabled by cloud-init to set up the OS environment. 42 | Configuring the envrionment with cloud-init is done through meta-data and user-data files. 43 | The name of these files are the same than in `cloudInit` sheep section `userData` and `metaData`. 44 | 45 | Refer to [cloud-init documentation](https://cloudinit.readthedocs.io/en/latest/#) for more details about syntax and options. 46 | 47 | ```yaml 48 | cloudInit: 49 | enable: true 50 | metaData: 51 | instance-id: 001-local01 52 | local-hostname: sheep 53 | networkConfig: 54 | version: 2 55 | ethernets: 56 | enp12s0: 57 | dhcp4: true 58 | ens9: 59 | addresses: 60 | - 172.19.17.111/24 61 | gateway4: 172.19.17.1 62 | userData: 63 | users: 64 | - name: linux 65 | lock_passwd: false 66 | ssh_authorized_keys: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDdXnRJVWf7OvFa0UZPkvDBave2BWhr29HFlO/bI/98rmPc0zn24a8Wplo/Sts4SrL3xZNATH5tWwNpPulBThPqnjdMU4Rw2Jf/mjlQXiT7+w3w60/HrMd62J/d/dyYrIuvuog3OAEi1vsiKCRm/9ptpbNA4E34ZUBSOpT3bx0b4NszYB2g7VdcmgHHXSY16AVCv3I3ZN0UmWphw1hpjpxfHTinE2pR5L0HVMikxqaxjCZI7DSpi8f4gQJn7gjLTh905o751Z3s7Y4L/v9NTEXmCPF425krwxDD4EMSMJ6BXgAExvPolWV0/W9HUtKX7XtEJUKWLUlikb7qTRWR1sld ubuntu@dev-01 67 | sudo: ALL=(ALL) NOPASSWD:ALL 68 | shell: /bin/bash 69 | chpasswd: 70 | expire: false 71 | list: | 72 | linux:linux 73 | ssh_pwauth: true 74 | 75 | ``` 76 | 77 | !!! Note 78 | 79 | * See meta-data part in cloud-init documentation : [meta-data](https://cloudinit.readthedocs.io/en/latest/topics/instancedata.html) 80 | * See user-data part in cloud-init documentation : [user-data](https://cloudinit.readthedocs.io/en/latest/topics/format.html) 81 | -------------------------------------------------------------------------------- /doc/docs/config_example.md: -------------------------------------------------------------------------------- 1 | # Sheep configuration full examples 2 | 3 | ## Simplified 4 | 5 | Below is an example of a Sheep configuration file in simplified mode. 6 | 7 | ```yaml 8 | bootloader: 9 | kernel_parameter: 'console=ttyS1,115200n8' 10 | 11 | linux: 12 | image: https://cloud.centos.org/centos/7/images/CentOS-7-aarch64-GenericCloud-2003.qcow2 13 | label: CentOS 7 14 | device: /dev/sda 15 | rootfsType: ext4 16 | rootfsLabel: centos-fs 17 | selinux: disable 18 | blacklist_module: 19 | - mei 20 | - mei_me 21 | 22 | network: 23 | interfaces: 24 | - id: enp12s0 25 | type: dhcp 26 | - id: ens9 27 | type: static 28 | address: x.x.x.x 29 | gateway: x.x.x.x 30 | 31 | pxePilot: 32 | enable: true 33 | url: http://172.19.17.1:3478 34 | config_after_reboot: local 35 | 36 | environment: 37 | users: 38 | - name: linux 39 | sudoer: true 40 | password: linux 41 | ssh_authorized_key: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDdXnRJVWf7OvFa0UZPkvDBave2BWhr29HFlO/bI/98rmPc0zn24a8Wplo/Sts4SrL3xZNATH5tWwNpPulBThPqnjdMU4Rw2Jf/mjlQXiT7+w3w60/HrMd62J/d/dyYrIuvuog3OAEi1vsiKCRm/9ptpbNA4E34ZUBSOpT3bx0b4NszYB2g7VdcmgHHXSY16AVCv3I3ZN0UmWphw1hpjpxfHTinE2pR5L0HVMikxqaxjCZI7DSpi8f4gQJn7gjLTh905o751Z3s7Y4L/v9NTEXmCPF425krwxDD4EMSMJ6BXgAExvPolWV0/W9HUtKX7XtEJUKWLUlikb7qTRWR1sld ubuntu@dev-01 42 | shell: /bin/bash 43 | local_hostname: sheep 44 | 45 | cloudInit: 46 | enable: false 47 | instance_id: 001-local01 48 | 49 | sheep: 50 | reboot: false 51 | 52 | ``` 53 | 54 | ## Cloud-init users oriented 55 | 56 | Below is an example of a configuration file where appears clearly the configurable part of cloud-init. 57 | 58 | ```yaml 59 | bootloader: 60 | kernel_parameter: 'console=ttyS1,115200n8' 61 | 62 | linux: 63 | image: https://cloud.centos.org/centos/7/images/CentOS-7-aarch64-GenericCloud-2003.qcow2 64 | label: CentOS 7 65 | device: /dev/sda 66 | rootfsType: ext4 67 | rootfsLabel: centos-fs 68 | selinux: disable 69 | blacklist_module: 70 | - mei 71 | - mei_me 72 | 73 | pxePilot: 74 | enable: true 75 | url: http://172.19.17.1:3478 76 | config_after_reboot: local 77 | 78 | cloudInit: 79 | enable: true 80 | metaData: 81 | instance-id: 001-local01 82 | local-hostname: sheep 83 | networkConfig: 84 | version: 2 85 | ethernets: 86 | enp12s0: 87 | dhcp4: true 88 | ens9: 89 | addresses: 90 | - 172.19.17.111/24 91 | gateway4: 172.19.17.1 92 | userData: 93 | users: 94 | - name: linux 95 | lock_passwd: false 96 | ssh_authorized_keys: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDdXnRJVWf7OvFa0UZPkvDBave2BWhr29HFlO/bI/98rmPc0zn24a8Wplo/Sts4SrL3xZNATH5tWwNpPulBThPqnjdMU4Rw2Jf/mjlQXiT7+w3w60/HrMd62J/d/dyYrIuvuog3OAEi1vsiKCRm/9ptpbNA4E34ZUBSOpT3bx0b4NszYB2g7VdcmgHHXSY16AVCv3I3ZN0UmWphw1hpjpxfHTinE2pR5L0HVMikxqaxjCZI7DSpi8f4gQJn7gjLTh905o751Z3s7Y4L/v9NTEXmCPF425krwxDD4EMSMJ6BXgAExvPolWV0/W9HUtKX7XtEJUKWLUlikb7qTRWR1sld ubuntu@dev-01 97 | sudo: ALL=(ALL) NOPASSWD:ALL 98 | shell: /bin/bash 99 | chpasswd: 100 | expire: false 101 | list: | 102 | linux:linux 103 | ssh_pwauth: true 104 | 105 | sheep: 106 | reboot: false 107 | 108 | ``` 109 | -------------------------------------------------------------------------------- /doc/docs/config_lin.md: -------------------------------------------------------------------------------- 1 | # Linux installation 2 | 3 | ## Bootloaders 4 | 5 | ### Supported bootloaders 6 | 7 | GRUB is the only one supported bootloader at this time. There is currently no plan to support 8 | more bootloaders. 9 | 10 | ### Supported boot modes 11 | 12 | Sheep is able to install the operating system using either UEFI mode and Legacy (MBR) mode 13 | 14 | ### Configuration 15 | 16 | Configure the bootloader of the future linux OS 17 | 18 | * `image` : Path to grub-efi.tar.gz archive - For **UEFI** system only 19 | * `change_boot_order` : **none** (EFI boot entry created but boot order unchanged), **persistent** (change boot order, default) or **once** (change next boot for one reboot only) - Set new EFI menu entry as first in boot order - For **UEFI** system only 20 | * `kernel_parameter` : Kernel parameters you want to add to customize the way your system boots 21 | 22 | ```yaml 23 | bootloader: 24 | image: http://path/to/grub-efi.tar.gz 25 | change_boot_order: none 26 | kernel_parameter: 'console=ttyS1,57600n8' 27 | 28 | ``` 29 | 30 | !!! Note 31 | Sheep does not support multiboot. Be careful, installating Linux with Sheep on a drive, 32 | flush the whole partition table on the drive. 33 | 34 | ## Linux system 35 | 36 | Configure your linux OS 37 | 38 | * `image` : Path to the linux filesystem image you want to use 39 | * `label` : Name your distribution for **EFI menu entry** and **grub menu** - Default value **Linux** 40 | * `device` : Storage device to contain OS - First storage disk listed during the installation by default 41 | * `rootfsType` : **ext4** & **btrfs** supported - Default value **ext4** 42 | * `rootfsLabel` : label of root partition 43 | It has to be 12 characters or less : `[0-9]` `[a-z]` `[A-Z]` `-` `_` allowed 44 | Default value **rootfs** 45 | * `selinux` : **enable** or **disable** - Default value **disable** 46 | * `blacklist_module` : List kernel module that has to be disable 47 | 48 | ```yaml 49 | linux: 50 | image: https://cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-amd64-disk1.img 51 | label: ubuntu_16_09 52 | device: /dev/sda 53 | rootfsType: ext4 54 | rootfsLabel: cloudimg-rootfs 55 | selinux: disable 56 | blacklist_module: 57 | - mei 58 | - mei_me 59 | 60 | ``` 61 | 62 | !!! Note 63 | 64 | - SELINUX isn't used by every linux distribtuion. For example enabling it for ubuntu will have no effect 65 | - Permissive mode of Selinux not available for the moment with sheep 66 | 67 | ## Pxe-pilot 68 | 69 | Configure pxe-pilot with sheep installation 70 | 71 | * `enable` : **true** or **false** 72 | * `url` : Pxe-pilot API endpoint URL 73 | * `config_after_reboot` : Pxe-pilot configuration name to indicate the machine to reboot on it's hard disk 74 | 75 | ```yaml 76 | pxePilot: 77 | enable: true 78 | url: http://pxe_pilot_server_address 79 | config_after_reboot: local 80 | 81 | ``` 82 | 83 | ## Sheep execution 84 | 85 | Configure machine reboot by the end of sheep execution. 86 | 87 | * `reboot`: **true** or **false** 88 | 89 | ```yaml 90 | sheep: 91 | reboot: false 92 | ``` 93 | -------------------------------------------------------------------------------- /doc/docs/config_net.md: -------------------------------------------------------------------------------- 1 | # Networking 2 | 3 | ## Basic network configuration 4 | 5 | Configure OS Network has to be done this way when cloud-init is `disable`. Otherwise this part won't be taken into account. 6 | This configuration will be turned into a cloud-init network configuration file by sheep during execution. 7 | 8 | * `interfaces` : List of interfaces to configure 9 | * `id` : Interface identifier 10 | * `type` : **static** or **dhcp** 11 | * `address` : Required in **static** mode 12 | * `gateway` : Can be configured in **static** mode 13 | 14 | ```yaml 15 | network: 16 | interfaces: 17 | - id: ens1 18 | type: static 19 | address: 172.19.17.111 20 | gateway: 172.19.17.1 21 | 22 | ``` 23 | 24 | !!! Note 25 | 26 | If this section empty, the network will be configures by clou-init default mode 27 | That means **dhcp** on the first connected interface. 28 | 29 | ## Advanced network configuration with cloud-init usage 30 | 31 | Configure network in sheep-configuration file the same way you would writethe file network-config for cloud-init. 32 | This permit you to use all cloud-init features not adapted by sheep. 33 | Set `.cloudInit.enabled` on `true` to see that taken into account. 34 | See network part on cloud-init documentation : [network-config](https://cloudinit.readthedocs.io/en/latest/topics/network-config.html) 35 | -------------------------------------------------------------------------------- /doc/docs/css/codehilite.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Initially Generated with `pygmentize -S default -f html -a .codehilite` 3 | * 4 | * This style have been modified to match Sheep documentation requirements 5 | */ 6 | 7 | .codehilite .hll { background-color: #ffffcc } 8 | .codehilite { background: #f8f8f8; } 9 | .codehilite .c { color: #408080; font-style: italic } /* Comment */ 10 | .codehilite .k { color: #008000; font-weight: bold } /* Keyword */ 11 | .codehilite .o { color: #666666 } /* Operator */ 12 | .codehilite .ch { color: #408080; font-style: italic } /* Comment.Hashbang */ 13 | .codehilite .cm { color: #408080; font-style: italic } /* Comment.Multiline */ 14 | .codehilite .cp { color: #BC7A00 } /* Comment.Preproc */ 15 | .codehilite .cpf { color: #408080; font-style: italic } /* Comment.PreprocFile */ 16 | .codehilite .c1 { color: #408080; font-style: italic } /* Comment.Single */ 17 | .codehilite .cs { color: #408080; font-style: italic } /* Comment.Special */ 18 | .codehilite .gd { color: #A00000 } /* Generic.Deleted */ 19 | .codehilite .ge { font-style: italic } /* Generic.Emph */ 20 | .codehilite .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 21 | .codehilite .gi { color: #00A000 } /* Generic.Inserted */ 22 | .codehilite .go { color: #888888 } /* Generic.Output */ 23 | .codehilite .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 24 | .codehilite .gs { font-weight: bold } /* Generic.Strong */ 25 | .codehilite .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 26 | .codehilite .gt { color: #0044DD } /* Generic.Traceback */ 27 | .codehilite .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ 28 | .codehilite .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ 29 | .codehilite .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ 30 | .codehilite .kp { color: #008000 } /* Keyword.Pseudo */ 31 | .codehilite .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ 32 | .codehilite .kt { color: #B00040 } /* Keyword.Type */ 33 | .codehilite .m { color: #666666 } /* Literal.Number */ 34 | .codehilite .s { color: #BA2121 } /* Literal.String */ 35 | .codehilite .na { color: #7D9029 } /* Name.Attribute */ 36 | .codehilite .nb { color: #008000 } /* Name.Builtin */ 37 | .codehilite .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 38 | .codehilite .no { color: #880000 } /* Name.Constant */ 39 | .codehilite .nd { color: #AA22FF } /* Name.Decorator */ 40 | .codehilite .ni { color: #999999; font-weight: bold } /* Name.Entity */ 41 | .codehilite .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ 42 | .codehilite .nf { color: #0000FF } /* Name.Function */ 43 | .codehilite .nl { color: #A0A000 } /* Name.Label */ 44 | .codehilite .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 45 | .codehilite .nt { color: #008000; font-weight: bold } /* Name.Tag */ 46 | .codehilite .nv { color: #19177C } /* Name.Variable */ 47 | .codehilite .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 48 | .codehilite .w { color: #bbbbbb } /* Text.Whitespace */ 49 | .codehilite .mb { color: #666666 } /* Literal.Number.Bin */ 50 | .codehilite .mf { color: #666666 } /* Literal.Number.Float */ 51 | .codehilite .mh { color: #666666 } /* Literal.Number.Hex */ 52 | .codehilite .mi { color: #666666 } /* Literal.Number.Integer */ 53 | .codehilite .mo { color: #666666 } /* Literal.Number.Oct */ 54 | .codehilite .sa { color: #BA2121 } /* Literal.String.Affix */ 55 | .codehilite .sb { color: #BA2121 } /* Literal.String.Backtick */ 56 | .codehilite .sc { color: #BA2121 } /* Literal.String.Char */ 57 | .codehilite .dl { color: #BA2121 } /* Literal.String.Delimiter */ 58 | .codehilite .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ 59 | .codehilite .s2 { color: #BA2121 } /* Literal.String.Double */ 60 | .codehilite .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ 61 | .codehilite .sh { color: #BA2121 } /* Literal.String.Heredoc */ 62 | .codehilite .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ 63 | .codehilite .sx { color: #008000 } /* Literal.String.Other */ 64 | .codehilite .sr { color: #BB6688 } /* Literal.String.Regex */ 65 | .codehilite .s1 { color: #BA2121 } /* Literal.String.Single */ 66 | .codehilite .ss { color: #19177C } /* Literal.String.Symbol */ 67 | .codehilite .bp { color: #008000 } /* Name.Builtin.Pseudo */ 68 | .codehilite .fm { color: #0000FF } /* Name.Function.Magic */ 69 | .codehilite .vc { color: #19177C } /* Name.Variable.Class */ 70 | .codehilite .vg { color: #19177C } /* Name.Variable.Global */ 71 | .codehilite .vi { color: #19177C } /* Name.Variable.Instance */ 72 | .codehilite .vm { color: #19177C } /* Name.Variable.Magic */ 73 | .codehilite .il { color: #666666 } /* Literal.Number.Integer.Long */ 74 | -------------------------------------------------------------------------------- /doc/docs/img/sheep-logo.png: -------------------------------------------------------------------------------- 1 | ../../../img/sheep-logo.png -------------------------------------------------------------------------------- /doc/docs/index.md: -------------------------------------------------------------------------------- 1 | ../../README.md -------------------------------------------------------------------------------- /doc/docs/internals.md: -------------------------------------------------------------------------------- 1 | ## Internals 2 | 3 | This section describes how Sheep works internally. 4 | 5 | ## Installation process in UEFI boot mode 6 | 7 | Here are the steps executed by this tool in order to install a Linux distribution 8 | 9 | 1. Wipe the target drive (every single partition is deleted) 10 | 2. Create three GPT partitions 11 | - The EFI System partition 12 | - The cloud-init partition 13 | - The Linux root filesystem partition 14 | 3. Format partitions using the appropriate filesystem 15 | 4. Mount partitions 16 | 5. Get Linux root filesystem image and extract it 17 | 6. Get EFI bootloader and install it 18 | 7. Create boot entry in the EFI boot manager 19 | 8. Write the grub menu 20 | 9. Configure the Linux root filesystem using cloud-init 21 | 10. Unmount partitions 22 | 11. Reboot the machine 23 | 24 | ## Installation process in legacy boot mode 25 | 26 | Here are the steps executed by this tool in order to install a Linux distribution 27 | 28 | 1. Wipe the target drive (every single partition is deleted) 29 | 2. Create three GPT partitions 30 | - The bios boot partition for MBR 31 | - The cloud-init partition 32 | - The Linux root filesystem partition 33 | 3. Format partitions using the appropriate filesystem 34 | 4. Mount partitions 35 | 5. Get Linux root filesystem image and extract it 36 | 6. Write the MBR partition using grub-install 37 | 7. Write the grub menu 38 | 8. Configure the Linux root filesystem using cloud-init 39 | 9. Unmount partitions 40 | 10. Reboot the machine 41 | 42 | The full process run in a couple of seconds when using a `tar.gz` or `tar.xz` a root filesystem archive. It takes additionnal time (depending on your CPU performance) when extracting Qcow2 43 | -------------------------------------------------------------------------------- /doc/docs/launching.md: -------------------------------------------------------------------------------- 1 | # Sheep launching 2 | 3 | ## Launch sheep using sheep-live 4 | 5 | This first way to launch sheep makes the process run fully automated. 6 | It is documented in [sheep-live documentation](./sheep-live.md) 7 | 8 | ## Execution without kernel parameters command line 9 | 10 | Reading variables through /proc/cmdline is good for deployment automation, 11 | especially when booting a live distribution from the network as an installation 12 | environment. 13 | 14 | However, we are able to run Sheep passing parameters directly 15 | to the script because there are no reason to enforce this way of doing. We are 16 | be able to pass parameters even if it was not previoulsy configured in the 17 | kernel parameter line. 18 | 19 | It's also usefull for testing purpose during development because we can run 20 | different tests without the need of rebooting the machine. 21 | 22 | See the example below : 23 | 24 | ```bash 25 | SHEEP_PARAMETERS='sheep.config=http://netserver/ubuntu-16.04-leopard-sheep-cfg.yml' sheep 26 | ``` 27 | -------------------------------------------------------------------------------- /doc/docs/pxe-pilot.md: -------------------------------------------------------------------------------- 1 | # PXE Pilot integration 2 | 3 | This section describes how to automate Linux distribution installation Using Sheep & PXE Pilot with DNSMASQ as DHCP/TFTP server and a Syslinux EFI bootloader. 4 | 5 | The goal is to provide userfriendly interface to manage many servers and re-image any machine with any Linux distribution using a one line command. 6 | -------------------------------------------------------------------------------- /doc/docs/quickstart.md: -------------------------------------------------------------------------------- 1 | # Quickstart 2 | 3 | Installing a Linux distribution on a physical machine using Sheep basically consists in runnning a single script 4 | that will perform Linux installation in a fully automated way. The sheep script only needs a configuration file describing the Linux distribution you aim to install. 5 | 6 | ## Write a Sheep configuration 7 | 8 | Here is a minimalist Sheep YAML configuration 9 | 10 | ```yaml 11 | bootloader: 12 | image: http://replace/by/real/path/grub.tar.gz 13 | 14 | linux: 15 | image: https://cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-amd64-disk1.img 16 | label: Ubuntu 16.04 LTS 17 | 18 | network: 19 | interfaces: 20 | - id: ens1 21 | type: dhcp 22 | 23 | environment: 24 | users: 25 | - name: linux 26 | sudoer: true 27 | password: linux 28 | ssh_authorized_key: ssh-rsa ... 29 | local_hostname: linux 30 | ``` 31 | 32 | In this example, we use an official Ubuntu qcow2 cloud image. The bootloader image is only needed when your machine runs an UEFI firmware. When in BIOS (Legacy) mode, you can basically remove the bootloader section. 33 | 34 | ## Setup the execution environment 35 | 36 | The purpose of Sheep is to install Linux from Linux. That means you need your system to run Linux to install Linux on your local drive. That is the purpose of Sheep Live, a Live Linux distribution made to run Sheep. 37 | 38 | As Sheep primarily makes sense in a fully automated environement, Sheep Live provides artifacts to easily boot over the network. 39 | 40 | Download the Sheep Live kernel, initrd and root filesystem binaries [here](https://github.com/sheeplinux/sheep-live/releases/latest). 41 | 42 | Using these artifacts deployed on your TFTP/HTTP server, you can write a configuration to boot Sheep Live. 43 | 44 | **Example using pxelinux/syslinux** 45 | 46 | ```text 47 | default sheep 48 | label sheep 49 | kernel /sheep-live/vmlinuz 50 | append boot=live fetch=http://netserver/sheep-live.squashfs initrd=/sheep-live/initrd.img ssh=sheep sheep.script=http://netserver/sheep sheep.config=http://netserver/ubuntu-16.04-leopard.yml startup=/usr/bin/sheep-service 51 | ``` 52 | 53 | To learn more about Sheep Live, please refer to [Sheep live environment](https://sheeplinux.github.io/sheep/sheep-live/) page. 54 | 55 | ## Run Sheep 56 | 57 | Once Sheep Live is up and running, it automatically runs the Linux installation process using Sheep and then reboot the machine when installation complete. 58 | 59 | If for any reason you want to prevent Sheep to run automatically (e.g. to debug or run manually) you only have to remove the `startup` kernel parameter on the command line. 60 | -------------------------------------------------------------------------------- /doc/docs/sheep-live.md: -------------------------------------------------------------------------------- 1 | # Sheep-live 2 | 3 | ## What is sheep-live ? 4 | 5 | Sheep-live is a Debian 10 Linux based on the GRML Live building system. 6 | We use it to have a faster and optimized sheep execution. 7 | It contains some packages absent in a basic grml and has a mechanism to download and run sheep via the information given in kernel command parameters while launching sheep-live. 8 | As it, the user can simply have on his main server the sheep configuration file and the pxeboot file specifying in the kernel parameters to launch the service sheep when booting on sheep-live. 9 | 10 | ## Why sheep-live ? 11 | 12 | Sheep could be run from any Linux distribution as soon as all the dependencies are present. 13 | Providing a specific distribution gives two major advantages to avoid undesirable side effects : 14 | 15 | * Linux environment to run Sheep with all the needed material for Sheep inside (avoid internet interaction during the Sheep process) 16 | * A distribution used to test and validate sheep in the continuous integration platform 17 | 18 | ## How to create a sheep-live build ? 19 | 20 | All the documentation related to this question is available in the git repository [sheep-live](https://github.com/sheeplinux/sheep-live) 21 | 22 | ## How to launch sheep-live 23 | 24 | To launch sheep-live, place the sheep-live squashfs in your web server, the initrd and the kernel file in your HTTP / TFTP server. 25 | Create a network boot file specifying the path to sheep-live squashfs. 26 | If you want sheep to be run automatically after the operating system boot, specify it in the kernel command line with `startup=/usr/bin/sheep-service`. 27 | Below is an example of a pxelinux file we use to launch sheep on our machines : 28 | 29 | ``` 30 | DEFAULT sheeplive 31 | 32 | label sheeplive 33 | kernel /sheep-live/vmlinuz 34 | append boot=live fetch=http://server_add/sheep-live.squashfs initrd=/sheep-live/initrd.img ssh=sheep console=ttyS1,57600n8 sheep.script=http://server_add/sheep.sh sheep.config=http://server_add/sheepCfg.yml startup=/usr/bin/sheep-service 35 | 36 | ``` 37 | With `server_add` the address of the network interface connected to the machine we want to install. 38 | As shown above, [GRML kernel parameters](https://git.grml.org/?p=grml-live.git;a=blob_plain;f=templates/GRML/grml-cheatcodes.txt;hb=HEAD) can be used to tune your configuration. 39 | Here are the Sheep specific kernel parameters to automatically run Sheep when Sheep Live is booted: 40 | 41 | * `sheep.script=http://...` : URL to download Sheep script from. 42 | * `sheep.config=http://...` : URL to download the Sheep YAML configuration for your machine. 43 | * `startup=/usr/bin/sheep-service` : Give script path to automatically start the Sheep service on boot (automatically install linux on your drive). 44 | Without the `startup` parameter you'll have to log into Sheep Live to run the Sheep service 45 | ``` 46 | $ sheep-service 47 | 48 | ``` 49 | * `sheep.log.level` : Configure the logger with value `error` `warning` `info` or `debug`. 50 | * `sheep.delay` : Delay in seconds to delay sheep-service execution. Can be useful to avoid having boot log in sheep log. 51 | -------------------------------------------------------------------------------- /doc/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Sheep 2 | site_author: Guillaume Giamarchi, Mathilde Hermet 3 | 4 | extra_css: [css/codehilite.css] 5 | 6 | theme: 7 | name: readthedocs 8 | highlightjs: false 9 | 10 | nav: 11 | - Home: index.md 12 | - quickstart.md 13 | - launching.md 14 | - sheep-live.md 15 | - Configuration: 16 | - config.md 17 | - config_lin.md 18 | - config_env.md 19 | - config_net.md 20 | - config_example.md 21 | - Miscellaneous: 22 | - internals.md 23 | - pxe-pilot.md 24 | 25 | markdown_extensions: 26 | - admonition 27 | - codehilite 28 | -------------------------------------------------------------------------------- /img/sheep-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sheeplinux/sheep/3682ec3b8d45f7bd148a4df4b5514727f9782b60/img/sheep-logo.png -------------------------------------------------------------------------------- /sheep: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ############################################################################## 4 | # 5 | # Copyright 2020 Mathilde Hermet 6 | # Copyright 2020 Guillaume Giamarchi 7 | # 8 | # Licensed under the Apache License, Version 2.0 (the "License"); 9 | # you may not use this file except in compliance with the License. 10 | # You may obtain a copy of the License at 11 | # 12 | # http://www.apache.org/licenses/LICENSE-2.0 13 | # 14 | # Unless required by applicable law or agreed to in writing, software 15 | # distributed under the License is distributed on an "AS IS" BASIS, 16 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | # See the License for the specific language governing permissions and 18 | # limitations under the License. 19 | # 20 | ############################################################################## 21 | 22 | set -e 23 | 24 | # 25 | # Initialize logger. To configure the logger, environment variables SHEEP_LOG_FILE and 26 | # SHEEP_LOG_LEVEL can be set prior calling this function. Authorized log levels are 27 | # ERROR, WARNING, INFO and DEBUG. Log level can also be configured using kernel parameter 28 | # `sheep.log.level`. 29 | # 30 | init_logger() { 31 | if [ -z "${SHEEP_LOG_FILE}" ]; then 32 | SHEEP_LOG_FILE="/tmp/sheep-$(date +%s).log" 33 | fi 34 | touch ${SHEEP_LOG_FILE} 35 | 36 | if [ -z ${SHEEP_LOG_LEVEL} ]; then 37 | SHEEP_LOG_LEVEL=$(search_kernel_parameter "sheep.log.level" "INFO") 38 | else 39 | SHEEP_LOG_LEVEL=${SHEEP_LOG_LEVEL^^} 40 | if [ "${SHEEP_LOG_LEVEL}" != "ERROR" ] && [ "${SHEEP_LOG_LEVEL}" != "WARNING" ] && [ "${SHEEP_LOG_LEVEL}" != "INFO" ] && [ "${SHEEP_LOG_LEVEL}" != "DEBUG" ]; then 41 | SHEEP_LOG_LEVEL=INFO 42 | fi 43 | fi 44 | } 45 | 46 | # 47 | # Shortcut for log_info function 48 | # 49 | # $* - Text to log 50 | # 51 | log() { 52 | log_info $* 53 | } 54 | 55 | # 56 | # Log text in file specified by the environment variable 57 | # SHEEP_LOG_FILE when log level is ERROR, WARNING 58 | # or INFO 59 | # 60 | # $* - Text to log 61 | # 62 | log_info() { 63 | _log INFO $* 64 | } 65 | 66 | # 67 | # Log text in file specified by the environment variable 68 | # SHEEP_LOG_FILE when log level is ERROR or WARNING 69 | # 70 | # $* - Text to log 71 | # 72 | log_warning() { 73 | _log WARNING $* 74 | } 75 | 76 | # 77 | # Log text in file specified by the environment variable 78 | # SHEEP_LOG_FILE (always) 79 | # 80 | # $* - Text to log 81 | # 82 | log_error() { 83 | _log ERROR $* 84 | } 85 | 86 | # 87 | # Log text in file specified by the environment variable 88 | # SHEEP_LOG_FILE when loglevel is DEBUG 89 | # 90 | # $* - Text to log 91 | # 92 | log_debug() { 93 | _log DEBUG $* 94 | } 95 | 96 | # 97 | # Internal function for logging. The one that actually do logging 98 | # 99 | # $1 - Log severity (i.e. ERROR, WARNING, INFO or DEBUG) 100 | # $* - Text to log 101 | # 102 | _log() { 103 | local severity="${1}" 104 | 105 | if [ "${severity}" = 'ERROR' ]; then 106 | : 107 | elif [ "${severity}" = 'WARNING' ]; then 108 | if [ "${SHEEP_LOG_LEVEL}" = "ERROR" ]; then 109 | return 110 | fi 111 | elif [ "${severity}" = 'DEBUG' ]; then 112 | if [ "${SHEEP_LOG_LEVEL}" != "DEBUG" ]; then 113 | return 114 | fi 115 | else 116 | # 117 | # If severity is equals to something else, the only one remaining authorized value is 'INFO', 118 | # so we force this value. It is equivalent to have a fallback value to INFO when the value is unknown 119 | # 120 | severity=INFO 121 | if [[ "${SHEEP_LOG_LEVEL}" = "ERROR" || "${SHEEP_LOG_LEVEL}" = "WARNING" ]]; then 122 | return 123 | fi 124 | fi 125 | 126 | { 127 | printf "$(date '+[%D %T %z]') %-7s | " ${severity} 128 | shift 129 | echo "$*" 130 | } | tee -a ${SHEEP_LOG_FILE} ${SHEEP_SERIAL} > /dev/null 2>&1 131 | } 132 | 133 | # 134 | # $1 - Error message 135 | # 136 | exit_on_error() { 137 | log_debug "-> ${FUNCNAME[0]} $*" 138 | 139 | echo "ERROR : ${1}" >&2 140 | log_error "Exit with status code 1" 141 | exit 1 142 | } 143 | 144 | # 145 | # Configuration map populated when search_value is called the first time. 146 | # 147 | declare -A kernelParametersMap 148 | 149 | # 150 | # When the function is called the first time it parses the command line parameter 151 | # to populate the `kernelParametersMap` variable. 152 | # 153 | # By default, parameters are retrieved from the kernel command line reading /proc/cmdline 154 | # content. This can be overriden by setting up the SHEEP_PARAMETERS environment variable 155 | # with the exact same syntax. 156 | # 157 | load_kernel_parameters() { 158 | log_debug "-> ${FUNCNAME[0]} $*" 159 | 160 | if [ ${#kernelParametersMap[@]} -eq 0 ]; then 161 | local cmd=${SHEEP_PARAMETERS} 162 | if [ -z "${cmd}" ]; then 163 | cmd=$(cat /proc/cmdline) 164 | fi 165 | 166 | IFS=' ' read -r -a array <<< "${cmd}" 167 | for param in ${array[@]}; do 168 | local key=$(echo "${param}" | cut -d '=' -f 1) 169 | local value=$(echo "${param}" | cut -d '=' -f 2-) 170 | kernelParametersMap[${key}]="${value}" 171 | done 172 | fi 173 | } 174 | 175 | # 176 | # search_kernel_parameter returns the value matching a given kernel parameter key. 177 | # if the parameter is not defined or if its value is blank, it returns a default value if 178 | # provided. If a kernel parameter key appears several times on the command line, only the 179 | # last one is taken into account. 180 | # 181 | # $1 - Kernel parameter key 182 | # $2 - Default value 183 | # 184 | search_kernel_parameter() { 185 | log_debug "-> ${FUNCNAME[0]} $*" 186 | load_kernel_parameters 187 | echo "${kernelParametersMap[${1}]:-${2}}" 188 | } 189 | 190 | # 191 | # search_value returns a piece of configuration from the Sheep YAML configuration file. 192 | # 193 | # The function call parser yq with -w 10000 parameters to parse correctly long sized value 194 | # With -Y to parse correctly when the key have subkeys. 195 | # 196 | # $1 - Parameter identifier 197 | # $2 - Default value 198 | # $3 - Parsing option (possible values are 'string' or 'yaml'. Default is 'string') 199 | # 200 | search_value() { 201 | log_debug "-> ${FUNCNAME[0]} $*" 202 | 203 | if [ -z "${3}" ] || [ "${3}" == "string" ]; then 204 | local value=$(yq -r "${1}" "${CONFIG_FILE}") 205 | if [ "${value}" == "null" ]; then 206 | value="${2}" 207 | fi 208 | echo "${value}" 209 | elif [ "${3}" == "yaml" ]; then 210 | yq -r -Y -w 100000 "${1}" "${CONFIG_FILE}" 211 | fi 212 | } 213 | 214 | # 215 | # Return the value corresponding to the key if given in sheep config file 216 | # Create an error to stop sheep execution if value is empty for the key 217 | # 218 | # $1 - key 219 | # $2 - error message if the value is not found 220 | # 221 | search_mandatory_value() { 222 | log_debug "-> ${FUNCNAME[0]} $*" 223 | 224 | local value=$(yq -r "${1}" "${CONFIG_FILE}") 225 | 226 | [ "${value}" == "null" ] && exit_on_error "${2}" 227 | log_debug "Mandatory value for '${1}' found => '${value}'" 228 | echo "${value}" 229 | } 230 | 231 | # 232 | # This function prepare the environment by downloading tools required by the yaml parser for variables implementation 233 | # 234 | prepare_env() { 235 | log_debug "-> ${FUNCNAME[0]} $*" 236 | 237 | ### Workaround for Debian repos issue when runnning GRML 238 | ### E: The repository 'http://security.debian.org testing/updates Release does not have a Release' file. 239 | ### We do not need this package repository so we delete it 240 | # This line is required only if the version of grml is not sheep-live 241 | if [ $(cat /etc/grml_version | grep "^grml64-full 2018.12") -lt 1 ]; then 242 | cat <<- 'EOF' > /etc/apt/sources.list.d/debian.list 243 | deb http://snapshot.debian.org/archive/debian/20181230/ testing main contrib non-free 244 | deb-src http://snapshot.debian.org/archive/debian/20181230/ testing main contrib non-free 245 | EOF 246 | fi 247 | 248 | # Downloading of packet needed to parse YAML configuration file 249 | if command_exists yq; then 250 | log_debug "The parser is already installed" 251 | else 252 | if command_exists apt; then 253 | apt update 254 | log_debug "apt-update return : $?" 255 | apt install -y python-pip 256 | log_debug "apt install python-pip return : $?" 257 | apt install -y python-setuptools 258 | log_debug "apt install python-setuptools return : $?" 259 | pip install wheel 260 | log_debug "apt install wheel return : $?" 261 | apt install -y jq 262 | log_debug "apt install jq return : $?" 263 | pip install yq 264 | log_debug "apt install yq return : $?" 265 | else 266 | exit_on_error "Neither yq nor apt available on this live distribution" 267 | fi 268 | fi 269 | 270 | } 271 | 272 | load_config() { 273 | log_debug "-> ${FUNCNAME[0]} $*" 274 | 275 | local CONFIG_FILE_PATH=$(search_kernel_parameter sheep.config) 276 | if [ -z ${CONFIG_FILE_PATH} ]; then 277 | exit_on_error "Configuration file is missing" 278 | fi 279 | CONFIG_FILE=$(mktemp -d)/config 280 | wget --quiet -O ${CONFIG_FILE} ${CONFIG_FILE_PATH} 281 | log_debug "downloading of config file by wget return : $?" 282 | } 283 | 284 | # 285 | # Create every variable needed by calling search_value or search_mandatory_value 286 | # 287 | config_variable() { 288 | log_debug "-> ${FUNCNAME[0]} $*" 289 | 290 | OS_NAME=$(search_value ".linux.label" "Linux") 291 | CLOUD_INIT_ENABLED=$(search_value ".cloudInit.enable" "false") 292 | BLOCK_DEVICE=$(search_value ".linux.device" $(ls /dev/[hs]d[a-z] | head -1)) 293 | EFI_PARTITION="${BLOCK_DEVICE}1" 294 | CIDATA_PARTITION="${BLOCK_DEVICE}2" 295 | CIDATA_PARTITION_SIZE=100M 296 | LINUX_PARTITION="${BLOCK_DEVICE}3" 297 | BOOT_MODE=$([ -d /sys/firmware/efi ] && echo uefi || echo legacy) 298 | if [ "${BOOT_MODE}" == "legacy" ]; then 299 | CODE_PARTITIONNING_BOOT=ef02 300 | BOOT_PARTITION_SIZE=2M 301 | elif [ "${BOOT_MODE}" == "uefi" ]; then 302 | EFI_ARCHIVE_URL=$(search_mandatory_value .bootloader.image "'.bootloader.image' parameter must be provided") 303 | local ret=$? 304 | if [ ${ret} -ne 0 ]; then 305 | exit ${ret} 306 | fi 307 | CODE_PARTITIONNING_BOOT=ef00 308 | BOOT_PARTITION_SIZE=500M 309 | else 310 | exit_on_error "Boot mode '${BOOT_MODE}' is not supported" 311 | fi 312 | PXE_PILOT_ENABLED=$(search_value ".pxePilot.enable" "false") 313 | if [ "${PXE_PILOT_ENABLED}" == "true" ]; then 314 | PXE_PILOT_BASEURL="$(search_mandatory_value .pxePilot.url \"'.pxePilot.url' parameter must be provided\")" 315 | local ret=$? 316 | if [ ${ret} -ne 0 ]; then 317 | exit ${ret} 318 | fi 319 | PXE_PILOT_CFG=$(search_value ".pxePilot.config_after_reboot" "local") 320 | fi 321 | LINUX_ROOTFS_URL=$(search_mandatory_value ".linux.image" "'.linux.image' parameter must be provided") 322 | local ret=$? 323 | if [ ${ret} -ne 0 ]; then 324 | exit ${ret} 325 | fi 326 | EFI_ENTRY_LABEL="${OS_NAME}" 327 | CODE_PARTITIONNING=8300 328 | KERNEL_PARAMETER=$(search_value ".bootloader.kernel_parameter") 329 | SELINUX=$(search_value ".linux.selinux" "disable") 330 | ROOTFS_LABEL=$(search_value ".linux.rootfsLabel" "rootfs") 331 | check_filesystem_label ${ROOTFS_LABEL} 332 | ROOTFS_TYPE=$(search_value ".linux.rootfsType" "ext4") 333 | REBOOT_WHEN_DONE=$(search_value ".sheep.reboot" "true") 334 | } 335 | 336 | # 337 | # Download tools to install and set the OS 338 | # 339 | download_tools() { 340 | log_debug "-> ${FUNCNAME[0]} $*" 341 | 342 | if command_exists guestmount; then 343 | log_debug "guestmount is already installed" 344 | else 345 | if command_exists apt; then 346 | DEBIAN_FRONTEND=noninteractive apt install -y libguestfs-tools 347 | log_debug "apt install -y libguestfs-tools return : $?" 348 | else 349 | exit_on_error "Neither apt nor guestmount available on the live distribution " 350 | fi 351 | fi 352 | } 353 | 354 | # 355 | # Check if the command exists 356 | # 357 | command_exists() { 358 | command -v "$@" > /dev/null 2>&1 359 | } 360 | 361 | # 362 | # Check if the config file match the following requirements : 363 | # * Less than 12 characters 364 | # * Characters allowed [0-9],[a-z],'-','_' 365 | # 366 | # $1 - Root filesystem label 367 | # 368 | check_filesystem_label() { 369 | local size=${#1} 370 | local count=0 371 | if [ ${size} -gt 12 ]; then 372 | exit_on_error "Number of character exceed maximal size : 12 characters max" 373 | fi 374 | if [[ "${1}" =~ ([0-9A-Za-z_-]{$size}) ]]; then 375 | : 376 | else 377 | exit_on_error "Invalid character used in the name given to filesystem : character must be a number, a letter '_' or '-'" 378 | fi 379 | } 380 | 381 | # 382 | # Create three partitions on the drive: 383 | # - 1 Boot partition , EFI or MBR type 384 | # - 1 CIDATA partition (cloud-init data partition) 385 | # - 1 Linux system partition to contain Linux filesystem 386 | # Everything on the drive is wiped beforehand. 387 | # 388 | system_partitionning() { 389 | log_debug "-> ${FUNCNAME[0]} $*" 390 | 391 | gdisk ${BLOCK_DEVICE} <<- EOF 392 | o 393 | Y 394 | n 395 | 1 396 | 397 | +${BOOT_PARTITION_SIZE} 398 | ${CODE_PARTITIONNING_BOOT} 399 | yes 400 | n 401 | 2 402 | 403 | +${CIDATA_PARTITION_SIZE} 404 | ${CODE_PARTITIONNING} 405 | n 406 | 3 407 | 408 | 409 | ${CODE_PARTITIONNING} 410 | wq 411 | yes 412 | EOF 413 | } 414 | 415 | # 416 | # Format the 3 partitions: 417 | # - 1st partition in FAT32 if EFI type / No formating if MBR type 418 | # - 2nd partition in FAT 32 among cloud-init requirements 419 | # - 3rd partition in ext4 or btrfs among user requirements 420 | # 421 | partitions_formating() { 422 | log_debug "-> ${FUNCNAME[0]} $*" 423 | 424 | if [ "${BOOT_MODE}" == "uefi" ]; then 425 | mkfs.fat -F 32 -n EFI ${EFI_PARTITION} 426 | fi 427 | 428 | mkfs.fat -F 32 -n cidata ${CIDATA_PARTITION} 429 | 430 | if [ "${ROOTFS_TYPE}" == "ext4" ]; then 431 | mkfs.ext4 -q -L ${ROOTFS_LABEL} ${LINUX_PARTITION} <<- EOF 432 | y 433 | EOF 434 | elif [ "${ROOTFS_TYPE}" == "btrfs" ]; then 435 | mkfs.btrfs -f -q -L ${ROOTFS_LABEL} ${LINUX_PARTITION} 436 | else 437 | exit_on_error "Filesystem type '${FILE_SYSTEM_TYPE}' unknown or not supported" 438 | fi 439 | } 440 | 441 | # 442 | # This function check whether a string value 443 | # reprensents a `true` boolean value or not 444 | # 445 | # $1 - string value representing a boolean 446 | # 447 | isTrue() { 448 | if [ "${1,,}" = 'true' ]; then 449 | return 0 450 | fi 451 | return 1 452 | } 453 | 454 | # 455 | # This function check whether a string value 456 | # reprensents a `false` boolean value or not 457 | # 458 | # $1 - string value representing a boolean 459 | # 460 | isFalse() { 461 | if ! isTrue ${1}; then 462 | return 0 463 | fi 464 | return 1 465 | } 466 | 467 | # 468 | # Create root directory rootfs 469 | # Mounts the root file system partition on this one 470 | # Create two directories in it : boot and inside efi 471 | # Mounts efi partition on efi directory 472 | # 473 | partitions_mounting() { 474 | log_debug "-> ${FUNCNAME[0]} $*" 475 | 476 | if [ -e ${rootfs} ]; then 477 | rm -rf ${rootfs} 478 | fi 479 | mkdir ${rootfs} 480 | mount ${LINUX_PARTITION} ${rootfs} 481 | if [ "${BOOT_MODE}" == "uefi" ]; then 482 | mkdir -p ${rootfs}/boot/efi 483 | mount ${EFI_PARTITION} ${rootfs}/boot/efi 484 | fi 485 | mkdir -p ${cloudfs} 486 | rm -rf ${cloudfs}/* 487 | mount ${CIDATA_PARTITION} ${cloudfs} 488 | } 489 | 490 | # 491 | # Download the file containing the root file system in /tmp directory and named it as linux_rootfs. 492 | # 493 | # The type supported are compressed archive like .tar.gz and .tar.xz, squashfs, qcow2. 494 | # 495 | # Analyses the type of root file system file and call function for extracting and copying the file system depending on the type. 496 | # 497 | linux_rootfs_installation() { 498 | log_debug "-> ${FUNCNAME[0]} $*" 499 | 500 | linux_image_dir=/mnt/image 501 | linux_image=/tmp/linux-rootfs 502 | 503 | wget --quiet -O ${linux_image} ${LINUX_ROOTFS_URL} 504 | 505 | if [ -e ${linux_image_dir} ]; then 506 | rm -rf ${linux_image_dir} 507 | fi 508 | 509 | mkdir ${linux_image_dir} 510 | 511 | if [ -n "$(file ${linux_image} | grep XZ)" ]; then 512 | archiveTar_installation "xfJ" 513 | elif [ -n "$(file ${linux_image} | grep gzip)" ]; then 514 | archiveTar_installation "xzf" 515 | elif [ -n "$(file ${linux_image} | grep Squashfs)" ]; then 516 | rm -rf ${linux_image_dir} 517 | squashfs_installation 518 | elif [ -n "$(file ${linux_image} | grep QCOW)" ]; then 519 | qcow2_installation 520 | fi 521 | } 522 | 523 | archiveTar_installation() { 524 | log_debug "-> ${FUNCNAME[0]} $*" 525 | 526 | ( 527 | cd ${linux_image_dir} 528 | tar ${1} ${linux_image} 529 | ) 530 | cp -rp ${linux_image_dir}/* ${rootfs} 531 | } 532 | 533 | squashfs_installation() { 534 | log_debug "-> ${FUNCNAME[0]} $*" 535 | 536 | unsquashfs -d ${linux_image_dir} ${linux_image} 537 | cp -rp ${linux_image_dir}/* ${rootfs} 538 | } 539 | 540 | qcow2_installation() { 541 | log_debug "-> ${FUNCNAME[0]} $*" 542 | 543 | guestmount -a ${linux_image} -m /dev/sda1 ${linux_image_dir} 544 | 545 | cp -rp ${linux_image_dir}/* ${rootfs} 546 | 547 | umount ${linux_image_dir} 548 | } 549 | 550 | bootloader_installation() { 551 | log_debug "-> ${FUNCNAME[0]} $*" 552 | 553 | if [ "${BOOT_MODE}" == "uefi" ]; then 554 | bootloader_installation_uefi 555 | elif [ "${BOOT_MODE}" == "legacy" ]; then 556 | grub-install --root-directory=${rootfs} ${BLOCK_DEVICE} 557 | fi 558 | } 559 | 560 | bootloader_installation_uefi() { 561 | log_debug "-> ${FUNCNAME[0]} $*" 562 | 563 | # bootloader_name is used to name the bootloader folder 564 | # in the EFI partition. For now, it has to be 'ubuntu' and 565 | # cannot be changed as long we rely on Grub EFI comming from 566 | # Cannonical because some paths are hardcoded into binaries. 567 | local bootloader_name=ubuntu 568 | 569 | local bootloader_dir=${rootfs}/boot/efi/EFI/${bootloader_name} 570 | local bootloader_archive_file=/tmp/efi.tar.gz 571 | local change_boot_order=$(search_value ".bootloader.change_boot_order" "persistent") 572 | 573 | wget --quiet -O ${bootloader_archive_file} ${EFI_ARCHIVE_URL} 574 | tar xvzf ${bootloader_archive_file} -C ${rootfs}/boot/efi 575 | rm -f ${bootloader_archive_file} 576 | cat <<- EOF > ${bootloader_dir}/grub.cfg 577 | search --label ${ROOTFS_LABEL} --set 578 | set prefix=(\$root)'/boot/grub2' 579 | configfile \$prefix/grub.cfg 580 | EOF 581 | 582 | efibootmgr -c -d ${BLOCK_DEVICE} -p 1 -L "${EFI_ENTRY_LABEL}" -l "\EFI\\${bootloader_name}\shimx64.efi" 583 | 584 | case "${change_boot_order}" in 585 | "persistent") 586 | local boot_num=$(efibootmgr -v | grep "${EFI_ENTRY_LABEL}" | grep 'GPT' | grep -o -e "Boot[0-9]*" | sed -r -e "s/Boot//") 587 | efibootmgr -o ${boot_num} 588 | ;; 589 | "once") 590 | local boot_num=$(efibootmgr -v | grep "${EFI_ENTRY_LABEL}" | grep 'GPT' | grep -o -e "Boot[0-9]*" | sed -r -e "s/Boot//") 591 | efibootmgr -n ${boot_num} 592 | ;; 593 | "none") 594 | if isFalse ${PXE_PILOT_ENABLED}; then 595 | log_warning "Option .pxePilot.enable and .bootloader.change_boot_order configured on false, Sheep won't manage changing of boot order" 596 | fi 597 | ;; 598 | *) 599 | exit_on_error "Incorrect value for .bootloader.change_boot_order" 600 | ;; 601 | esac 602 | } 603 | 604 | # 605 | # This function is useful to erase unecessary efi boot entry 606 | # 607 | # - Unecessary boot entry are those which have been added with a path to the bootloader 608 | # - Avoid having bug after reboot 609 | # 610 | efi_entry_cleanup() { 611 | log_debug "-> ${FUNCNAME[0]} $*" 612 | 613 | num=$(efibootmgr -v | grep "File" | cut -d ' ' -f 1 | grep "0" | cut -d 't' -f 2 | cut -d '*' -f 1) 614 | N=$(echo ${num} | wc -w) 615 | for i in $(seq 1 ${N}); do 616 | entry=$(echo ${num} | cut -d ' ' -f ${i}) 617 | efibootmgr -b ${entry} -B 618 | done 619 | } 620 | 621 | # 622 | # Write fstab file anew to match present drive configuration 623 | # 624 | configure_fstab() { 625 | log_debug "-> ${FUNCNAME[0]} $*" 626 | 627 | cat <<- EOF > ${rootfs}/etc/fstab 628 | LABEL=${ROOTFS_LABEL} / ${ROOTFS_TYPE} defaults 0 0 629 | EOF 630 | 631 | if [ "${BOOT_MODE}" == "uefi" ]; then 632 | cat <<- EOF >> ${rootfs}/etc/fstab 633 | LABEL=EFI /boot/efi vfat defaults 0 0 634 | EOF 635 | fi 636 | cat <<- EOF >> ${rootfs}/etc/fstab 637 | LABEL=cidata vfat defaults 0 0 638 | EOF 639 | } 640 | 641 | # 642 | # Create file meta-data at root of cidata partition with values given in sheep config file 643 | # Thus instance-id and machine hostname 644 | # 645 | config_metadata_ci_disable() { 646 | log_debug "-> ${FUNCNAME[0]} $*" 647 | 648 | local instanceId=$(search_value ".cloudInit.instance_id") 649 | local localHostname=$(search_value ".environment.local_hostname") 650 | 651 | if [ -z "${instanceId}" ]; then 652 | instanceId=$(date +%s) 653 | fi 654 | echo "instance-id: ${instanceId}" > ${cloudfs}/meta-data 655 | if [ -n "${localHostname}" ]; then 656 | echo "local-hostname: ${localHostname}" >> ${cloudfs}/meta-data 657 | fi 658 | } 659 | 660 | # 661 | # Create file network-config at root of cidata partition with values given in sheep config file 662 | # Thus network config version, interfaces id, and network config for these interfaces 663 | # 664 | config_network_config_ci_disable() { 665 | local c=0 666 | local interface=$(search_value ".network.interfaces[${c}].id") 667 | local mode=$(search_value ".network.interfaces[${c}].mode") 668 | 669 | if [ -z "${interface}" ]; then 670 | return 671 | fi 672 | cat <<- EOF > ${cloudfs}/network-config 673 | version: 1 674 | config: 675 | EOF 676 | while ! [ -z "${interface}" ]; do 677 | cat <<- EOF >> ${cloudfs}/network-config 678 | - name: ${interface} 679 | type: physical 680 | subnets: 681 | EOF 682 | if [ "${mode}" == "dhcp" ]; then 683 | cat <<- EOF >> ${cloudfs}/network-config 684 | - type: dhcp 685 | EOF 686 | elif [ "${mode}" == "static" ]; then 687 | local address=$(search_mandatory_value ".network.interfaces[${c}].address") 688 | local ret=$? 689 | if [ ${ret} -ne 0 ]; then 690 | exit ${ret} 691 | fi 692 | local gateway=$(search_value ".network.interfaces[${c}].gateway") 693 | cat <<- EOF >> ${cloudfs}/network-config 694 | - address: ${address} 695 | EOF 696 | if [ -n "${gateway}" ]; then 697 | cat <<- EOF >> ${cloudfs}/network-config 698 | gateway: ${gateway} 699 | EOF 700 | fi 701 | cat <<- EOF >> ${cloudfs}/network-config 702 | type: static 703 | EOF 704 | else 705 | exit_on_error "Network mode must be 'dhcp' or 'static' in cloud-init disable mode" 706 | fi 707 | c=$((${c} + 1)) 708 | interface=$(search_value ".network.interfaces[${c}].id") 709 | mode=$(search_value ".network.interfaces[${c}].mode") 710 | done 711 | } 712 | 713 | # 714 | # Create file user-data at root of cidata partition with values given in sheep config file 715 | # Thus all informations relative to a linux user 716 | # 717 | config_userdata_ci_disable() { 718 | log_debug "-> ${FUNCNAME[0]} $*" 719 | 720 | local c=0 721 | local user=$(search_value ".environment.users[${c}]") 722 | local sshCfg=false 723 | local passwordCfg=false 724 | local defaultUser=false 725 | 726 | cat <<- EOF > ${cloudfs}/user-data 727 | #cloud-config 728 | users: 729 | EOF 730 | 731 | if [ -n "${user}" ]; then 732 | while ! [ -z "${user}" ]; do 733 | local name=$(search_value ".environment.users[${c}].name") 734 | local sudoer=$(search_value ".environment.users[${c}].sudoer") 735 | local ssh=$(search_value ".environment.users[${c}].ssh_authorized_key") 736 | local password=$(search_value ".environment.users[${c}].password") 737 | local shell=$(search_value ".environment.users[${c}].shell") 738 | cat <<- EOF >> ${cloudfs}/user-data 739 | - name: ${name} 740 | lock_passwd: false 741 | EOF 742 | if [ -n "${ssh}" ]; then 743 | echo " ssh_authorized_keys: ${ssh}" >> ${cloudfs}/user-data 744 | sshCfg=true 745 | fi 746 | if [ -n "${password}" ]; then 747 | passwordCfg=true 748 | fi 749 | if isTrue ${sudoer}; then 750 | echo " sudo: ALL=(ALL) NOPASSWD:ALL" >> ${cloudfs}/user-data 751 | fi 752 | if [ -n "${shell}" ]; then 753 | echo " shell: ${shell}" >> ${cloudfs}/user-data 754 | fi 755 | c=$((${c} + 1)) 756 | user=$(search_value ".environment.users[${c}]") 757 | done 758 | else 759 | defaultUser=true 760 | cat <<- EOF >> ${cloudfs}/user-data 761 | - name: linux 762 | lock_passwd: false 763 | sudo: ALL=(ALL) NOPASSWD:ALL 764 | shell: /bin/bash 765 | EOF 766 | fi 767 | 768 | if isFalse "${passwordCfg}" && isFalse "${sshCfg}" && isFalse "${defaultUser}"; then 769 | exit_on_error "ERROR: No password and no ssh key given in config file, you wont be able to login after reboot" 770 | fi 771 | if isFalse "${passwordCfg}" && isFalse "${defaultUser}"; then 772 | return 773 | fi 774 | cat <<- EOF >> ${cloudfs}/user-data 775 | chpasswd: 776 | expire: false 777 | list : | 778 | EOF 779 | if isTrue ${defaultUser}; then 780 | cat <<- EOF >> ${cloudfs}/user-data 781 | linux:linux 782 | EOF 783 | else 784 | N=$((${c} - 1)) 785 | for i in $(seq 0 ${N}); do 786 | name=$(search_value ".environment.users[${i}].name") 787 | password=$(search_value ".environment.users[${i}].password") 788 | if [ -n "${password}" ]; then 789 | echo " ${name}:${password}" >> ${cloudfs}/user-data 790 | fi 791 | done 792 | if [ -n "${ssh}" ]; then 793 | cat <<- EOF >> ${cloudfs}/user-data 794 | ssh_pwauth: true 795 | EOF 796 | fi 797 | fi 798 | } 799 | 800 | # 801 | # Create file meta-data at root of cidata partition with values given in sheep config file 802 | # Thus instance-id and machine hostname 803 | # 804 | config_metadata_ci_enable() { 805 | log_debug "-> ${FUNCNAME[0]} $*" 806 | 807 | search_value ".cloudInit.metaData" "" "yaml" >> ${cloudfs}/meta-data 808 | } 809 | 810 | # 811 | # Create file network-config at root of cidata partition with values given in sheep config file 812 | # Thus network config version, interfaces id, and network config for these interfaces 813 | # 814 | config_network_config_ci_enable() { 815 | log_debug "-> ${FUNCNAME[0]} $*" 816 | 817 | search_value ".cloudInit.networkConfig" "" "yaml" >> ${cloudfs}/network-config 818 | } 819 | 820 | # 821 | # Create file user-data at root of cidata partition with values given in sheep config file 822 | # Thus all informations relative to a linux user 823 | # 824 | config_userdata_ci_enable() { 825 | log_debug "-> ${FUNCNAME[0]} $*" 826 | 827 | echo "#cloud-config" > ${cloudfs}/user-data 828 | search_value ".cloudInit.userData" "" "yaml" >> ${cloudfs}/user-data 829 | } 830 | 831 | configure_environment() { 832 | log_debug "-> ${FUNCNAME[0]} $*" 833 | 834 | if isTrue "${CLOUD_INIT_ENABLED}"; then 835 | log " cloud-init config | Configure meta-data file" 836 | config_metadata_ci_enable 837 | 838 | log " cloud-init config | Configure user-data file" 839 | config_userdata_ci_enable 840 | 841 | log " cloud-init config | Configure network-config file" 842 | config_network_config_ci_enable 843 | else 844 | log " cloud-init config | Configure meta-data file" 845 | config_metadata_ci_disable 846 | 847 | log " cloud-init config | Configure user-data file" 848 | config_userdata_ci_disable 849 | 850 | log " cloud-init config | Configure network-config file" 851 | config_network_config_ci_disable 852 | fi 853 | } 854 | 855 | blacklist_module() { 856 | log_debug "-> ${FUNCNAME[0]} $*" 857 | 858 | local i=0 859 | local mod=$(search_value ".linux.blacklist_module[${i}]") 860 | while ! [ -z "${mod}" ]; do 861 | echo "blacklist ${mod}" >> ${rootfs}/etc/modprobe.d/blacklist.conf 862 | i=$((${i} + 1)) 863 | mod=$(search_value ".linux.blacklist_module[${i}]") 864 | done 865 | } 866 | 867 | linux_rootfs_configuration() { 868 | log_debug "-> ${FUNCNAME[0]} $*" 869 | 870 | log "Configuring Linux | Configure partitions mount in /etc/fstab" 871 | configure_fstab 872 | 873 | log "Configuring Linux | Configure GRUB bootloader" 874 | rootfs_bootloader_configuration 875 | 876 | log "Configuring Linux | Configure Cloud-init" 877 | configure_environment 878 | 879 | log "Configuring Linux | Blacklist modules" 880 | blacklist_module 881 | } 882 | 883 | # 884 | # Verification of grub.cfg file presence 885 | # Existing grub.cfg file is used to check the path to the kernel an initrd files 886 | # These paths are used to recreate a new grub.cfg file 887 | # 888 | rootfs_bootloader_configuration() { 889 | log_debug "-> ${FUNCNAME[0]} $*" 890 | 891 | local grubFile=${rootfs}/boot/grub2/grub.cfg 892 | local legacyGrubFile=${rootfs}/boot/grub/grub.cfg 893 | 894 | if [ -e ${grubFile} ]; then 895 | cp ${grubFile} ${grubFile}.bak 896 | if [ ${BOOT_MODE} == "legacy" ] && [ ! -e ${legacyGrubFile} ]; then 897 | (cd ${rootfs}/boot/grub2 && ln -s ./../grub2/grub.cfg ./../grub/grub.cfg) 898 | fi 899 | else 900 | if [ ! -e ${legacyGrubFile} ]; then 901 | exit_on_error "Unable to locate GRUB config file" 902 | fi 903 | mkdir -p ${rootfs}/boot/grub2/ 904 | cp ${legacyGrubFile} ${grubFile} 905 | mv ${legacyGrubFile} ${legacyGrubFile}.bak 906 | (cd ${rootfs}/boot/grub && ln -s ../grub2/grub.cfg .) 907 | fi 908 | 909 | local kernel=$(grep -o -m 1 -e 'linux\(16\)*\s*[^/]*/boot/[^ ]*' ${grubFile} | sed -e's#.*\(/boot/.*\)#\1#') 910 | local initrd=$(grep -o -m 1 -e 'initrd\(16\)*\s*[^/]*/boot/[^ ]*' ${grubFile} | sed -e's#.*\(/boot/.*\)#\1#') 911 | 912 | if [[ -z "${kernel}" || -z ${initrd} ]]; then 913 | # TODO Handle files in /mnt/boot/loader/entries/ for Fedora 914 | # e.g. /mnt/boot/loader/entries/f241772f3e32496c92975269b5794615-5.0.9-301.fc30.x86_64.conf 915 | : 916 | if [[ -z "${kernel}" || -z ${initrd} ]]; then 917 | exit_on_error "Cannot find kernel or initrd file path" 918 | fi 919 | fi 920 | 921 | cat <<- EOF > ${grubFile} 922 | default=0 923 | timeout=5 924 | 925 | menuentry '${OS_NAME}' { 926 | insmod gzio 927 | search --label ${ROOTFS_LABEL} --set 928 | linux ${kernel} root=LABEL=${ROOTFS_LABEL} ro ${KERNEL_PARAMETER} ds=nocloud 929 | initrd ${initrd} 930 | } 931 | EOF 932 | } 933 | 934 | # 935 | # This function disable the SElinux service in the configuration file by default 936 | # If selinux variable is set to enable, it creates the file .autorelabel at the filesystem root 937 | # 938 | # Indeed selinux uses extended attributes 939 | # and by copying the rootfile system as sheep does, it comes to a problem on it 940 | # 941 | # So SElinux is locking every access to the OS. You cannot log after reboot. 942 | # Having the file .autorelabel after reboot involves two reboots before beeing able to login. 943 | # At the first reboot, the presence of .autorelabel launch the relabelling of all files. 944 | # Then the system reboot and you're able to login. 945 | # 946 | SElinux_configuration() { 947 | log_debug "-> ${FUNCNAME[0]} $*" 948 | 949 | local config_file=${rootfs}/etc/selinux/config 950 | if [ -e ${config_file} ]; then 951 | if [ "${SELINUX}" == "enable" ]; then 952 | touch ${rootfs}/.autorelabel 953 | else 954 | sed -i -e 's/SELINUX=enforcing/SELINUX=disabled/' ${config_file} 955 | fi 956 | fi 957 | } 958 | 959 | partitions_unmounting() { 960 | log_debug "-> ${FUNCNAME[0]} $*" 961 | 962 | cd / 963 | umount -R ${rootfs} 964 | umount ${cloudfs} 965 | } 966 | 967 | # 968 | # Server try to reach the pxe-pilot server on one of its interface to execute a curl command 969 | # and change the boot file after reboot 970 | # 971 | notify_pxepilot() { 972 | log_debug "-> ${FUNCNAME[0]} $*" 973 | 974 | local code=0 975 | while read mac; do 976 | code=$(curl -s -o /dev/null -w '%{http_code}' -i -X PUT "${PXE_PILOT_BASEURL}/v1/configurations/${PXE_PILOT_CFG}/deploy" -d '{"hosts":[{"macAddress":"'"${mac}"'"}]}') 977 | if [ "${code}" == "200" ]; then 978 | break 979 | fi 980 | done < <(ip a | awk '/link\/ether /{ print $2 }' | sort -u) 981 | 982 | if [ "${code}" != "200" ]; then 983 | exit_on_error "PXE Pilot API call error" 984 | fi 985 | } 986 | 987 | print_banner() { 988 | if [ -z "${SHEEP_SERIAL}" ]; then 989 | return 990 | fi 991 | 992 | { 993 | echo '' 994 | echo ' ___ _ _ ___ ___ ___ ' 995 | echo ' / __| | || | | __| | __| | _ \' 996 | echo ' \__ \ | __ | | _| | _| | _/' 997 | echo ' |___/ |_||_| |___| |___| |_|' 998 | echo '' 999 | 1000 | } > ${SHEEP_SERIAL} 1001 | } 1002 | 1003 | # 1004 | # Return which console serial device is used 1005 | # 1006 | get_serial_device() { 1007 | local device=$(search_kernel_parameter console | grep -e '^ttyS' | sed 's/,/ /g' | awk '{ print $1 }') 1008 | if [ -z ${device} ]; then 1009 | return 1010 | fi 1011 | echo "/dev/${device}" 1012 | } 1013 | 1014 | main() { 1015 | SHEEP_SERIAL=$(get_serial_device) 1016 | 1017 | init_logger 1018 | 1019 | log_debug "-> ${FUNCNAME[0]} $*" 1020 | 1021 | SHEEP_DELAY=$(search_kernel_parameter "sheep.delay" "0") 1022 | if [ ${SHEEP_DELAY} -gt 0 ]; then 1023 | log "Linux installation will start in ${SHEEP_DELAY} second(s)..." 1024 | sleep ${SHEEP_DELAY} 1025 | fi 1026 | 1027 | print_banner 1028 | 1029 | log "Preparing tools required for installation" 1030 | prepare_env 1031 | 1032 | log "Download configuration file" 1033 | load_config 1034 | 1035 | log "Reading input configuration" 1036 | config_variable 1037 | 1038 | log "Downloading tools required by OS installtion" 1039 | download_tools 1040 | 1041 | log "Starting installation process" 1042 | 1043 | rootfs=/mnt/rootfs 1044 | cloudfs=/mnt/cloudfs 1045 | 1046 | if [ "${BOOT_MODE}" == "uefi" ]; then 1047 | log "Cleaning local boot EFI entries from the EFI Boot Manager" 1048 | efi_entry_cleanup 1049 | fi 1050 | 1051 | log "Erasing drive an creating the partition table" 1052 | system_partitionning 1053 | 1054 | log "Formating partitions" 1055 | partitions_formating 1056 | 1057 | log "Mount partition in read-write mode" 1058 | partitions_mounting 1059 | 1060 | log "Installing Linux root filesystem into the Linux partition" 1061 | linux_rootfs_installation 1062 | 1063 | log "Installing bootloader" 1064 | bootloader_installation 1065 | 1066 | log "Configuring Linux" 1067 | linux_rootfs_configuration 1068 | 1069 | log "Configuring SELinux if present" 1070 | SElinux_configuration 1071 | 1072 | log "Unmounting partitions" 1073 | partitions_unmounting 1074 | 1075 | if isTrue "${PXE_PILOT_ENABLED}"; then 1076 | log "Notifying PXE Pilot" 1077 | notify_pxepilot 1078 | fi 1079 | 1080 | log "Installation complete" 1081 | touch /var/run/sheep.success # Marker to indicate installation is successful 1082 | 1083 | if isTrue "${REBOOT_WHEN_DONE}"; then 1084 | log "Rebooting system..." 1085 | reboot 1086 | else 1087 | log_warning "System will not reboot by itself (disabled in Sheep configuration)" 1088 | fi 1089 | } 1090 | 1091 | if [ "$(basename $0)" = "sheep" ]; then 1092 | set -x 1093 | main >> /var/log/sheep.log 2>&1 1094 | fi 1095 | -------------------------------------------------------------------------------- /tests/integration_test/centOS7_legacy_CID_winterfell.yml: -------------------------------------------------------------------------------- 1 | bootloader: 2 | change_boot_order: none 3 | kernel_parameter: "console=ttyS1,115200n8" 4 | 5 | linux: 6 | image: http://@@SHEEP_CI_RUNNER_IP@@/CentOS-7-x86_64-GenericCloud-1907.qcow2 7 | label: CentOS 7 8 | device: /dev/sda 9 | rootfsType: ext4 10 | rootfsLabel: centos-fs 11 | selinux: disable 12 | blacklist_module: 13 | - mei 14 | - mei_me 15 | 16 | network: 17 | interfaces: 18 | - id: enp12s0 19 | mode: dhcp 20 | - id: ens9 21 | mode: static 22 | address: 172.19.17.111 23 | gateway: 172.19.17.1 24 | 25 | pxePilot: 26 | enable: true 27 | url: http://@@SHEEP_CI_RUNNER_IP@@:3478 28 | config_after_reboot: local 29 | 30 | environment: 31 | users: 32 | - name: linux 33 | sudoer: true 34 | password: linux 35 | ssh_authorized_key: @@SHEEP_CI_SSH_PUB_KEY@@ 36 | shell: /bin/bash 37 | local_hostname: sheep 38 | 39 | cloudInit: 40 | enable: false 41 | instance_id: 001-local01 42 | 43 | sheep: 44 | reboot: false 45 | -------------------------------------------------------------------------------- /tests/integration_test/centOS7_legacy_CIE_winterfell.yml: -------------------------------------------------------------------------------- 1 | bootloader: 2 | change_boot_order: none 3 | kernel_parameter: "console=ttyS1,115200n8" 4 | 5 | linux: 6 | image: http://@@SHEEP_CI_RUNNER_IP@@/CentOS-7-x86_64-GenericCloud-1907.qcow2 7 | label: CentOS 7 8 | device: /dev/sda 9 | rootfsType: ext4 10 | rootfsLabel: centos-fs 11 | selinux: disable 12 | blacklist_module: 13 | - mei 14 | - mei_me 15 | 16 | pxePilot: 17 | enable: true 18 | url: http://@@SHEEP_CI_RUNNER_IP@@:3478 19 | config_after_reboot: local 20 | 21 | cloudInit: 22 | enable: true 23 | metaData: 24 | instance-id: 001-local01 25 | local-hostname: sheep 26 | networkConfig: 27 | version: 2 28 | ethernets: 29 | enp12s0: 30 | dhcp4: true 31 | ens9: 32 | addresses: 33 | - 172.19.17.111/24 34 | gateway4: 172.19.17.1 35 | userData: 36 | users: 37 | - name: linux 38 | lock_passwd: false 39 | ssh_authorized_keys: @@SHEEP_CI_SSH_PUB_KEY@@ 40 | sudo: ALL=(ALL) NOPASSWD:ALL 41 | shell: /bin/bash 42 | chpasswd: 43 | expire: false 44 | list: | 45 | linux:linux 46 | ssh_pwauth: true 47 | sheep: 48 | reboot: false 49 | -------------------------------------------------------------------------------- /tests/integration_test/centOS7_uefi_CID_leopard.yml: -------------------------------------------------------------------------------- 1 | bootloader: 2 | change_boot_order: none 3 | image: http://@@SHEEP_CI_RUNNER_IP@@/grub.tar.gz 4 | kernel_parameter: "console=ttyS1,57600n8" 5 | 6 | linux: 7 | image: http://@@SHEEP_CI_RUNNER_IP@@/CentOS-7-x86_64-GenericCloud-1907.qcow2 8 | label: CentOS 7 9 | device: /dev/sda 10 | rootfsType: ext4 11 | rootfsLabel: centos-fs 12 | selinux: disable 13 | 14 | network: 15 | interfaces: 16 | - id: ens1 17 | mode: dhcp 18 | 19 | pxePilot: 20 | enable: true 21 | url: http://@@SHEEP_CI_RUNNER_IP@@:3478 22 | config_after_reboot: local 23 | 24 | cloudInit: 25 | enable: true 26 | metaData: 27 | instance-id: 001-local01 28 | local-hostname: sheep 29 | userData: 30 | users: 31 | - name: linux 32 | lock_passwd: false 33 | ssh_authorized_keys: @@SHEEP_CI_SSH_PUB_KEY@@ 34 | sudo: ALL=(ALL) NOPASSWD:ALL 35 | shell: /bin/bash 36 | chpasswd: 37 | expire: false 38 | list: | 39 | linux:linux 40 | ssh_pwauth: true 41 | 42 | sheep: 43 | reboot: false 44 | -------------------------------------------------------------------------------- /tests/integration_test/debian9_legacy_CID_leopard.yml: -------------------------------------------------------------------------------- 1 | bootloader: 2 | change_boot_order: none 3 | image: http://@@SHEEP_CI_RUNNER_IP@@/grub.tar.gz 4 | kernel_parameter: "console=ttyS1,57600n8" 5 | 6 | linux: 7 | image: http://@@SHEEP_CI_RUNNER_IP@@/debian-9.11.2-20190926-openstack-amd64.qcow2 8 | label: Debian 9 9 | device: /dev/sda 10 | rootfsType: ext4 11 | rootfsLabel: debian9-fs 12 | selinux: disable 13 | 14 | network: 15 | interfaces: 16 | - id: ens1 17 | mode: dhcp 18 | 19 | pxePilot: 20 | enable: true 21 | url: http://@@SHEEP_CI_RUNNER_IP@@:3478 22 | config_after_reboot: local 23 | 24 | environment: 25 | users: 26 | - name: linux 27 | sudoer: true 28 | password: linux 29 | ssh_authorized_key: @@SHEEP_CI_SSH_PUB_KEY@@ 30 | shell: /bin/bash 31 | local_hostname: sheep 32 | 33 | cloudInit: 34 | enable: false 35 | instance_id: 001-local01 36 | 37 | sheep: 38 | reboot: false 39 | -------------------------------------------------------------------------------- /tests/integration_test/debian9_legacy_CID_winterfell.yml: -------------------------------------------------------------------------------- 1 | bootloader: 2 | change_boot_order: none 3 | kernel_parameter: "console=ttyS1,115200n8" 4 | 5 | linux: 6 | image: http://@@SHEEP_CI_RUNNER_IP@@/debian-9.11.2-20190926-openstack-amd64.qcow2 7 | label: Debian 9 8 | device: /dev/sda 9 | rootfsType: ext4 10 | rootfsLabel: debian9-fs 11 | selinux: disable 12 | blacklist_module: 13 | - mei 14 | - mei_me 15 | 16 | network: 17 | interfaces: 18 | - id: enp12s0 19 | mode: dhcp 20 | - id: ens9 21 | mode: static 22 | address: 172.19.17.111 23 | gateway: 172.19.17.1 24 | 25 | pxePilot: 26 | enable: true 27 | url: http://@@SHEEP_CI_RUNNER_IP@@:3478 28 | config_after_reboot: local 29 | 30 | environment: 31 | users: 32 | - name: linux 33 | sudoer: true 34 | password: linux 35 | ssh_authorized_key: @@SHEEP_CI_SSH_PUB_KEY@@ 36 | shell: /bin/bash 37 | local_hostname: sheep 38 | 39 | cloudInit: 40 | enable: false 41 | instance_id: 001-local01 42 | 43 | sheep: 44 | reboot: false 45 | -------------------------------------------------------------------------------- /tests/integration_test/debian9_legacy_CIE_winterfell.yml: -------------------------------------------------------------------------------- 1 | bootloader: 2 | change_boot_order: none 3 | kernel_parameter: "console=ttyS1,115200n8" 4 | 5 | linux: 6 | image: http://@@SHEEP_CI_RUNNER_IP@@/debian-9.11.2-20190926-openstack-amd64.qcow2 7 | label: Debian 9 8 | device: /dev/sda 9 | rootfsType: ext4 10 | rootfsLabel: debian9-fs 11 | selinux: disable 12 | blacklist_module: 13 | - mei 14 | - mei_me 15 | 16 | network: 17 | interfaces: 18 | - id: enp12s0 19 | 20 | pxePilot: 21 | enable: true 22 | url: http://@@SHEEP_CI_RUNNER_IP@@:3478 23 | config_after_reboot: local 24 | 25 | cloudInit: 26 | enable: true 27 | metaData: 28 | instance-id: 001-local01 29 | local-hostname: sheep 30 | networkConfig: 31 | version: 1 32 | config: 33 | - id: enp12s0 34 | type: physical 35 | name: enp12s0 36 | subnets: 37 | - type: dhcp 38 | - id: ens9 39 | type: physical 40 | name: ens9 41 | subnets: 42 | - address: 172.19.17.111/24 43 | gateway: 172.19.17.1 44 | type: static 45 | userData: 46 | users: 47 | - name: linux 48 | lock_passwd: false 49 | ssh_authorized_keys: @@SHEEP_CI_SSH_PUB_KEY@@ 50 | sudo: ALL=(ALL) NOPASSWD:ALL 51 | shell: /bin/bash 52 | chpasswd: 53 | expire: false 54 | list: | 55 | linux:linux 56 | ssh_pwauth: true 57 | sheep: 58 | reboot: false 59 | -------------------------------------------------------------------------------- /tests/integration_test/network.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_tool 4 | 5 | @test "test network protocol mode of interface" { 6 | file_path=$(search_value "${CURRENT_KEY}".data.filePath) 7 | mode=$(search_value "${CURRENT_KEY}".data.mode) 8 | 9 | run ssh_cmd "${IP}" "${USER}" "${PASSWORD}" "cat ${file_path}" 10 | 11 | [ ${status} -eq 0 ] 12 | [ $(echo "${output}" | grep "${mode}" | wc -l) -ge 1 ] 13 | } 14 | 15 | @test "test ip address value if given" { 16 | 17 | static_add=$(search_value ."${CURRENT_KEY}".data.ipAdd) 18 | 19 | if [ "${static_add}" == "null" ]; then 20 | skip "No static address to test" 21 | fi 22 | 23 | run ssh_cmd "${IP}" "${USER}" "${PASSWORD}" "sudo ip add" 24 | 25 | [ ${status} -eq 0 ] 26 | [ $(echo "${output}" | grep "${static_add}/" | wc -l) -ge 1 ] 27 | } 28 | -------------------------------------------------------------------------------- /tests/integration_test/openSUSE15_1_legacy_CID_winterfell.yml: -------------------------------------------------------------------------------- 1 | bootloader: 2 | change_boot_order: none 3 | kernel_parameter: "console=ttyS4,115200n8" 4 | 5 | linux: 6 | image: http://@@SHEEP_CI_RUNNER_IP@@/openSUSE-Leap-15.1-OpenStack.x86_64.qcow2 7 | label: OpenSUSE 15.1 8 | device: /dev/sda 9 | rootfsType: btrfs 10 | rootfsLabel: openSUSE-fs 11 | selinux: disable 12 | blacklist_module: 13 | - mei 14 | - mei_me 15 | 16 | network: 17 | interfaces: 18 | - id: eth0 19 | mode: dhcp 20 | - id: eth1 21 | mode: static 22 | address: 172.19.17.111 23 | gateway: 172.19.17.1 24 | 25 | pxePilot: 26 | enable: true 27 | url: http://@@SHEEP_CI_RUNNER_IP@@:3478 28 | config_after_reboot: local 29 | 30 | environment: 31 | users: 32 | - name: linux 33 | sudoer: true 34 | password: linux 35 | ssh_authorized_key: @@SHEEP_CI_SSH_PUB_KEY@@ 36 | shell: /bin/bash 37 | local_hostname: sheep 38 | 39 | cloudInit: 40 | enable: false 41 | instance_id: 001-local01 42 | 43 | sheep: 44 | reboot: false 45 | -------------------------------------------------------------------------------- /tests/integration_test/openSUSE15_1_legacy_CIE_winterfell.yml: -------------------------------------------------------------------------------- 1 | bootloader: 2 | change_boot_order: none 3 | kernel_parameter: "console=ttyS4,115200n8" 4 | 5 | linux: 6 | image: http://@@SHEEP_CI_RUNNER_IP@@/openSUSE-Leap-15.1-OpenStack.x86_64.qcow2 7 | label: OpenSUSE 15.1 8 | device: /dev/sda 9 | rootfsType: btrfs 10 | rootfsLabel: openSUSE-fs 11 | selinux: disable 12 | blacklist_module: 13 | - mei 14 | - mei_me 15 | 16 | pxePilot: 17 | enable: true 18 | url: http://@@SHEEP_CI_RUNNER_IP@@:3478 19 | config_after_reboot: local 20 | 21 | cloudInit: 22 | enable: true 23 | metaData: 24 | instance-id: 001-local01 25 | local-hostname: sheep 26 | networkConfig: 27 | version: 2 28 | ethernets: 29 | eth0: 30 | dhcp4: true 31 | eth1: 32 | addresses: 33 | - 172.19.17.111/24 34 | gateway4: 172.19.17.1 35 | userData: 36 | users: 37 | - name: linux 38 | lock_passwd: false 39 | ssh_authorized_keys: @@SHEEP_CI_SSH_PUB_KEY@@ 40 | sudo: ALL=(ALL) NOPASSWD:ALL 41 | shell: /bin/bash 42 | chpasswd: 43 | expire: false 44 | list: | 45 | linux:linux 46 | ssh_pwauth: true 47 | sheep: 48 | reboot: false 49 | -------------------------------------------------------------------------------- /tests/integration_test/openSUSE15_1_uefi_CID_leopard.yml: -------------------------------------------------------------------------------- 1 | bootloader: 2 | change_boot_order: none 3 | image: http://@@SHEEP_CI_RUNNER_IP@@/grub.tar.gz 4 | kernel_parameter: "console=ttyS1,57600n8" 5 | 6 | linux: 7 | image: http://@@SHEEP_CI_RUNNER_IP@@/openSUSE-Leap-15.1-OpenStack.x86_64.qcow2 8 | label: OpenSUSE 15.1 9 | device: /dev/sda 10 | rootfsType: btrfs 11 | rootfsLabel: openSUSE-fs 12 | selinux: disable 13 | 14 | network: 15 | interfaces: 16 | - id: ens1 17 | mode: dhcp 18 | 19 | pxePilot: 20 | enable: true 21 | url: http://@@SHEEP_CI_RUNNER_IP@@:3478 22 | config_after_reboot: local 23 | 24 | environment: 25 | users: 26 | - name: linux 27 | sudoer: true 28 | password: linux 29 | ssh_authorized_key: @@SHEEP_CI_SSH_PUB_KEY@@ 30 | shell: /bin/bash 31 | local_hostname: sheep 32 | 33 | cloudInit: 34 | enable: false 35 | instance_id: 001-local01 36 | 37 | sheep: 38 | reboot: false 39 | -------------------------------------------------------------------------------- /tests/integration_test/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # 6 | # Log on stdout 7 | # 8 | # $* - Text to log 9 | # 10 | log() { 11 | echo "[$(date "+%Y-%m-%d %H:%M:%S")] $*" 12 | } 13 | 14 | # 15 | # $1 - MAC address 16 | # 17 | get_ip_from_mac() { 18 | sudo arp -an | awk /$1/'{print $2}' | sed 's/[()]//g' 19 | } 20 | 21 | # 22 | # $1 - MAC address 23 | # 24 | delete_arp_entry() { 25 | while read ip; do 26 | log "Deleting ARP entry for IP address '${ip}'" 27 | sudo arp -d ${ip} 28 | done < <(get_ip_from_mac ${1}) 29 | } 30 | 31 | # 32 | # Look at the ARP table every 10 seconds until either the IP address 33 | # matching the input MAC address is found or the timeout is reached. 34 | # 35 | # $1 - MAC address 36 | # $2 - Timeout in seconds 37 | # 38 | wait_for_ip() { 39 | local mac=$1 40 | local max_wait=$2 41 | local s_time=$(date +%s) 42 | local c_time= 43 | local ip= 44 | while true; do 45 | c_time=$(date +%s) 46 | 47 | ip=$(get_ip_from_mac ${mac}) 48 | if [ -n "${ip}" ]; then 49 | log "IP $ip found for MAC ${mac}" 50 | break 51 | fi 52 | 53 | if [ $(($c_time - $s_time)) -gt ${max_wait} ]; then 54 | log "ERROR : Timeout..." 55 | exit 1 56 | fi 57 | 58 | log "Waiting for IP to be reachable for MAC ${mac}..." 59 | 60 | sleep 10 61 | done 62 | } 63 | 64 | # 65 | # Run a command through SSH 66 | # 67 | # $1 - Linux IP 68 | # $2 - Linux username 69 | # $3 - Linux password 70 | # $* Command to execute 71 | # 72 | ssh_cmd() { 73 | local ip=$1 74 | local username=$2 75 | local password=$3 76 | shift 77 | shift 78 | shift 79 | { 80 | timeout 20 sshpass -p ${password} \ 81 | ssh -o StrictHostKeyChecking=no \ 82 | -o UserKnownHostsFile=/dev/null \ 83 | -o LogLevel=QUIET \ 84 | ${username}@${ip} "$*" 85 | return $? 86 | } < /dev/null 87 | } 88 | 89 | # 90 | # Wait untill SSH port is open and SSH login using specified user/password works 91 | # If the timeout is reached, it will exit with an error code. 92 | # 93 | # $1 - Linux IP address 94 | # $2 - Linux username 95 | # $3 - Linux password 96 | # $4 - Timeout in secconds 97 | # 98 | wait_for_ssh() { 99 | local ip=$1 100 | local username=$2 101 | local password=$3 102 | local max_wait=$4 103 | local s_time=$(date +%s) 104 | local c_time= 105 | local ssh_ok=0 106 | while true; do 107 | c_time=$(date +%s) 108 | ssh_ok=$(sudo nmap -sT -p 22 ${ip} | grep '22/tcp open' | wc -l) 109 | if [ $ssh_ok -eq 1 ]; then 110 | if [ "$(ssh_cmd ${ip} ${username} ${password} echo ok)" = "ok" ]; then 111 | log "SSH connection ok" 112 | break 113 | fi 114 | ssh_ok=0 115 | fi 116 | 117 | if [ $(($c_time - $s_time)) -gt ${max_wait} ]; then 118 | log "ERROR : Timeout !" 119 | exit 1 120 | fi 121 | 122 | log "Waiting for Linux to be SSH accessible on IP ${ip}..." 123 | sleep 10 124 | done 125 | } 126 | 127 | # 128 | # $1 - Linux IP address 129 | # $2 - Linux username 130 | # $3 - Linux password 131 | # 132 | print_sheep_log() { 133 | log "" 134 | log "!!! See Sheep log below !!!" 135 | log "" 136 | ssh_cmd ${1} ${2} ${3} 'cat /var/log/sheep.log' 137 | log "" 138 | log "!!! End of Sheep log !!!" 139 | log "" 140 | } 141 | 142 | # 143 | # $1 - Linux IP address 144 | # $2 - Linux username 145 | # $3 - Linux password 146 | # $4 - Timeout in secconds 147 | # 148 | wait_for_installation_complete() { 149 | local ip=$1 150 | local username=$2 151 | local password=$3 152 | local max_wait=$4 153 | local s_time=$(date +%s) 154 | local c_time= 155 | local ssh_ok=0 156 | while true; do 157 | c_time=$(date +%s) 158 | 159 | if [ "$(ssh_cmd ${ip} ${username} ${password} 'if [ -e /var/run/sheep.success ] ; then echo ok ; fi')" == 'ok' ]; then 160 | log 'Installation complete' 161 | break 162 | fi 163 | 164 | if [ $(($c_time - $s_time)) -gt ${max_wait} ]; then 165 | print_sheep_log ${ip} ${username} ${password} 166 | exit 1 167 | fi 168 | 169 | log "Waiting for Sheep to complete OS installation..." 170 | sleep 10 171 | done 172 | 173 | print_sheep_log ${ip} ${username} ${password} 174 | } 175 | 176 | # 177 | # Deploy TFTP and HTTP configuration file for network boot 178 | # 179 | deploy_boot_config() { 180 | sudo mkdir -p /var/www/html/sheep 181 | 182 | sudo rm -f /var/www/html/sheep/${CONFIG_NAME} /var/tftp/pxelinux.cfg/conf/*/${CONFIG_NAME} /var/www/html/sheep/${CONFIG_NAME}.yml 183 | sudo cp ${SHEEP_SCRIPT_SOURCE} /var/www/html/sheep/${CONFIG_NAME} 184 | 185 | if [ $(echo ${DUT_NAME} | grep '\-legacy' | wc -l) -eq 1 ]; then 186 | # Legacy mode (using pxelinux) 187 | sudo bash -c "cat > /var/tftp/pxelinux.cfg/conf/pxelinux/${CONFIG_NAME}" <<- EOF 188 | default sheep 189 | label sheep 190 | kernel /sheep-live/vmlinuz 191 | append boot=live fetch=${SHEEP_SQUASHFS} initrd=/sheep-live/initrd.img ssh=sheep ${EXTRA_KERNEL_CMDLINE} startup=/usr/bin/sheep-service sheep.script=${SHEEP_SCRIPT} sheep.config=${SHEEP_CONFIG} sheep.log.level=DEBUG 192 | EOF 193 | else 194 | # UEFI mode (using GRUB) 195 | sudo bash -c "cat > /var/tftp/pxelinux.cfg/conf/grub/${CONFIG_NAME}" <<- EOF 196 | cat > ${WORKDIR}/netboot <<- EOF 197 | set timeout=2 198 | set default="0" 199 | 200 | menuentry "Sheep" { 201 | linux /sheep-live/vmlinuz boot=live fetch=${SHEEP_SQUASHFS} ssh=sheep ${EXTRA_KERNEL_CMDLINE} startup=/usr/bin/sheep-service sheep.script=${SHEEP_SCRIPT} sheep.config=${SHEEP_CONFIG} sheep.log.level=DEBUG 202 | initrd /sheep-live/initrd.img 203 | } 204 | EOF 205 | fi 206 | 207 | local tmp_config=$(mktemp) 208 | chmod +r ${tmp_config} 209 | 210 | sed "s#@@SHEEP_CI_RUNNER_IP@@#${SHEEP_CI_RUNNER_IP}#g" ${SHEEP_CONFIG_SOURCE} | sed "s#@@SHEEP_CI_SSH_PUB_KEY@@#$(cat ${HOME}/.ssh/id_rsa.pub)#g" > ${tmp_config} 211 | 212 | sudo cp ${tmp_config} /var/www/html/sheep/${CONFIG_NAME}.yml 213 | 214 | log "Set PXE Pilot configuration '${CONFIG_NAME}' for machine '${DUT_NAME}'" 215 | pxe-pilot config deploy ${CONFIG_NAME} ${DUT_NAME} > /dev/null 2>&1 216 | } 217 | 218 | # 219 | # $1 - DUT Name 220 | # 221 | get_ipmi_ip() { 222 | pxe-pilot host list | grep " $1 " | sed -e 's/.* | \([0-9]*.[0-9]*.[0-9]*.[0-9]*\) | .*/\1/' 223 | } 224 | 225 | # 226 | # $1 - DUT Name 227 | # $2 - IPMI command 228 | # 229 | ipmi() { 230 | set +e 231 | local cmd="ipmitool -I lanplus -U USERID -P PASSW0RD -H $(get_ipmi_ip ${1}) ${2}" 232 | sleep 3 233 | ${cmd} 234 | local status=$? 235 | set -e 236 | if [ ${status} -ne 0 ]; then 237 | sleep 3 238 | ${cmd} 239 | fi 240 | } 241 | 242 | # 243 | # $1 - DUT Name 244 | # 245 | start_dut() { 246 | local status= 247 | local s_time=$(date +%s) 248 | local c_time= 249 | local max_wait=20 250 | 251 | ipmi $1 "power on" > /dev/null 2>&1 252 | 253 | while true; do 254 | c_time=$(date +%s) 255 | status=$(status_dut $1) 256 | if [ ${status} == 'on' ]; then 257 | break 258 | fi 259 | 260 | if [ $(($c_time - $s_time)) -gt ${max_wait} ]; then 261 | log "Time out due to ipmi unreachable" 262 | exit 1 263 | fi 264 | 265 | sleep 1 266 | done 267 | 268 | } 269 | 270 | # 271 | # $1 - DUT Name 272 | # 273 | stop_dut() { 274 | local status= 275 | local s_time=$(date +%s) 276 | local c_time= 277 | local max_wait=20 278 | 279 | ipmi $1 "power off" > /dev/null 2>&1 280 | 281 | while true; do 282 | c_time=$(date +%s) 283 | status=$(status_dut $1) 284 | if [ ${status} == 'off' ]; then 285 | break 286 | fi 287 | 288 | if [ $(($c_time - $s_time)) -gt ${max_wait} ]; then 289 | log "Time out due to ipmi unreachable" 290 | exit 1 291 | fi 292 | 293 | sleep 1 294 | done 295 | } 296 | 297 | # 298 | # $1 - DUT Name 299 | # 300 | status_dut() { 301 | local status=$(ipmi $1 "power status" 2> /dev/null | sed "s/Chassis Power is //") 302 | if [[ ${status} != 'on' && ${status} != 'off' ]]; then 303 | echo 'unknown' 304 | return 305 | fi 306 | echo "${status}" 307 | } 308 | 309 | # 310 | # $1 - PXE Pilot host name 311 | # 312 | get_mac_address() { 313 | echo $(curl ${PXE_PILOT_API}/v1/hosts 2> /dev/null | jq -r ".[] | select(.name == \"${1}\") | .macAddresses[0]") 314 | } 315 | 316 | # 317 | # Print current date and time in format "dd/mm/yyyy hh:mm:ss" 318 | # 319 | # 320 | current_datetime() { 321 | date "+%d/%m/%Y %H:%M:%S" 322 | } 323 | 324 | # 325 | # $1 - DUT Name 326 | # 327 | start_asciinema() { 328 | mkdir -p ${HOME}/job-cast 329 | local ip="$(get_ipmi_ip ${1})" 330 | local sol_script=${WORKDIR}/sol-${1}.sh 331 | 332 | cat > ${sol_script} <<- EOF 333 | while true ; do 334 | ipmitool -I lanplus -U USERID -P PASSW0RD -H ${ip} sol deactivate 335 | sleep 1 336 | ipmitool -I lanplus -U USERID -P PASSW0RD -H ${ip} sol activate 337 | done 338 | EOF 339 | 340 | screen -d -m asciinema rec -c "bash ${sol_script}" ${ASCIINEMA_FILE} 341 | } 342 | 343 | # 344 | # $1 - DUT Name 345 | # 346 | stop_asciinema() { 347 | local sol_script=${WORKDIR}/sol-${1}.sh 348 | 349 | local pid=$( 350 | ps -edf | grep -e "[a]sciinema rec .* ${sol_script}" | grep -v SCREEN | head -n 1 | awk '{print $2}' 351 | ) 352 | 353 | if [ -z "${pid}" ]; then 354 | log "!!! No asciinema process is running for machine '${1}' !!!" 355 | else 356 | kill ${pid} 357 | fi 358 | 359 | if [ -e ${ASCIINEMA_FILE} ]; then 360 | log "Asciinema recording saved in ${ASCIINEMA_FILE}" 361 | cp ${ASCIINEMA_FILE} ${CI_PROJECT_DIR} 362 | fi 363 | } 364 | 365 | # 366 | # Delete configurations created by the the test 367 | # 368 | cleanup() { 369 | log "Stopping asciinema SOL recording for ${DUT_NAME}" 370 | stop_asciinema ${DUT_NAME} 371 | 372 | log "Cleanup environment" 373 | sudo rm -f /var/tftp/pxelinux.cfg/conf/*/${CONFIG_NAME} 374 | sudo rm -f /var/www/html/sheep/${CONFIG_NAME} 375 | 376 | touch ${SHEEP_RESULTS_FILE} 377 | if [ $(cat ${SHEEP_RESULTS_FILE} | wc -l) -eq 0 ]; then 378 | echo "Pipeline ID,Job ID,Commit ID,Start date,End date,DUT,Sheep config,Final Step,Git ref" > ${SHEEP_RESULTS_FILE} 379 | fi 380 | echo "${CI_PIPELINE_ID},${CI_JOB_ID},${CI_COMMIT_SHORT_SHA},${TEST_START_DATE},$(current_datetime),${DUT_NAME},${CONFIG_FILE_NAME},${current_step},${CI_COMMIT_REF_NAME}" >> ${SHEEP_RESULTS_FILE} 381 | } 382 | 383 | # 384 | # Run the overall testing scenario 385 | # 386 | main() { 387 | TEST_START_DATE=$(current_datetime) 388 | current_step=begin 389 | 390 | local dut_status="$(status_dut ${DUT_NAME})" 391 | 392 | log "Machine '${DUT_NAME}' power status is ${dut_status}" 393 | 394 | if [ ${dut_status} == 'on' ]; then 395 | log "Stopping machine '${DUT_NAME}'" 396 | current_step=stop_dut 397 | stop_dut ${DUT_NAME} 398 | fi 399 | 400 | log "Generate and deploy netboot configuration..." 401 | current_step=deploy_boot_config 402 | deploy_boot_config 403 | 404 | mac=$(get_mac_address ${DUT_NAME}) 405 | log "MAC address for host '${DUT_NAME}' is ${mac}" 406 | 407 | log "Deleting ARP entry for MAC ${mac}" 408 | current_step=delete_arp_entry 409 | delete_arp_entry ${mac} 410 | 411 | log "Starting test on machine '${DUT_NAME}'" 412 | current_step=start_dut 413 | start_dut ${DUT_NAME} 414 | 415 | sleep 5 416 | 417 | # 418 | # In theory, we should start the SOL session and then start the machine. Because of a buggy 419 | # Behaviour on OCP Winterfell servers (DUT we use for legacy boot), we have to do it in the 420 | # reverse order. When starting the server with a SOL session already activated, serial output 421 | # is not always correctly captured. Starting the SOL session a couple of seconds after the 422 | # server is powered on do the trick. The downside is that we can lost the begining of the boot, 423 | # usually some POST codes, but in our use case it's not a big issue... 424 | # 425 | log "Starting asciinema SOL recording for machine '${DUT_NAME}'" 426 | start_asciinema ${DUT_NAME} 427 | 428 | wait_for_ip ${mac} 450 429 | ip=$(get_ip_from_mac ${mac}) 430 | if [ -z "${ip}" ]; then 431 | log "Error.... Server did not boot !" 432 | exit 1 433 | fi 434 | 435 | log "Waiting for Sheep Live to boot..." 436 | current_step=wait_for_ssh_grml 437 | wait_for_ssh ${ip} root sheep 600 438 | 439 | log "Waiting for the installation process to complete..." 440 | current_step=wait_for_installation_complete 441 | wait_for_installation_complete ${ip} root sheep 600 442 | 443 | log "Rebooting the machine..." 444 | current_step=reboot 445 | pxe-pilot host reboot ${DUT_NAME} 446 | 447 | log "Waiting for newly installed Linux to boot..." 448 | current_step=wait_for_ssh_linux 449 | wait_for_ssh ${ip} linux linux 600 450 | 451 | current_step=check_compliance 452 | 453 | export IP="${ip}" 454 | export USER=linux 455 | export PASSWORD=linux 456 | export INPUT_TEST=${SYS_TEST_FILE} 457 | local test_fail=false 458 | c=0 459 | current_test=$(yq -r ".${CI_JOB_NAME}[$c].name" "${SYS_TEST_FILE}") 460 | while [ "${current_test}" != "null" ]; do 461 | log "Performing test ${current_test}" 462 | export CURRENT_KEY=".${CI_JOB_NAME}[$c]" 463 | 464 | set +e 465 | bats ${CI_PROJECT_DIR}/tests/integration_test/"${current_test}".bats 466 | local bats_status=$? 467 | set -e 468 | if [ ${bats_status} -ne 0 ]; then 469 | test_fail=true 470 | fi 471 | c=$(($c + 1)) 472 | current_test=$(yq -r ".${CI_JOB_NAME}[$c].name" "${SYS_TEST_FILE}") 473 | done 474 | 475 | if [ "${test_fail}" == "true" ]; then 476 | log "System test fail - See the log" 477 | exit 1 478 | fi 479 | 480 | current_step=power_off 481 | log "Power off server ${DUT_NAME}" 482 | # Sending a poweroff command through ssh lead to close SSH connection and return a non-zero 483 | # exit code. Disabling -e flag to ignore this error 484 | set +e 485 | ssh_cmd ${ip} linux linux sudo poweroff 486 | set -e 487 | 488 | current_step=success 489 | log "Success :)" 490 | } 491 | 492 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null 2>&1 && pwd)" 493 | SHEEP_RESULTS_FILE=$HOME/sheep_integration_tests.csv 494 | SHEEP_SCRIPT_SOURCE=$SCRIPT_DIR/../../sheep 495 | SHEEP_CONFIG_SOURCE=$SCRIPT_DIR/${CONFIG_FILE_NAME} 496 | DUT_NAME=${CI_RUNNER_DESCRIPTION} 497 | CONFIG_NAME=${CI_RUNNER_DESCRIPTION}_${CI_COMMIT_SHORT_SHA}_${CI_PIPELINE_ID}_${CI_JOB_ID} 498 | SHEEP_CI_RUNNER_IP=$(cat /etc/sheep-ci-runner-ip) 499 | SHEEP_SQUASHFS=http://${SHEEP_CI_RUNNER_IP}/sheep-live.squashfs 500 | SHEEP_SCRIPT=http://${SHEEP_CI_RUNNER_IP}/sheep/${CONFIG_NAME} 501 | SHEEP_CONFIG=http://${SHEEP_CI_RUNNER_IP}/sheep/${CONFIG_NAME}.yml 502 | PXE_PILOT_API=http://${SHEEP_CI_RUNNER_IP}:3478 503 | ASCIINEMA_FILE=${HOME}/job-cast/${CONFIG_NAME}.cast 504 | WORKDIR=$(mktemp -d) 505 | 506 | if [ -z "${CI_RUNNER_DESCRIPTION}" ]; then 507 | log "ERROR : Variable 'CI_RUNNER_DESCRIPTION' is not defined..." 508 | exit 1 509 | fi 510 | 511 | if [ -z "${SHEEP_CI_RUNNER_IP}" ]; then 512 | log "ERROR : Variable 'SHEEP_CI_RUNNER_IP' is not defined..." 513 | exit 1 514 | fi 515 | 516 | if [ -z "${CONFIG_FILE_NAME}" ]; then 517 | log "ERROR : Variable 'CONFIG_FILE_NAME' is not defined..." 518 | exit 1 519 | fi 520 | 521 | current_step=init 522 | 523 | trap cleanup EXIT 524 | 525 | main 526 | -------------------------------------------------------------------------------- /tests/integration_test/shell.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_tool 4 | 5 | @test "test shell value for the system" { 6 | test_input=$(search_value "${CURRENT_KEY}".data) 7 | 8 | run ssh_cmd "${IP}" "${USER}" "${PASSWORD}" echo '$SHELL' 9 | 10 | [ ${status} -eq 0 ] 11 | [ "${output}" == "${test_input}" ] 12 | } 13 | -------------------------------------------------------------------------------- /tests/integration_test/system_test.yml: -------------------------------------------------------------------------------- 1 | ubuntu16_04_qcow2_uefi_boot: 2 | - name: "uname" 3 | data: "Linux sheep 4.4.0-173-generic" 4 | - name: "shell" 5 | data: "/bin/sh" 6 | 7 | centos7_qcow2_uefi_boot: 8 | - name: "uname" 9 | data: "Linux sheep 3.10.0-957.27.2.el7.x86_64" 10 | - name: "shell" 11 | data: "/bin/bash" 12 | 13 | debian9_qcow2_uefi_boot: 14 | - name: "uname" 15 | data: "Linux sheep 4.9.0-11-amd64" 16 | - name: "shell" 17 | data: "/bin/bash" 18 | 19 | opensuse15_1_qcow2_uefi_boot: 20 | - name: "uname" 21 | data: "Linux sheep 4.12.14-lp151.28.36-default" 22 | - name: "shell" 23 | data: "/bin/bash" 24 | 25 | ubuntu16_04_qcow2_legacy_boot: 26 | - name: "uname" 27 | data: "Linux sheep 4.4.0-173-generic" 28 | - name: "shell" 29 | data: "/bin/bash" 30 | - name: "network" 31 | data: 32 | mode: "iface enp12s0 inet dhcp" 33 | filePath: "/etc/network/interfaces.d/50-cloud-init.cfg" 34 | - name: "network" 35 | data: 36 | mode: "iface ens9 inet static" 37 | filePath: "/etc/network/interfaces.d/50-cloud-init.cfg" 38 | ipAdd: "172.19.17.111" 39 | 40 | centos7_qcow2_legacy_boot: 41 | - name: "uname" 42 | data: "Linux sheep 3.10.0-957.27.2.el7.x86_64" 43 | - name: "shell" 44 | data: "/bin/bash" 45 | - name: "network" 46 | data: 47 | mode: "BOOTPROTO=dhcp" 48 | filePath: "/etc/sysconfig/network-scripts/ifcfg-enp12s0" 49 | - name: "network" 50 | data: 51 | mode: "BOOTPROTO=none" 52 | filePath: "/etc/sysconfig/network-scripts/ifcfg-ens9" 53 | ipAdd: "172.19.17.111" 54 | 55 | debian9_qcow2_legacy_boot: 56 | - name: "uname" 57 | data: "Linux sheep 4.9.0-11-amd64" 58 | - name: "shell" 59 | data: "/bin/bash" 60 | - name: "network" 61 | data: 62 | mode: "iface enp12s0 inet dhcp" 63 | filePath: "/etc/network/interfaces.d/50-cloud-init.cfg" 64 | - name: "network" 65 | data: 66 | mode: "iface ens9 inet static" 67 | filePath: "/etc/network/interfaces.d/50-cloud-init.cfg" 68 | ipAdd: "172.19.17.111" 69 | 70 | opensuse15_1_qcow2_legacy_boot: 71 | - name: "uname" 72 | data: "Linux sheep 4.12.14-lp151.28.36-default" 73 | - name: "shell" 74 | data: "/bin/bash" 75 | - name: "network" 76 | data: 77 | mode: "BOOTPROTO=dhcp" 78 | filePath: "/etc/sysconfig/network/ifcfg-eth0" 79 | 80 | ubuntu16_04_qcow2_legacy_boot_CIE: 81 | - name: "uname" 82 | data: "Linux sheep 4.4.0-173-generic" 83 | - name: "shell" 84 | data: "/bin/bash" 85 | - name: "network" 86 | data: 87 | mode: "iface enp12s0 inet dhcp" 88 | filePath: "/etc/network/interfaces.d/50-cloud-init.cfg" 89 | - name: "network" 90 | data: 91 | mode: "iface ens9 inet static" 92 | filePath: "/etc/network/interfaces.d/50-cloud-init.cfg" 93 | ipAdd: "172.19.17.111" 94 | 95 | centos7_qcow2_legacy_boot_CIE: 96 | - name: "uname" 97 | data: "Linux sheep 3.10.0-957.27.2.el7.x86_64" 98 | - name: "shell" 99 | data: "/bin/bash" 100 | - name: "network" 101 | data: 102 | mode: "BOOTPROTO=dhcp" 103 | filePath: "/etc/sysconfig/network-scripts/ifcfg-enp12s0" 104 | - name: "network" 105 | data: 106 | mode: "BOOTPROTO=none" 107 | filePath: "/etc/sysconfig/network-scripts/ifcfg-ens9" 108 | ipAdd: "172.19.17.111" 109 | 110 | debian9_qcow2_legacy_boot_CIE: 111 | - name: "uname" 112 | data: "Linux sheep 4.9.0-11-amd64" 113 | - name: "shell" 114 | data: "/bin/bash" 115 | - name: "network" 116 | data: 117 | mode: "iface enp12s0 inet dhcp" 118 | filePath: "/etc/network/interfaces.d/50-cloud-init.cfg" 119 | - name: "network" 120 | data: 121 | mode: "iface ens9 inet static" 122 | filePath: "/etc/network/interfaces.d/50-cloud-init.cfg" 123 | ipAdd: "172.19.17.111" 124 | 125 | opensuse15_1_qcow2_legacy_boot_CIE: 126 | - name: "uname" 127 | data: "Linux sheep 4.12.14-lp151.28.36-default" 128 | - name: "shell" 129 | data: "/bin/bash" 130 | - name: "network" 131 | data: 132 | mode: "BOOTPROTO=dhcp" 133 | filePath: "/etc/sysconfig/network/ifcfg-eth0" 134 | 135 | ubuntu16_04_uefi_leopard__boot_order_once: 136 | - name: "uname" 137 | data: "Linux sheep 4.4.0-173-generic" 138 | - name: "shell" 139 | data: "/bin/sh" 140 | -------------------------------------------------------------------------------- /tests/integration_test/test_tool.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # Run a command through SSH 5 | # 6 | # $1 - Linux IP 7 | # $2 - Linux username 8 | # $3 - Linux password 9 | # $* Command to execute 10 | # 11 | ssh_cmd() { 12 | local ip=$1 13 | local username=$2 14 | local password=$3 15 | shift 16 | shift 17 | shift 18 | { 19 | timeout 20 sshpass -p ${password} \ 20 | ssh -o StrictHostKeyChecking=no \ 21 | -o UserKnownHostsFile=/dev/null \ 22 | -o LogLevel=QUIET \ 23 | ${username}@${ip} "$*" 24 | return $? 25 | } < /dev/null 26 | } 27 | 28 | # 29 | # search_value returns a piece of configuration from the Sheep YAML configuration file. 30 | # 31 | # The function call parser yq with -w 10000 parameters to parse correctly long sized value 32 | # With -Y to parse correctly when the key have subkeys. 33 | # 34 | # $1 - Parameter identifier 35 | # $2 - Parsing option (possible values are 'string' or 'yaml'. Default is 'string') 36 | # 37 | search_value() { 38 | 39 | if [ -z "${2}" ] || [ "${2}" == "string" ]; then 40 | local value=$(yq -r "${1}" "${INPUT_TEST}") 41 | echo "${value}" 42 | elif [ "${2}" == "yaml" ]; then 43 | yq -r -Y -w 100000 "${1}" "${INPUT_TEST}" 44 | fi 45 | } 46 | -------------------------------------------------------------------------------- /tests/integration_test/ubuntu16_04_legacy_CID_winterfell.yml: -------------------------------------------------------------------------------- 1 | bootloader: 2 | change_boot_order: none 3 | kernel_parameter: "console=ttyS4,115200n8" 4 | 5 | linux: 6 | image: http://@@SHEEP_CI_RUNNER_IP@@/xenial-server-cloudimg-amd64-disk1.img 7 | label: Ubuntu 16.04 LTS 8 | device: /dev/sda 9 | rootfsType: ext4 10 | rootfsLabel: Ubuntu-fs 11 | selinux: disable 12 | blacklist_module: 13 | - mei 14 | - mei_me 15 | 16 | network: 17 | interfaces: 18 | - id: enp12s0 19 | mode: dhcp 20 | - id: ens9 21 | mode: static 22 | address: 172.19.17.111 23 | gateway: 172.19.17.1 24 | 25 | pxePilot: 26 | enable: true 27 | url: http://@@SHEEP_CI_RUNNER_IP@@:3478 28 | config_after_reboot: local 29 | 30 | environment: 31 | users: 32 | - name: linux 33 | sudoer: true 34 | password: linux 35 | ssh_authorized_key: @@SHEEP_CI_SSH_PUB_KEY@@ 36 | shell: /bin/bash 37 | local_hostname: sheep 38 | 39 | cloudInit: 40 | enable: false 41 | instance_id: 001-local01 42 | 43 | sheep: 44 | reboot: false 45 | -------------------------------------------------------------------------------- /tests/integration_test/ubuntu16_04_legacy_CIE_winterfell.yml: -------------------------------------------------------------------------------- 1 | bootloader: 2 | change_boot_order: none 3 | kernel_parameter: "console=ttyS4,115200n8" 4 | 5 | linux: 6 | image: http://@@SHEEP_CI_RUNNER_IP@@/xenial-server-cloudimg-amd64-disk1.img 7 | label: Ubuntu 16.04 LTS 8 | device: /dev/sda 9 | rootfsType: ext4 10 | rootfsLabel: Ubuntu-fs 11 | selinux: disable 12 | blacklist_module: 13 | - mei 14 | - mei_me 15 | 16 | pxePilot: 17 | enable: true 18 | url: http://@@SHEEP_CI_RUNNER_IP@@:3478 19 | config_after_reboot: local 20 | 21 | cloudInit: 22 | enable: true 23 | metaData: 24 | instance-id: 001-local01 25 | local-hostname: sheep 26 | networkConfig: 27 | version: 2 28 | ethernets: 29 | enp12s0: 30 | dhcp4: true 31 | ens9: 32 | addresses: 33 | - 172.19.17.111/24 34 | gateway4: 172.19.17.1 35 | userData: 36 | users: 37 | - name: linux 38 | lock_passwd: false 39 | ssh_authorized_keys: @@SHEEP_CI_SSH_PUB_KEY@@ 40 | sudo: ALL=(ALL) NOPASSWD:ALL 41 | shell: /bin/bash 42 | chpasswd: 43 | expire: false 44 | list: | 45 | linux:linux 46 | ssh_pwauth: true 47 | sheep: 48 | reboot: false 49 | -------------------------------------------------------------------------------- /tests/integration_test/ubuntu16_04_uefi_CID_leopard.yml: -------------------------------------------------------------------------------- 1 | bootloader: 2 | change_boot_order: none 3 | image: http://@@SHEEP_CI_RUNNER_IP@@/grub.tar.gz 4 | kernel_parameter: "console=ttyS1,57600n8" 5 | 6 | linux: 7 | image: http://@@SHEEP_CI_RUNNER_IP@@/xenial-server-cloudimg-amd64-disk1.img 8 | label: Ubuntu 16.04 LTS 9 | device: /dev/sda 10 | rootfsType: ext4 11 | rootfsLabel: Ubuntu-fs 12 | selinux: disable 13 | 14 | network: 15 | interfaces: 16 | - id: ens1 17 | mode: dhcp 18 | 19 | pxePilot: 20 | enable: true 21 | url: http://@@SHEEP_CI_RUNNER_IP@@:3478 22 | config_after_reboot: local 23 | 24 | environment: 25 | users: 26 | - name: linux 27 | sudoer: true 28 | password: linux 29 | ssh_authorized_key: @@SHEEP_CI_SSH_PUB_KEY@@ 30 | local_hostname: sheep 31 | 32 | cloudInit: 33 | enable: false 34 | instance_id: 001-local01 35 | 36 | sheep: 37 | reboot: false 38 | -------------------------------------------------------------------------------- /tests/integration_test/ubuntu16_04_uefi_leopard__boot_order_once.yml: -------------------------------------------------------------------------------- 1 | bootloader: 2 | change_boot_order: once 3 | image: http://@@SHEEP_CI_RUNNER_IP@@/grub.tar.gz 4 | kernel_parameter: "console=ttyS1,57600n8" 5 | 6 | linux: 7 | image: http://@@SHEEP_CI_RUNNER_IP@@/xenial-server-cloudimg-amd64-disk1.img 8 | label: Ubuntu 16.04 LTS 9 | device: /dev/sda 10 | rootfsType: ext4 11 | rootfsLabel: Ubuntu-fs 12 | selinux: disable 13 | 14 | network: 15 | interfaces: 16 | - id: ens1 17 | mode: dhcp 18 | 19 | pxePilot: 20 | enable: false 21 | 22 | environment: 23 | users: 24 | - name: linux 25 | sudoer: true 26 | password: linux 27 | ssh_authorized_key: @@SHEEP_CI_SSH_PUB_KEY@@ 28 | local_hostname: sheep 29 | 30 | cloudInit: 31 | enable: false 32 | instance_id: 001-local01 33 | 34 | sheep: 35 | reboot: false 36 | -------------------------------------------------------------------------------- /tests/integration_test/uname.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_tool 4 | 5 | @test "test uname value for the system" { 6 | test_input=$(search_value "${CURRENT_KEY}".data) 7 | 8 | run ssh_cmd "${IP}" "${USER}" "${PASSWORD}" uname -a 9 | 10 | [ ${status} -eq 0 ] 11 | [ $(echo "${output}" | grep "${test_input}" | wc -l) -eq 1 ] 12 | } 13 | -------------------------------------------------------------------------------- /tests/unit_tests/check_filesystem_label.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "check_filesystem_label with 0123456789ab (12 characters)" { 6 | source ${BATS_TEST_DIRNAME}/../../sheep 7 | 8 | run check_filesystem_label 0123456789ab 9 | [ "${status}" -eq 0 ] 10 | } 11 | 12 | @test "check_filesystem_label with 0123456789abc (13 character)" { 13 | source ${BATS_TEST_DIRNAME}/../../sheep 14 | 15 | run check_filesystem_label 0123456789abc 16 | [ "${status}" -eq 1 ] 17 | [ "${output}" = "ERROR : Number of character exceed maximal size : 12 characters max" ] 18 | } 19 | 20 | @test "check_filesystem_label with A12_45-789zb (12 characters, all authorized characters)" { 21 | source ${BATS_TEST_DIRNAME}/../../sheep 22 | 23 | run check_filesystem_label A12_45-789zb 24 | [ "${status}" -eq 0 ] 25 | } 26 | 27 | @test "check_filesystem_label with 0123456789@ (less than 12 character)" { 28 | source ${BATS_TEST_DIRNAME}/../../sheep 29 | 30 | run check_filesystem_label 0123456789@ 31 | [ "${status}" -eq 1 ] 32 | [ "${output}" = "ERROR : Invalid character used in the name given to filesystem : character must be a number, a letter '_' or '-'" ] 33 | } 34 | 35 | @test "check_filesystem_label with 0123456789# (less than 12 character)" { 36 | source ${BATS_TEST_DIRNAME}/../../sheep 37 | 38 | run check_filesystem_label 0123456789# 39 | [ "${status}" -eq 1 ] 40 | [ "${output}" = "ERROR : Invalid character used in the name given to filesystem : character must be a number, a letter '_' or '-'" ] 41 | } 42 | 43 | @test "check_filesystem_label with 0123456789* (less than 12 character)" { 44 | source ${BATS_TEST_DIRNAME}/../../sheep 45 | 46 | run check_filesystem_label 0123456789* 47 | [ "${status}" -eq 1 ] 48 | [ "${output}" = "ERROR : Invalid character used in the name given to filesystem : character must be a number, a letter '_' or '-'" ] 49 | } 50 | 51 | @test "check_filesystem_label with 0123456789% (less than 12 character)" { 52 | source ${BATS_TEST_DIRNAME}/../../sheep 53 | 54 | run check_filesystem_label 0123456789% 55 | [ "${status}" -eq 1 ] 56 | [ "${output}" = "ERROR : Invalid character used in the name given to filesystem : character must be a number, a letter '_' or '-'" ] 57 | } 58 | 59 | @test "check_filesystem_label with 0123456789+ (less than 12 character)" { 60 | source ${BATS_TEST_DIRNAME}/../../sheep 61 | 62 | run check_filesystem_label 0123456789+ 63 | [ "${status}" -eq 1 ] 64 | [ "${output}" = "ERROR : Invalid character used in the name given to filesystem : character must be a number, a letter '_' or '-'" ] 65 | } 66 | 67 | @test "check_filesystem_label with 0123456789! (less than 12 character)" { 68 | source ${BATS_TEST_DIRNAME}/../../sheep 69 | 70 | run check_filesystem_label 0123456789! 71 | [ "${status}" -eq 1 ] 72 | [ "${output}" = "ERROR : Invalid character used in the name given to filesystem : character must be a number, a letter '_' or '-'" ] 73 | } 74 | -------------------------------------------------------------------------------- /tests/unit_tests/config_userdata_ci_disable.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "config_userdata_ci_disable with ssh key and password given " { 6 | source ${BATS_TEST_DIRNAME}/../../sheep 7 | export cloudfs=$(mktemp -d) 8 | export CONFIG_FILE=${BATS_TEST_DIRNAME}/sheepCfgUserDataCID1.yml 9 | userdata=${cloudfs}/user-data 10 | 11 | run config_userdata_ci_disable 12 | 13 | ssh_key=$(yq -r ".users[0].ssh_authorized_keys" "${userdata}") 14 | password=$(yq -r ".chpasswd.list" "${userdata}" | head -1) 15 | 16 | [ "${status}" -eq 0 ] 17 | [ "${ssh_key}" = "ssh" ] 18 | [ "${password}" = "linux:linux" ] 19 | rm -r ${cloudfs} 20 | } 21 | 22 | @test "config_userdata_ci_disable with ssh key given and no password " { 23 | source ${BATS_TEST_DIRNAME}/../../sheep 24 | export cloudfs=$(mktemp -d) 25 | export CONFIG_FILE=${BATS_TEST_DIRNAME}/sheepCfgUserDataCID2.yml 26 | userdata=${cloudfs}/user-data 27 | 28 | run config_userdata_ci_disable 29 | 30 | ssh_key=$(yq -r ".users[0].ssh_authorized_keys" "${userdata}") 31 | password=$(yq -r ".chpasswd.list" "${userdata}" | head -1) 32 | 33 | [ "${status}" -eq 0 ] 34 | [ "${ssh_key}" == "ssh" ] 35 | [ "${password}" == "null" ] 36 | rm -r ${cloudfs} 37 | } 38 | 39 | @test "config_userdata_ci_disable with password and no ssh key" { 40 | source ${BATS_TEST_DIRNAME}/../../sheep 41 | export cloudfs=$(mktemp -d) 42 | export CONFIG_FILE=${BATS_TEST_DIRNAME}/sheepCfgUserDataCID3.yml 43 | userdata=${cloudfs}/user-data 44 | 45 | run config_userdata_ci_disable 46 | 47 | ssh_key=$(yq -r ".users[0].ssh_authorized_keys" "${userdata}") 48 | password=$(yq -r ".chpasswd.list" "${userdata}" | head -1) 49 | 50 | [ "${status}" -eq 0 ] 51 | [ "${ssh_key}" == "null" ] 52 | [ "${password}" == "linux:linux" ] 53 | rm -r ${cloudfs} 54 | } 55 | 56 | @test "config_userdata_ci_disable with no password and no ssh key" { 57 | source ${BATS_TEST_DIRNAME}/../../sheep 58 | export cloudfs=$(mktemp -d) 59 | export CONFIG_FILE=${BATS_TEST_DIRNAME}/sheepCfgUserDataCID4.yml 60 | userdata=${cloudfs}/user-data 61 | 62 | run config_userdata_ci_disable 63 | 64 | ssh_key=$(yq -r ".users[0].ssh_authorized_keys" "${userdata}") 65 | password=$(yq -r ".chpasswd.list" "${userdata}" | head -1) 66 | 67 | [ "${status}" -eq 1 ] 68 | [ "${ssh_key}" == "null" ] 69 | [ "${password}" == "null" ] 70 | rm -r ${cloudfs} 71 | } 72 | -------------------------------------------------------------------------------- /tests/unit_tests/config_variable.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "config_variable with .network.interface, .bootloader.mode, .linux.image, .bootloader.image and .pxePilot.enable=false" { 6 | source ${BATS_TEST_DIRNAME}/../../sheep 7 | 8 | export CONFIG_FILE=${BATS_TEST_DIRNAME}/sheepCfgConfigVariable1.yml 9 | 10 | run config_variable 11 | [ "${status}" -eq 0 ] 12 | } 13 | 14 | @test "config_variable with .network.interface, .bootloader.mode, .linux.image, .bootloader.image ,.pxePilot.url given and .pxePilot.enable=true" { 15 | source ${BATS_TEST_DIRNAME}/../../sheep 16 | 17 | export CONFIG_FILE=${BATS_TEST_DIRNAME}/sheepCfgConfigVariable2.yml 18 | 19 | run config_variable 20 | [ "${status}" -eq 0 ] 21 | 22 | } 23 | 24 | @test "config_variable with .pxePilot.url not given and .pxePilot.enable=true" { 25 | source ${BATS_TEST_DIRNAME}/../../sheep 26 | 27 | export CONFIG_FILE=${BATS_TEST_DIRNAME}/sheepCfgConfigVariable3.yml 28 | 29 | run config_variable 30 | [ "${status}" -eq 1 ] 31 | } 32 | 33 | @test "config_variable with all mandatory parameter given except .linux.image" { 34 | source ${BATS_TEST_DIRNAME}/../../sheep 35 | 36 | export CONFIG_FILE=${BATS_TEST_DIRNAME}/sheepCfgConfigVariable5.yml 37 | 38 | run config_variable 39 | [ "${status}" -eq 1 ] 40 | } -------------------------------------------------------------------------------- /tests/unit_tests/get_serial_device.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "get_serial_device return linux device matching the console kernel parameter" { 6 | source ${BATS_TEST_DIRNAME}/../../sheep 7 | 8 | export SHEEP_PARAMETERS='sheep.script=http://sheep console=ttyS1' 9 | 10 | run get_serial_device 11 | 12 | [ "${status}" -eq 0 ] 13 | [ "${output}" = "/dev/ttyS1" ] 14 | } 15 | 16 | @test "get_serial_device return linux device matching the console with console defined twice" { 17 | source ${BATS_TEST_DIRNAME}/../../sheep 18 | 19 | export SHEEP_PARAMETERS='console=tty1 sheep.script=http://sheep console=ttyS2,115200' 20 | 21 | run get_serial_device 22 | 23 | [ "${status}" -eq 0 ] 24 | [ "${output}" = "/dev/ttyS2" ] 25 | } 26 | 27 | @test "get_serial_device return linux device when console paramter is not defined" { 28 | source ${BATS_TEST_DIRNAME}/../../sheep 29 | 30 | export SHEEP_PARAMETERS='sheep.script=http://sheep' 31 | 32 | run get_serial_device 33 | 34 | [ "${status}" -eq 0 ] 35 | [ "${output}" = "" ] 36 | } 37 | -------------------------------------------------------------------------------- /tests/unit_tests/log.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | init_log_test() { 6 | export SHEEP_LOG_FILE=$(mktemp) 7 | export SHEEP_LOG_LEVEL=$1 8 | source ${BATS_TEST_DIRNAME}/../../sheep 9 | } 10 | 11 | check_log_error_present() { 12 | init_log_test $1 13 | 14 | run log_error "This is an error message" 15 | 16 | [ "${status}" -eq 0 ] 17 | [ $(cat ${SHEEP_LOG_FILE} | wc -l) -eq 1 ] 18 | [[ "$(cat ${SHEEP_LOG_FILE})" =~ 'ERROR | This is an error message' ]] 19 | } 20 | 21 | @test "log_error present when log level is ERROR" { 22 | check_log_error_present ERROR 23 | } 24 | 25 | @test "log_error present when log level is WARNING" { 26 | check_log_error_present WARNING 27 | } 28 | 29 | @test "log_error present when log level is INFO" { 30 | check_log_error_present INFO 31 | } 32 | 33 | @test "log_error present when log level is DEBUG" { 34 | check_log_error_present DEBUG 35 | } 36 | 37 | check_log_warning_present() { 38 | init_log_test $1 39 | 40 | run log_warning "This is an warning message" 41 | [ "${status}" -eq 0 ] 42 | [ $(cat ${SHEEP_LOG_FILE} | wc -l) -eq 1 ] 43 | [[ "$(cat ${SHEEP_LOG_FILE})" =~ 'WARNING | This is an warning message' ]] 44 | } 45 | 46 | check_log_warning_absent() { 47 | init_log_test $1 48 | 49 | run log_warning "This is an warning message" 50 | [ "${status}" -eq 0 ] 51 | [ $(cat ${SHEEP_LOG_FILE} | wc -l) -eq 0 ] 52 | } 53 | 54 | @test "log_warning absent when log level is ERROR" { 55 | check_log_warning_absent ERROR 56 | } 57 | 58 | @test "log_warning present when log level is WARNING" { 59 | check_log_warning_present WARNING 60 | } 61 | 62 | @test "log_warning present when log level is INFO" { 63 | check_log_warning_present INFO 64 | } 65 | 66 | @test "log_warning present when log level is DEBUG" { 67 | check_log_warning_present DEBUG 68 | } 69 | 70 | check_log_info_present() { 71 | init_log_test $1 72 | 73 | run log_info "This is an info message" 74 | [ "${status}" -eq 0 ] 75 | [ $(cat ${SHEEP_LOG_FILE} | wc -l) -eq 1 ] 76 | [[ "$(cat ${SHEEP_LOG_FILE})" =~ 'INFO | This is an info message' ]] 77 | } 78 | 79 | check_log_info_absent() { 80 | init_log_test $1 81 | 82 | run log_info "This is an info message" 83 | [ "${status}" -eq 0 ] 84 | [ $(cat ${SHEEP_LOG_FILE} | wc -l) -eq 0 ] 85 | } 86 | 87 | @test "log_info absent when log level is ERROR" { 88 | check_log_info_absent ERROR 89 | } 90 | 91 | @test "log_info absent when log level is WARNING" { 92 | check_log_info_absent WARNING 93 | } 94 | 95 | @test "log_info present when log level is INFO" { 96 | check_log_info_present INFO 97 | } 98 | 99 | @test "log_info present when log level is DEBUG" { 100 | check_log_info_present DEBUG 101 | } 102 | 103 | check_log_present() { 104 | init_log_test $1 105 | 106 | run log "This is an info message" 107 | [ "${status}" -eq 0 ] 108 | [ $(cat ${SHEEP_LOG_FILE} | wc -l) -eq 1 ] 109 | [[ "$(cat ${SHEEP_LOG_FILE})" =~ 'INFO | This is an info message' ]] 110 | } 111 | 112 | check_log_absent() { 113 | init_log_test $1 114 | 115 | run log "This is an info message" 116 | [ "${status}" -eq 0 ] 117 | [ $(cat ${SHEEP_LOG_FILE} | wc -l) -eq 0 ] 118 | } 119 | 120 | @test "log absent when log level is ERROR" { 121 | check_log_absent ERROR 122 | } 123 | 124 | @test "log absent when log level is WARNING" { 125 | check_log_absent WARNING 126 | } 127 | 128 | @test "log present when log level is INFO" { 129 | check_log_present INFO 130 | } 131 | 132 | @test "log present when log level is DEBUG" { 133 | check_log_present DEBUG 134 | } 135 | 136 | check_log_debug_present() { 137 | init_log_test $1 138 | 139 | run log_debug "This is a debug message" 140 | [ "${status}" -eq 0 ] 141 | [ $(cat ${SHEEP_LOG_FILE} | wc -l) -eq 1 ] 142 | [[ "$(cat ${SHEEP_LOG_FILE})" =~ 'DEBUG | This is a debug message' ]] 143 | } 144 | 145 | check_log_debug_absent() { 146 | init_log_test $1 147 | 148 | run log_debug "This is a debug message" 149 | [ "${status}" -eq 0 ] 150 | [ $(cat ${SHEEP_LOG_FILE} | wc -l) -eq 0 ] 151 | } 152 | 153 | @test "log_debug absent when log level is ERROR" { 154 | check_log_debug_absent ERROR 155 | } 156 | 157 | @test "log_debug absent when log level is WARNING" { 158 | check_log_debug_absent WARNING 159 | } 160 | 161 | @test "log_debug absent when log level is INFO" { 162 | check_log_debug_absent INFO 163 | } 164 | 165 | @test "log_info present when log level is DEBUG" { 166 | check_log_debug_present DEBUG 167 | } 168 | -------------------------------------------------------------------------------- /tests/unit_tests/run: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | bats -v > /dev/null 2>&1 4 | 5 | if [ $? -ne 0 ]; then 6 | echo "" 7 | echo "ERROR : Bats binary was not found. Please ensure bats is installed on your system." 8 | echo " See https://github.com/sstephenson/bats" 9 | echo "" 10 | exit 1 11 | fi 12 | 13 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null 2>&1 && pwd)" 14 | 15 | export TEST_LOG_FILE="tests-$(date "+%Y-%m-%d_%H-%M-%S").log" 16 | 17 | bats ${DIR}/*.bats 18 | 19 | test_status=$? 20 | 21 | if [ ${test_status} -ne 0 ]; then 22 | if [ -n ${CI} ]; then 23 | echo "" 24 | echo "!!! See failures in log file ${DIR}/${TEST_LOG_FILE}" 25 | echo "" 26 | else 27 | cat ${DIR}/${TEST_LOG_FILE} 28 | fi 29 | fi 30 | 31 | exit ${test_status} 32 | -------------------------------------------------------------------------------- /tests/unit_tests/search_kernel_parameter.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "search_kernel_parameter with existing values" { 6 | source ${BATS_TEST_DIRNAME}/../../sheep 7 | 8 | export SHEEP_PARAMETERS='sheep.config=http://config.yml sheep.script=http://sheep.bash sheep.delay=5' 9 | 10 | run search_kernel_parameter sheep.config 11 | [ "${status}" -eq 0 ] 12 | [ "${output}" = "http://config.yml" ] 13 | 14 | flush_log 15 | 16 | run search_kernel_parameter sheep.script 17 | [ "${status}" -eq 0 ] 18 | [ "${output}" = "http://sheep.bash" ] 19 | 20 | flush_log 21 | 22 | run search_kernel_parameter sheep.delay 23 | [ "${status}" -eq 0 ] 24 | [ "${output}" = "5" ] 25 | } 26 | 27 | @test "search_kernel_parameter with existing values and a default value" { 28 | source ${BATS_TEST_DIRNAME}/../../sheep 29 | 30 | export SHEEP_PARAMETERS='sheep.config=http://config.yml sheep.script=http://sheep.bash sheep.delay=5' 31 | 32 | run search_kernel_parameter sheep.config default1 33 | [ "${status}" -eq 0 ] 34 | [ "${output}" = "http://config.yml" ] 35 | 36 | flush_log 37 | 38 | run search_kernel_parameter sheep.script default2 39 | [ "${status}" -eq 0 ] 40 | [ "${output}" = "http://sheep.bash" ] 41 | 42 | flush_log 43 | 44 | run search_kernel_parameter sheep.delay default3 45 | [ "${status}" -eq 0 ] 46 | [ "${output}" = "5" ] 47 | } 48 | 49 | @test "search_kernel_parameter with parameter defined twice return last" { 50 | source ${BATS_TEST_DIRNAME}/../../sheep 51 | 52 | export SHEEP_PARAMETERS='console=tty1 console=ttyS2,115200' 53 | 54 | run search_kernel_parameter console 55 | [ "${status}" -eq 0 ] 56 | [ "${output}" = "ttyS2,115200" ] 57 | } 58 | 59 | @test "search_kernel_parameter with non existing value returns empty string" { 60 | source ${BATS_TEST_DIRNAME}/../../sheep 61 | 62 | export SHEEP_PARAMETERS='sheep.config=http://config.yml sheep.script=http://sheep.bash sheep.delay=5' 63 | 64 | run search_kernel_parameter console 65 | [ "${status}" -eq 0 ] 66 | [ "${output}" = "" ] 67 | } 68 | 69 | @test "search_kernel_parameter with non existing value and default value returns default value" { 70 | source ${BATS_TEST_DIRNAME}/../../sheep 71 | 72 | export SHEEP_PARAMETERS='sheep.config=http://config.yml sheep.script=http://sheep.bash sheep.delay=5' 73 | 74 | run search_kernel_parameter console tty3 75 | [ "${status}" -eq 0 ] 76 | [ "${output}" = "tty3" ] 77 | } 78 | -------------------------------------------------------------------------------- /tests/unit_tests/search_mandatory_value.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "search_mandatory_value with existing values" { 6 | source ${BATS_TEST_DIRNAME}/../../sheep 7 | 8 | export CONFIG_FILE=${BATS_TEST_DIRNAME}/sheepCfgTestSearch_m_v1.yml 9 | 10 | run search_mandatory_value .param1 11 | [ "${status}" -eq 0 ] 12 | [ "${output}" = "value1" ] 13 | 14 | flush_log 15 | 16 | run search_mandatory_value .param2 17 | [ "${status}" -eq 0 ] 18 | [ "${output}" = "value2" ] 19 | 20 | flush_log 21 | 22 | run search_mandatory_value .param3 23 | [ "${status}" -eq 0 ] 24 | [ "${output}" = "value3" ] 25 | } 26 | 27 | @test "search_mandatory_value with no values" { 28 | source ${BATS_TEST_DIRNAME}/../../sheep 29 | 30 | export CONFIG_FILE=${BATS_TEST_DIRNAME}/sheepCfgTestSearch_m_v2.yml 31 | 32 | run search_mandatory_value .param1 33 | [ "${status}" -eq 1 ] 34 | 35 | flush_log 36 | 37 | run search_mandatory_value .param2 38 | [ "${status}" -eq 1 ] 39 | 40 | flush_log 41 | 42 | run search_mandatory_value .param3 43 | [ "${status}" -eq 1 ] 44 | } 45 | 46 | @test "search_mandatory_value with not existing param and an error message" { 47 | source ${BATS_TEST_DIRNAME}/../../sheep 48 | 49 | export CONFIG_FILE=${BATS_TEST_DIRNAME}/sheepCfgTestSearch_m_v3.yml 50 | 51 | run search_mandatory_value .param4 "No param4" 52 | [ "${status}" -eq 1 ] 53 | [ "${output}" = "ERROR : No param4" ] 54 | } 55 | 56 | 57 | -------------------------------------------------------------------------------- /tests/unit_tests/search_value.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load test_helper 4 | 5 | @test "search_value with existing values" { 6 | source ${BATS_TEST_DIRNAME}/../../sheep 7 | 8 | export CONFIG_FILE=${BATS_TEST_DIRNAME}/sheepCfgTestSearch_m_v1.yml 9 | 10 | run search_value .param1 11 | [ "${status}" -eq 0 ] 12 | [ "${output}" = "value1" ] 13 | 14 | flush_log 15 | 16 | run search_value .param2 17 | [ "${status}" -eq 0 ] 18 | [ "${output}" = "value2" ] 19 | 20 | flush_log 21 | 22 | run search_value .param3 23 | [ "${status}" -eq 0 ] 24 | [ "${output}" = "value3" ] 25 | } 26 | 27 | @test "search_value with existing values with a default value" { 28 | source ${BATS_TEST_DIRNAME}/../../sheep 29 | 30 | export CONFIG_FILE=${BATS_TEST_DIRNAME}/sheepCfgTestSearch_m_v1.yml 31 | 32 | run search_value .param1 default 33 | [ "${status}" -eq 0 ] 34 | [ "${output}" = "value1" ] 35 | 36 | flush_log 37 | 38 | run search_value .param2 default 39 | [ "${status}" -eq 0 ] 40 | [ "${output}" = "value2" ] 41 | 42 | flush_log 43 | 44 | run search_value .param3 default 45 | [ "${status}" -eq 0 ] 46 | [ "${output}" = "value3" ] 47 | } 48 | 49 | @test "search_value with not existing param and no default value" { 50 | source ${BATS_TEST_DIRNAME}/../../sheep 51 | 52 | export CONFIG_FILE=${BATS_TEST_DIRNAME}/sheepCfgTestSearch_m_v3.yml 53 | 54 | run search_value .param4 55 | [ "${status}" -eq 0 ] 56 | [ -z "${output}" ] 57 | } 58 | 59 | @test "search_value with not existing param and a default value" { 60 | source ${BATS_TEST_DIRNAME}/../../sheep 61 | 62 | export CONFIG_FILE=${BATS_TEST_DIRNAME}/sheepCfgTestSearch_m_v3.yml 63 | 64 | run search_value .param4 "defaultForParam4" 65 | [ "${status}" -eq 0 ] 66 | [ "${output}" = "defaultForParam4" ] 67 | } 68 | 69 | @test "search_value with not existing param and no default value and optionyamlValue" { 70 | source ${BATS_TEST_DIRNAME}/../../sheep 71 | 72 | export CONFIG_FILE=${BATS_TEST_DIRNAME}/sheepCfgTestSearch_m_v4.yml 73 | 74 | run search_value ".cloudInit.userData" "" "yaml" 75 | [ "${status}" -eq 0 ] 76 | [ "${output}" = "users: 77 | - name: linux 78 | lock_passwd: false 79 | ssh_authorized_keys: ssh-rsa ojoihdahioahdjnfnainfioajefijaeoifhaoiehfioah apkpẑakdkzepojfijzeifjiozefiozejiofhozehfoheofheaoha 80 | sudo: ALL=(ALL) NOPASSWD:ALL 81 | shell: /bin/bash 82 | chpasswd: 83 | expire: false 84 | list: | 85 | linux:linux 86 | ssh_pwauth: true" ] 87 | } -------------------------------------------------------------------------------- /tests/unit_tests/sheepCfgConfigVariable1.yml: -------------------------------------------------------------------------------- 1 | bootloader: 2 | image: http://172.19.118.1/rootfs.qcow2 3 | kernel_parameter: 4 | 5 | linux: 6 | image: http://172.19.118.1/rootfs.qcow2 7 | label: 8 | device: 9 | selinux: 10 | 11 | network: 12 | interfaces: 13 | - id: ens1 14 | 15 | pxePilot: 16 | enable: false 17 | url: 18 | config_after_reboot: 19 | -------------------------------------------------------------------------------- /tests/unit_tests/sheepCfgConfigVariable2.yml: -------------------------------------------------------------------------------- 1 | bootloader: 2 | image: http://172.19.118.1/qcow2/os-deploy-bootloader.tar.gz 3 | kernel_parameter: 4 | 5 | linux: 6 | image: http://172.19.118.1/squashfs/disco.squashfs 7 | label: 8 | device: 9 | selinux: 10 | 11 | network: 12 | interfaces: 13 | - id: ens1 14 | 15 | pxePilot: 16 | enable: true 17 | url: http://172.19.118.1:3478 18 | config_after_reboot: 19 | -------------------------------------------------------------------------------- /tests/unit_tests/sheepCfgConfigVariable3.yml: -------------------------------------------------------------------------------- 1 | bootloader: 2 | image: http://172.19.118.1/qcow2/os-deploy-bootloader.tar.gz 3 | kernel_parameter: 4 | 5 | linux: 6 | image: http://172.19.118.1/squashfs/disco.squashfs 7 | label: 8 | device: 9 | selinux: 10 | 11 | network: 12 | interfaces: 13 | - id: ens1 14 | 15 | pxePilot: 16 | enable: true 17 | url: 18 | config_after_reboot: 19 | -------------------------------------------------------------------------------- /tests/unit_tests/sheepCfgConfigVariable4.yml: -------------------------------------------------------------------------------- 1 | bootloader: 2 | image: http://172.19.118.1/bootloader 3 | kernel_parameter: 4 | 5 | linux: 6 | image: http://172.19.118.1/image.qcow2 7 | label: 8 | device: 9 | selinux: 10 | 11 | network: 12 | interfaces: 13 | - id: ens1 14 | 15 | pxePilot: 16 | enable: false 17 | url: 18 | config_after_reboot: 19 | -------------------------------------------------------------------------------- /tests/unit_tests/sheepCfgConfigVariable5.yml: -------------------------------------------------------------------------------- 1 | bootloader: 2 | image: http://172.19.118.1/rootfs.qcow2 3 | kernel_parameter: 'console=ttyS1,57600n8' 4 | 5 | linux: 6 | image: 7 | label: 8 | device: 9 | selinux: 10 | 11 | network: 12 | interfaces: 13 | - id: ens1 14 | 15 | pxePilot: 16 | enable: false 17 | url: 18 | config_after_reboot: 19 | -------------------------------------------------------------------------------- /tests/unit_tests/sheepCfgTestSearch_m_v1.yml: -------------------------------------------------------------------------------- 1 | param1: value1 2 | param2: value2 3 | param3: value3 -------------------------------------------------------------------------------- /tests/unit_tests/sheepCfgTestSearch_m_v2.yml: -------------------------------------------------------------------------------- 1 | param1: 2 | param2: 3 | param3: 4 | -------------------------------------------------------------------------------- /tests/unit_tests/sheepCfgTestSearch_m_v3.yml: -------------------------------------------------------------------------------- 1 | param4: 2 | -------------------------------------------------------------------------------- /tests/unit_tests/sheepCfgTestSearch_m_v4.yml: -------------------------------------------------------------------------------- 1 | cloudInit: 2 | enable: true 3 | metaData: 4 | instance-id: 001-local01 5 | local-hostname: sheep 6 | network: 7 | version: 2 8 | ethernets: 9 | ens1: 10 | dhcp4: true 11 | userData: 12 | users: 13 | - name: linux 14 | lock_passwd: false 15 | ssh_authorized_keys: ssh-rsa ojoihdahioahdjnfnainfioajefijaeoifhaoiehfioah apkpẑakdkzepojfijzeifjiozefiozejiofhozehfoheofheaoha 16 | sudo: ALL=(ALL) NOPASSWD:ALL 17 | shell: /bin/bash 18 | chpasswd: 19 | expire: false 20 | list: | 21 | linux:linux 22 | ssh_pwauth: true 23 | -------------------------------------------------------------------------------- /tests/unit_tests/sheepCfgUnitTest.yml: -------------------------------------------------------------------------------- 1 | param1: value1 2 | param2: value2 3 | param3: value3 4 | -------------------------------------------------------------------------------- /tests/unit_tests/sheepCfgUserDataCID1.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | users: 3 | - name: linux 4 | sudoer: true 5 | password: linux 6 | ssh_authorized_key: ssh 7 | shell: /bin/bash 8 | local_hostname: sheep 9 | -------------------------------------------------------------------------------- /tests/unit_tests/sheepCfgUserDataCID2.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | users: 3 | - name: linux 4 | sudoer: true 5 | ssh_authorized_key: ssh 6 | shell: /bin/bash 7 | local_hostname: sheep 8 | -------------------------------------------------------------------------------- /tests/unit_tests/sheepCfgUserDataCID3.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | users: 3 | - name: linux 4 | sudoer: true 5 | password: linux 6 | shell: /bin/bash 7 | local_hostname: sheep 8 | -------------------------------------------------------------------------------- /tests/unit_tests/sheepCfgUserDataCID4.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | users: 3 | - name: linux 4 | sudoer: true 5 | shell: /bin/bash 6 | local_hostname: sheep 7 | -------------------------------------------------------------------------------- /tests/unit_tests/test_helper.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BATS_OUT_LOG=${BATS_TEST_DIRNAME}/${TEST_LOG_FILE:-test.log} 4 | 5 | setup() { 6 | { 7 | title "#${BATS_TEST_NUMBER} | Begin test | ${BATS_TEST_NAME} | ${BATS_TEST_FILENAME}" 8 | } >> ${BATS_OUT_LOG} 9 | } 10 | 11 | teardown() { 12 | flush_log 13 | { 14 | title "#${BATS_TEST_NUMBER} | End test | ${BATS_TEST_NAME} | ${BATS_TEST_FILENAME}" 15 | } >> ${BATS_OUT_LOG} 16 | } 17 | 18 | title() { 19 | { 20 | echo "" 21 | echo "---------------------------------------------------------------------------------------------------------------------" 22 | echo "--- ${1}" 23 | echo "---------------------------------------------------------------------------------------------------------------------" 24 | echo "" 25 | } >> ${BATS_OUT_LOG} 26 | } 27 | 28 | flush_log() { 29 | { 30 | echo "${lines[@]}" 31 | } >> ${BATS_OUT_LOG} 32 | } 33 | --------------------------------------------------------------------------------