From 70fa03898ee412e30b6b87cf961004bf16ccaef4 Mon Sep 17 00:00:00 2001 From: Marek Szwałkiewicz Date: Wed, 1 Mar 2023 12:27:28 +0100 Subject: [GATING] Add configuration for Azure3 gating in the fork of chained-ci MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change includes: * moving submodules of chained-ci-roles and chained-ci-vue as static folders to the repo (they were quite old and not updated for some time) * create azure access artifacts * add config for azure3 gating pipeline Issue-ID: INT-2207 Signed-off-by: Marek Szwałkiewicz Change-Id: Idb475c166d78f10ed4204153ab634110aa9093f6 --- .gitlab-ci.yml | 87 +++- .gitmodules | 6 - chained-ci-vue/LICENSE | 201 ++++++++ chained-ci-vue/README.md | 4 + chained-ci-vue/favicon.png | Bin 0 -> 5898 bytes chained-ci-vue/index.html | 238 +++++++++ chained-ci-vue/init.sh | 68 +++ chained-ci-vue/js/config.js | 16 + chained-ci-vue/js/index.js | 250 +++++++++ chained-ci-vue/js/lib.js | 558 +++++++++++++++++++++ chained-ci-vue/js/visibility.LICENSE | 3 + chained-ci-vue/js/visibility.core.js | 189 +++++++ chained-ci-vue/js/visibility.timers.js | 161 ++++++ chained-ci-vue/logo.svg | 130 +++++ chained-ci-vue/style.css | 219 ++++++++ pod_config/config/artifacts/azure.zip | Bin 0 -> 1132 bytes pod_config/config/az14-gating3.yaml | 168 +++++++ pod_config/config/idf-az14-gating3.yaml | 171 +++++++ pod_inventory/group_vars/all.yml | 194 ++++--- .../host_vars/onap_oom_gating_azure_3.yml | 85 ++++ pod_inventory/inventory | 4 +- roles/LICENSE | 201 ++++++++ roles/README.md | 15 + roles/artifact_init/defaults/main.yaml | 2 + roles/artifact_init/filter_plugins/filters.py | 8 + roles/artifact_init/tasks/main.yml | 180 +++++++ roles/get_artifacts/defaults/main.yml | 7 + roles/get_artifacts/filter_plugins/filters.py | 8 + roles/get_artifacts/tasks/binary.yml | 244 +++++++++ roles/get_artifacts/tasks/get_one_artifact.yml | 49 ++ roles/get_artifacts/tasks/job_id_fetch.yml | 20 + roles/get_artifacts/tasks/limit_to.yml | 20 + roles/get_artifacts/tasks/main.yml | 34 ++ roles/get_artifacts/tasks/url.yml | 13 + roles/gitlab-ci-generator/defaults/main.yml | 3 + roles/gitlab-ci-generator/tasks/main.yml | 45 ++ roles/gitlab-ci-generator/templates/gitlab-ci.yml | 204 ++++++++ roles/library/filepath.py | 40 ++ roles/logo.png | Bin 0 -> 4723 bytes roles/logo.svg | 130 +++++ roles/prepare/README.md | 28 ++ roles/prepare/tasks/continue.yml | 15 + roles/prepare/tasks/except.yml | 55 ++ roles/prepare/tasks/exit.yml | 13 + roles/prepare/tasks/main.yml | 93 ++++ roles/prepare/tasks/only.yml | 57 +++ roles/run-ci/tasks/grafana_start.yml | 42 ++ roles/run-ci/tasks/grafana_stop.yml | 53 ++ roles/run-ci/tasks/main.yml | 270 ++++++++++ roles/trigger_myself/tasks/main.yml | 75 +++ 50 files changed, 4563 insertions(+), 113 deletions(-) delete mode 100644 .gitmodules create mode 100644 chained-ci-vue/LICENSE create mode 100644 chained-ci-vue/README.md create mode 100644 chained-ci-vue/favicon.png create mode 100644 chained-ci-vue/index.html create mode 100755 chained-ci-vue/init.sh create mode 100644 chained-ci-vue/js/config.js create mode 100644 chained-ci-vue/js/index.js create mode 100644 chained-ci-vue/js/lib.js create mode 100644 chained-ci-vue/js/visibility.LICENSE create mode 100644 chained-ci-vue/js/visibility.core.js create mode 100644 chained-ci-vue/js/visibility.timers.js create mode 100644 chained-ci-vue/logo.svg create mode 100644 chained-ci-vue/style.css create mode 100644 pod_config/config/artifacts/azure.zip create mode 100644 pod_config/config/az14-gating3.yaml create mode 100644 pod_config/config/idf-az14-gating3.yaml create mode 100644 pod_inventory/host_vars/onap_oom_gating_azure_3.yml create mode 100644 roles/LICENSE create mode 100644 roles/README.md create mode 100644 roles/artifact_init/defaults/main.yaml create mode 100644 roles/artifact_init/filter_plugins/filters.py create mode 100644 roles/artifact_init/tasks/main.yml create mode 100644 roles/get_artifacts/defaults/main.yml create mode 100644 roles/get_artifacts/filter_plugins/filters.py create mode 100644 roles/get_artifacts/tasks/binary.yml create mode 100644 roles/get_artifacts/tasks/get_one_artifact.yml create mode 100644 roles/get_artifacts/tasks/job_id_fetch.yml create mode 100644 roles/get_artifacts/tasks/limit_to.yml create mode 100644 roles/get_artifacts/tasks/main.yml create mode 100644 roles/get_artifacts/tasks/url.yml create mode 100644 roles/gitlab-ci-generator/defaults/main.yml create mode 100644 roles/gitlab-ci-generator/tasks/main.yml create mode 100644 roles/gitlab-ci-generator/templates/gitlab-ci.yml create mode 100644 roles/library/filepath.py create mode 100644 roles/logo.png create mode 100644 roles/logo.svg create mode 100644 roles/prepare/README.md create mode 100644 roles/prepare/tasks/continue.yml create mode 100644 roles/prepare/tasks/except.yml create mode 100644 roles/prepare/tasks/exit.yml create mode 100644 roles/prepare/tasks/main.yml create mode 100644 roles/prepare/tasks/only.yml create mode 100644 roles/run-ci/tasks/grafana_start.yml create mode 100644 roles/run-ci/tasks/grafana_stop.yml create mode 100644 roles/run-ci/tasks/main.yml create mode 100644 roles/trigger_myself/tasks/main.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6c4b312..c953d1f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,15 +10,14 @@ stages: - lint - config - # - infra_install - # - virt_install - # - apps - # - check + - infra_install + - virt_install + - apps + - check variables: GIT_SUBMODULE_STRATEGY: recursive VAULT_FILE: .vault - RUNNER_TAG: ################################################################################ # Shared parameters @@ -49,8 +48,7 @@ variables: expire_in: 1 yrs .runner_env: &runner_env - CHAINED_CI_SRC: "" # Url to the gitlab chained ci project - # CHAINED_CI_SRC: "https://gitlab.devops.telekom.de/tnap/onapcommunity/integrationproject/onapdeployment/chained-ci.git" + CHAINED_CI_SRC: "https://gitlab.com/onap/integration/pipelines/chained-ci.git" ################################################################################ # Linting @@ -170,25 +168,62 @@ config:onap-daily-unh-oom-master: <<: *onap-daily-unh-oom-master_global <<: *set_config <<: *artifacts_longexpire -# infra_deploy:onap-daily-unh-oom-master: -# stage: infra_install -# <<: *onap-daily-unh-oom-master_global -# <<: *run_ci -# <<: *artifacts_longexpire -# virt_install:onap-daily-unh-oom-master: -# stage: virt_install -# <<: *onap-daily-unh-oom-master_global -# <<: *run_ci -# <<: *artifacts_longexpire -# apps_deploy:onap-daily-unh-oom-master: -# stage: apps -# <<: *onap-daily-unh-oom-master_global -# <<: *run_ci -# <<: *artifacts_longexpire -# apps_test:onap-daily-unh-oom-master: -# stage: check -# <<: *onap-daily-unh-oom-master_global -# <<: *run_ci +infra_deploy:onap-daily-unh-oom-master: + stage: infra_install + <<: *onap-daily-unh-oom-master_global + <<: *run_ci + <<: *artifacts_longexpire +virt_install:onap-daily-unh-oom-master: + stage: virt_install + <<: *onap-daily-unh-oom-master_global + <<: *run_ci + <<: *artifacts_longexpire +apps_deploy:onap-daily-unh-oom-master: + stage: apps + <<: *onap-daily-unh-oom-master_global + <<: *run_ci + <<: *artifacts_longexpire +apps_test:onap-daily-unh-oom-master: + stage: check + <<: *onap-daily-unh-oom-master_global + <<: *run_ci + +################################################################################ +# onap_oom_gating_azure_3 +################################################################################ + +.onap_oom_gating_azure_3_global: &onap_oom_gating_azure_3_global + variables: + pod: onap_oom_gating_azure_3 + <<: *runner_env + environment: + name: azure/onap_gating_3/oom_gating + only: + variables: + - $POD == "onap_oom_gating_azure_3" + refs: + - web + - schedules + - triggers + +config:onap_oom_gating_azure_3: + stage: config + <<: *onap_oom_gating_azure_3_global + <<: *set_config + <<: *artifacts +build_integration:onap_oom_gating_azure_3: + stage: infra_install + <<: *onap_oom_gating_azure_3_global + <<: *run_ci +onap_deploy:onap_oom_gating_azure_3: + stage: apps + <<: *onap_oom_gating_azure_3_global + <<: *run_ci + <<: *artifacts +onap_test:onap_oom_gating_azure_3: + stage: check + <<: *onap_oom_gating_azure_3_global + <<: *run_ci ## # End of generated file diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index b0a82e6..0000000 --- a/.gitmodules +++ /dev/null @@ -1,6 +0,0 @@ -[submodule "chained-ci-vue"] - path = chained-ci-vue - url = https://gitlab.com/Orange-OpenSource/lfn/ci_cd/chained-ci-vue.git -[submodule "roles"] - path = roles - url = https://gitlab.com/Orange-OpenSource/lfn/ci_cd/chained-ci-roles.git diff --git a/chained-ci-vue/LICENSE b/chained-ci-vue/LICENSE new file mode 100644 index 0000000..3be62f8 --- /dev/null +++ b/chained-ci-vue/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018 Orange-OpenSource / lfn / ci_cd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/chained-ci-vue/README.md b/chained-ci-vue/README.md new file mode 100644 index 0000000..e4baa04 --- /dev/null +++ b/chained-ci-vue/README.md @@ -0,0 +1,4 @@ +Chained-ci-vue +================ + +Submodule for a better visualization of the chained-ci diff --git a/chained-ci-vue/favicon.png b/chained-ci-vue/favicon.png new file mode 100644 index 0000000..6e07930 Binary files /dev/null and b/chained-ci-vue/favicon.png differ diff --git a/chained-ci-vue/index.html b/chained-ci-vue/index.html new file mode 100644 index 0000000..abc92bc --- /dev/null +++ b/chained-ci-vue/index.html @@ -0,0 +1,238 @@ + + + + + + Pipelines + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+

Please set your gitlabs + private tokens +

+
+
+
+ + +
+ + +
+ +
+
+
+
this is required and can be generated on your user profile like: + {{gitlabProfileToken}} + (Only API option is needed)
+
+
+
+
+ +
+
+
+
+ Scenario filter: + +
+
+ Next update: {{ timer }} / {{ actualRefresh }} + + (Please set filter or optimize it to have a better update time) + +
+ +
+
+
+
+ +
+
{{ pipelines[id].scenario }}
+
{{ pipelines[id].branch }}
+
{{ pipelines[id].date }}
+
{{ pipelines[id].time }}
+
{{ Math.round(pipelines[id].details.duration/60) }} min
+
+ +
+
+
+
+
+
{{ stage.name }}
+
+
+
+
+
+
+ {{ job.shortname }}
+
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+ Load more pipelines +
+
+
+
+
+ + + +
+
+ +

+ {{ title }} +

+
+ {{ message }} +
+
+
+
+ + +
+
+ +

+
+ + Pipeline {{ pipeline.name }} {{ pipeline.id }} + + +
+
+ +

+
+
+
Loading, please wait...
+
+
+
+
+
{{ stage.name }}
+ +
+
+
+
+
The pipeline was probably not triggered, check console: + +
+
+
+
+
+
+
+
+
+ + + diff --git a/chained-ci-vue/init.sh b/chained-ci-vue/init.sh new file mode 100755 index 0000000..ff308e8 --- /dev/null +++ b/chained-ci-vue/init.sh @@ -0,0 +1,68 @@ +#!/usr/bin/env sh + +export RUN_SCRIPT=${0} +export INV_FOLDER=${1} +export ROOT_FOLDER=${PWD} + +mkdir ${ROOT_FOLDER}/public +mkdir -p ${ROOT_FOLDER}/public/${INV_FOLDER}/host_vars +mkdir -p ${ROOT_FOLDER}/public/${INV_FOLDER}/group_vars + +yaml2json(){ + SRC=$1 + DEST="public/${SRC%.*}.json"; + echo "convert ${SRC} to ${DEST}" + yq '.' ${SRC} > ${DEST} +} + +updateConf(){ + ITEM=$1 + VALUE=$(echo $2| sed 's/:/\\:/g') + echo "set $ITEM to $VALUE" + sed -i -e "s|^var $ITEM = .*$|var $ITEM = \"$VALUE\";|" ${ROOT_FOLDER}/public/js/config.js +} + +updateConfObj(){ + ITEM=$1 + VALUE=$(echo $2| sed 's/:/\\:/g') + echo "set $ITEM to $VALUE" + sed -i -e "s|^var $ITEM = .*$|var $ITEM = $VALUE;|" ${ROOT_FOLDER}/public/js/config.js +} + +## Install Yq +pip install --user yq +include_splitted=$( yq -r '.include' ${CI_CONFIG_PATH}) +# Convert chained_ci files to json +if [ -z "${include_splitted}" ]; then + # Monolitic gitlab ci, doing nothing + echo "no need to merge splitted files" +else + # Non monolitic gitlab ci, add the yaml part into main one + for part_file in $(yq -r '.include[]' ${CI_CONFIG_PATH}); do + sed 's/---//' $part_file >> ${CI_CONFIG_PATH} + done +fi +yaml2json ${CI_CONFIG_PATH} + +for sc in ${INV_FOLDER}/host_vars/*.yml; do + yaml2json $sc +done +yaml2json ${INV_FOLDER}/group_vars/all.yml + +# Copy site +cp -rf chained-ci-vue/js public/ +cp -rf chained-ci-vue/index.html public/ +cp -rf chained-ci-vue/style.css public/ + +# Generate config +updateConf gitlabUrl ${CI_PROJECT_URL%"$CI_PROJECT_PATH"} +updateConf chainedCiProjectId ${CI_PROJECT_ID} +updateConf scenarioFolder "${INV_FOLDER}/" +updateConf chainedCiUrl ${CI_PROJECT_URL} +updateConf gitlabCiFilename "${CI_CONFIG_PATH%.*}.json" +updateConf pipelines_size ${pipeline_size:-10} +updateConf rootUrl "${CI_PROJECT_NAME}/" + +# get all gitlab used +tokenTargets=$(jq -r '.gitlab.git_projects | map(try(.url | split("/")| .[2]))| sort | unique | @csv' ${ROOT_FOLDER}/public/${INV_FOLDER}/group_vars/all.json) +updateConfObj tokenTargets "[${tokenTargets}]" diff --git a/chained-ci-vue/js/config.js b/chained-ci-vue/js/config.js new file mode 100644 index 0000000..f71ff70 --- /dev/null +++ b/chained-ci-vue/js/config.js @@ -0,0 +1,16 @@ +// Generated by init.sh +var gitlabUrl = ; +var chainedCiProjectId = ; +var scenarioFolder = ; +var chainedCiUrl = ; +var rootUrl = ; +var gitlabCiFilename = ; +var pipelines_size = ; +var updateTimer = 60; +var tokenTargets = []; +var optimizedRefreshLevel = 5; + +// tool url +var gitlabCiFile = gitlabCiFilename; +var gitlabApi = gitlabUrl+'api/v4/projects/'; +var gitlabProfileToken = '/profile/personal_access_tokens'; diff --git a/chained-ci-vue/js/index.js b/chained-ci-vue/js/index.js new file mode 100644 index 0000000..447a27b --- /dev/null +++ b/chained-ci-vue/js/index.js @@ -0,0 +1,250 @@ +// Init token we need to fetch +var privateTokens = []; +tokenTargets.forEach(function(target){ + privateTokens.push({'target': target, + 'value': '', + 'msg': '', + 'icon': '', + 'accessGranted': false}) +}) +pipelineRefreshId = -1; +// Start the authentication +authenticate(); + + +/** + * VUE for authentication form + * + * @data {list} privateTokens list of token objects + * @data {list} gitlabProfileToken list of gitlab token to ask + * + * @computed {dict} tokensByTarget dict of tokens by target + * @computed {dict} globalAccessGranted remove the form if all token are verified + * + * @methods {function} check the form by starting validateTokens + */ +var headerVue = new Vue({ + el: '#header', + data: {project: {}} +}); + +/** + * VUE for pipelines + * + * @data {bool} loading lock var to avoid concurrent load + * @data {dict} pipelines dict of pipelines + * @data {list} pipelinesIds list of pipelines + * @data {bool} accessGranted show the pipelines + * @data {int} pages page indication + * + * @computed {list} sortedPipelinesIds list of reverse pipelines ids + * + * @methods {function} job_details load job details on click + * @methods {function} handleScroll load new pipelines on scroll to bottom + */ +var pipelinesVue = new Vue({ + el: '#pipelines', + data: { + loading: false, + newPipelineUrl: '', + pipelines: {}, + pipelinesIds: [], + accessGranted: false, + pipelineFilter: '', + pages: 1, + timer: updateTimer, + actualRefresh: updateTimer * 2, + optimizedRefresh: false, + }, + computed: { + sortedPipelinesIds: function() { + filteredList = []; + var pipelines = this.pipelines; + var filter = this.pipelineFilter; + this.pipelinesIds.sort().reverse().forEach( + function(pipelineId){ + if(pipelines[pipelineId].scenario.includes(filter)){ + filteredList.push(pipelineId) + } + } + ); + this.optimizedRefresh = (filteredList.length <= optimizedRefreshLevel); + if(this.optimizedRefresh){ + updateLoop(updateTimer/2); + }else{ + updateLoop(updateTimer); + } + return filteredList; + } + }, + methods:{ + mouseOverJob: function(job){ + iconMouseOver(job); + }, + mouseLeaveJob: function(job){ + iconMouseLeave(job); + }, + jobAction: function(job){ + jobActionSwitch(job.status, job.id) + }, + jobDetails: function(event, job){ + taskDetailsVue.showModal = true; + taskDetailsVue.showWaiting = true; + taskDetailsVue.showPipeline = false; + loadSubPipeline(job.id, job.name); + }, + loadMore: function() { + if (!pipelinesVue.loading){ + pipelinesVue.loading = true; + pipelinesVue.pages += 1; + loadPipelines(pipelinesVue.pages); + } + }, + } +}); + +// Vue.directive('scroll', { +// inserted: function(el, binding) { +// let f = function(evt) { +// if (binding.value(evt, el)) {} +// }; +// window.addEventListener('scroll', f); +// }, +// }); + +/** + * VUE for authentication form + * + * @data {list} privateTokens list of token objects + * @data {list} gitlabProfileToken list of gitlab token to ask + * + * @computed {dict} tokensByTarget dict of tokens by target + * @computed {dict} globalAccessGranted remove the form if all token are verified + * + * @methods {function} check the form by starting validateTokens + */ +var authVue = new Vue({ + el: '#auth', + data: {privateTokens: privateTokens, + gitlabProfileToken: gitlabProfileToken}, + methods:{ + checkForm: function (e) { + validateTokens(this.privateTokens); + e.preventDefault(); + } + }, + computed: { + tokensByTarget: function() { + tokens = {} + this.privateTokens.forEach(function(token){ + tokens[token.target] = token.value + }); + return tokens + }, + globalAccessGranted: function() { + granted = true; + this.privateTokens.forEach(function(token){ + granted = (granted && token.accessGranted) + }); + if (granted){ + localStorage.setItem("chained_ci_tokens", JSON.stringify(this.privateTokens)); + load() + } + pipelinesVue.accessGranted = granted + return granted; + } + } +}); + +/** + * VUE for the detail of a job (show the sub pipeline) + * + * @data {bool} showModal show the modal vue + * @data {bool} showPipeline show the pipeline + * @data {bool} showWaiting show the waiting message + * @data {bool} chainedCiFailure prompt a message of chained ci failed + * @data {dict} pipeline pipeline data + */ +var taskDetailsVue = new Vue({ + el: '#task_details', + data: { + showModal: false, + showPipeline: false, + showWaiting: false, + chainedCiFailure: false, + pipeline: { + 'name': '', + 'url': '', + 'id': '', + 'status': '', + 'statusIcon': '', + 'console': '', + 'stages': [], + 'parentTaskId': '', + 'parentTaskName': '', + } + }, + methods:{ + mouseOverPipeline: function(pipeline){ + iconMouseOver(pipeline); + }, + mouseLeavePipeline: function(pipeline){ + iconMouseLeave(pipeline); + }, + jobAction: function(pipeline){ + console.log(pipeline) + this.showModal = false; + this.showPipeline = false; + jobActionSwitch(pipeline.status, pipeline.parentTaskId) + } + } +}); + +/** + * VUE for alert + * + * @data {bool} showModal show the modal vue + * @data {bool} showPipeline show the pipeline + * @data {bool} showWaiting show the waiting message + * @data {bool} chainedCiFailure prompt a message of chained ci failed + * @data {dict} pipeline pipeline data + */ +var alertVue = new Vue({ + el: '#alert', + data: { + showModal: false, + title: '', + message: '', + } +}); + +// Modal template +Vue.component('modal', { + template: '#modal-template', + methods:{ + closeModal: function(event, emit){ + if(event.target.className == 'modal-wrapper'){ + // emit('close') + alertVue.showModal = false + alertVue.title = '' + alertVue.message = '' + taskDetailsVue.showModal = false + taskDetailsVue.showPipeline = false + taskDetailsVue.showWaiting = false + taskDetailsVue.chainedCiFailure = false + taskDetailsVue.pipeline = { + 'name': '', + 'url': '', + 'id': '', + 'status': '', + 'statusIcon': '', + 'console': '', + 'stages': [], + 'parentTaskId': '', + 'parentTaskName': '', + } + updatePipelines + } + }, + }, +}) diff --git a/chained-ci-vue/js/lib.js b/chained-ci-vue/js/lib.js new file mode 100644 index 0000000..8ce3e11 --- /dev/null +++ b/chained-ci-vue/js/lib.js @@ -0,0 +1,558 @@ +/** + * Different functions to load pipelines and jobs from gitlab + * + * Description. (use period) + * + * @link https://gitlab.com/Orange-OpenSource/lfn/ci_cd/chained-ci-vue/blob/master/js/lib.js + * @file lib.js + * @author David Blaisonneau + */ + + + /** + * Load the pipelines + */ +function load(){ + // Load gitlab-ci and save it to configCi var + getJson(gitlabCiFile, function(resp) { + configCi = resp; + // Get Gitlab project name + loadTitle(); + // Load last pipelines + pipelinesVue.loading = true; + loadPipelines(0); + + setInterval(function (){ + if(pipelinesVue.timer > 0 ){ + pipelinesVue.timer = pipelinesVue.timer - 1; + } + }, 1000); + updateLoop(updateTimer) + // setInterval(() => { + // console.log("set refresh to "+ (updateTimer * 1000)+ "ms"); + // updatePipelines(); + // }, updateTimer * 1000); + }); +} + +function updateLoop(timer){ + // The refresh time has changed, update loop + if(timer != pipelinesVue.actualRefresh){ + // If we had a Visibility loop, stop it + if(pipelineRefreshId >= 0){ + console.log("stop actual refresh loop ["+pipelineRefreshId+"]") + Visibility.stop(pipelineRefreshId); + } + pipelinesVue.timer = timer + pipelinesVue.actualRefresh = timer; + console.log("set refresh to "+ timer + "s for next update"); + pipelineRefreshId = Visibility.every(timer * 1000, + function (){ + console.log("Update pipelines, then sleep for "+ timer +" seconds") + // Update the pipelines + updatePipelines(); + // Update the timer + pipelinesVue.timer = timer + }); + } + + +} + +/** + * Sort jobs by stage + * + * Get the whole list of jobs in a pipeline and return a list of stages dict + * containing the name of the stage and the list of jobs in this stage + * + * @params {list} jobs List of jobs sent by the /jobs API + * + * @return {list} List of {'name': 'stage name', 'jobs': []} + */ +function jobsByStages(jobs){ + stages = {}; + stagesList = stages2List(jobs) + stagesList.forEach(function(stage){ + stages[stage]={'name': stage, 'jobs': []}}) + jobs.forEach(function(job){ + job.statusIcon = getIcon(job.status); + stages[job.stage].jobs.push(job) + }); + jobsByStagesList = [] + // return a list order by stage step + stagesList.forEach(function(stage){ + jobsByStagesList.push(stages[stage]) + }); + return jobsByStagesList +} + +/** + * Get stages list from jobs list + * + * Get a list of stages used by the jobs + * + * @params {list} jobs List of jobs sent by the /jobs API + * + * @return {list} List of stage names + */ +function stages2List(jobs){ + stagesList = [] + jobs.forEach(function(job) { + if(stagesList.indexOf(job.stage) < 0){ + stagesList.push(job.stage) + } + }) + return stagesList +} + +/** + * Get an icon class + * + * Get an icon class name from a string + * + * @params {str} type name of the icon to get + * + * @return {str} icon class + */ +function getIcon(type){ + switch(type){ + case 'failed': + return 'fa fa-times-circle w3-text-red' + break; + case 'success': + return 'fa fa-check-circle w3-text-green' + break; + case 'running': + return 'fa fa-play-circle w3-text-blue' + break; + case 'waiting': + return 'fa fa-pause-circle w3-text-orange' + break; + case 'skipped': + return 'fa fa-dot-circle w3-text-blue-gray' + break; + case 'created': + return 'fa fa-circle-notch w3-text-blue-gray' + break; + case 'canceled': + return 'fa fa-stop-circle w3-text-blue-gray' + break; + case 'retry': + return 'fa fa-plus-circle w3-text-orange' + break; + case 'stop': + return 'fa fa-stop-circle w3-text-orange' + break; + default: + return 'fa fa-question-circle' + } +} + + +/** + * Wrapper to call gitlab api + * + * @params {str} project gitlab project id + * @params {str} call api function called + * @params {function} reqOnload function to call on load + * @params {str} api base gitlab api to call + * @params {str} method HTTP Method + */ +function gitlabCall(project, call, reqOnload, + api = gitlabApi, method = 'GET'){ + var requestURL = api+project+'/'+call; + getJson(requestURL, reqOnload, getToken(api), method); +} + +/** + * Get a JSON from an api + * + * GET a json from an url and run a function on it + * + * @params {str} requestURL gitlab project id + * @params {function} reqOnload function to call on load + * @params {str} token PRIVATE-TOKEN to add if needed (default: null) + * @params {str} method HTTP Method + */ +function getJson(requestURL, reqOnload, token = null, method = 'GET'){ + var request = new XMLHttpRequest(); + request.open(method, requestURL); + if (token){ + request.setRequestHeader('PRIVATE-TOKEN', token); + }; + request.responseType = 'json'; + request.send(); + request.onload = function() { + reqOnload(request.response); + } +} + +/** + * Get the token of a gitlab API + * + * Get API token from auth vector depending of the url to call + * + * @params {string} url the url to call + */ +function getToken(url){ + target = url.split('/')[2] + return authVue.tokensByTarget[target] +} + +/** + * Validate all API tokens + * + * Call gitlab API with a token to check it + * - Set VUEJS privateTokens.globalAccessGranted to boolean result of all + * authentications + * - Update privateTokens list to update the vue + * + * @params {list} tokens list of tokens from the form + */ +function validateTokens(tokens){ + tokens.forEach(function(token){ + if(token.value){ + getJson( + requestURL = 'https://'+ token.target + + '/api/v4/projects/?per_page=1', + function(resp){ + globalSuccess = true; + success = (resp.length == 1) + privateTokens.forEach(function(globalToken){ + if(token.target == globalToken.target){ + globalToken.accessGranted = success; + if(success){ + globalToken.icon = getIcon('success'); + }else{ + globalToken.icon = getIcon('failed'); + } + } + globalSuccess = (globalSuccess && globalToken.accessGranted) + }) + privateTokens.globalAccessGranted = globalSuccess + }, + token.value); + } + }); +} + +/** + * Authenticate at startup + * + * Recover saved tokens in localStorage and start token validation + */ +function authenticate(){ + // Try to authenticate with local token stored + if (typeof(Storage) !== "undefined") { + savedTokens = {} + savedTokensList = JSON.parse(localStorage.getItem("chained_ci_tokens")); + if(savedTokensList){ + savedTokensList.forEach(function(token){ + savedTokens[token.target] = token.value; + }) + privateTokens.forEach(function(token){ + if(token.target in savedTokens){ + token.value = savedTokens[token.target] + } + }); + validateTokens(privateTokens); + } + } else { + authVue.error = "No local storage, must authenticate again" + } +} + +/** + * Just load the page title from chained-ci project name + */ +function loadTitle(){ + gitlabCall(chainedCiProjectId, '', function(resp) { + headerVue.project = resp; + pipelinesVue.newPipelineUrl = resp.web_url + '/pipelines/new'; + }); +} + +/** + * Load pipelines of the project + * + * Call gitlab API to get the pipelines and prepare them + * - set global pipelines info + * - load pipeline jobs + * - for each job: + * - get the scenario names + * - clean jobs names + * - set if a job is a sub pipeline + * + * @params {string} page the page of the api to call (to load them by smal bulks) + */ +function loadPipelines(page = 1, size = pipelines_size){ + gitlabCall(chainedCiProjectId, 'pipelines?page='+page+'&per_page='+size, function(resp) { + console.log('load page '+page+' with size '+ size) + previous_pipelinesIds = Object.keys(pipelinesVue.pipelines); + previous_sorted_pipelinesIds = pipelinesVue.sortedPipelinesIds; + pipelines = resp; + res = {} + // Add more info to pipelines + pipelines.forEach(function(pipeline) { + console.log(pipeline) + load_it = false + if (previous_pipelinesIds.indexOf(pipeline.id.toString()) < 0 ){ + console.log("new pipeline " + pipeline.id); + load_it = true; + }else if (previous_sorted_pipelinesIds.indexOf(pipeline.id.toString()) >= 0 ){ + console.log("sorted pipeline " + pipeline.id); + load_it = true; + }else{ + console.log("filtered existing pipeline " + pipeline.id + ", pass"); + } + if(load_it){ + res[pipeline.id] = {}; + res[pipeline.id].id = pipeline.id; + res[pipeline.id].status = pipeline.status; + res[pipeline.id].statusIcon = getIcon(pipeline.status); + res[pipeline.id].scenario = ''; + res[pipeline.id].branch = pipeline.ref; + res[pipeline.id].details = {}; + res[pipeline.id].stages = []; + res[pipeline.id].url = pipeline.web_url; + // Add details + gitlabCall(chainedCiProjectId, 'pipelines/'+pipeline.id, function(resp) { + res[pipeline.id].details = resp; + dt = resp.started_at.split('T'); + res[pipeline.id].date = dt[0]; + res[pipeline.id].time = dt[1].split('.')[0]; + res[pipeline.id].user = resp.user.name; + res[pipeline.id].userAvatar = resp.user.avatar_url; + }); + // Add jobs + gitlabCall(chainedCiProjectId, 'pipelines/'+pipeline.id+'/jobs', function(resp) { + jobs = resp; + // get scenario name + names = [] + jobs.forEach(function(job){ + if (job.name in configCi){ + if ('variables' in configCi[job.name]){ + if ('pod' in configCi[job.name].variables){ + name = configCi[job.name].variables.pod; + if(!names.includes(name)){names.push(name);} + } + } + } + }); + if(names.length){ + res[pipeline.id].scenario = names.join(' + ') + }else{ + res[pipeline.id].scenario = 'Internal' + } + + // test if it trig another pipeline + jobs.forEach(function(job){ + if (job.name in configCi){ + job.internal = (configCi[job.name].script[0].search('run-ci') < 0) + }else{ + job.internal = true; + } + // clean jobs names and remove the scenario name in it + if(job.name.search(res[pipeline.id].scenario)>=0){ + job.shortname = job.name.split(':').slice(0,-1).join(':') + }else{ + job.shortname = job.name + } + }); + + res[pipeline.id].stages = jobsByStages(jobs); + }); + }else{ + console.log("push previous values") + res[pipeline.id] = pipelinesVue.pipelines[pipeline.id] + } + }); + pipelinesVue.pipelines = Object.assign({}, pipelinesVue.pipelines, res) + pipelinesVue.pipelinesIds = Object.keys(pipelinesVue.pipelines); + pipelinesVue.loading = false; + }); +} + + +/** + * Update pipeline + * + * This function is trigged by a setInterval() and refresh all pipelines + * + */ +function updatePipelines(){ + // Update subpipline + if(taskDetailsVue.pipeline.status == 'running' ){ + console.log('update task') + loadSubPipeline(taskDetailsVue.pipeline.parentTaskId, + taskDetailsVue.pipeline.parentTaskName) + } + // Update all piplines + loadPipelines(0, pipelinesVue.pages * pipelines_size) +} + + +/** + * Run an action on a pipeline job + * + * Call gitlab API to get run an action on a pipeline job + * - set global pipelines info + * - load pipeline jobs + * - for each job: + * - get the scenario names + * - clean jobs names + * - set if a job is a sub pipeline + * + * @params {string} action the action to run, in ['cancel', 'retry'] + * @params {int} jobId the job ID + */ +function jobAction(action, jobId){ + gitlabCall( + chainedCiProjectId, + 'jobs/'+jobId+'/'+action, + function(resp) { + alertVue.showModal = true; + alertVue.title = 'Action '+ action +' on job '+ jobId; + alertVue.message = 'Status: ' + resp.status; + console.log(resp) + + }, + gitlabApi, + 'POST' + ) +} + +/** + * Load a sub pipeline + * + * Load the a pipeline trigged by chained ci + * - Call the job logs and recover the subpipeline url + * - Load the pipeline info + * - Load the pipeline jobs + * + * @params {string} jobId The job ID inside a chained ci pipeline + * @params {string} jobName The job Name inside a chained ci pipeline + */ +function loadSubPipeline(jobId, originalJobName){ + + // Get project URL from static config + pod = configCi[originalJobName].variables.pod; + jobName = originalJobName.split(":")[0] + // Load the config of this scenario + getJson(scenarioFolder+'/host_vars/'+pod+'.json', function(scenario) { + project = scenario.scenario_steps[jobName].project; + // Load top config + getJson(scenarioFolder+'/group_vars/all.json', function(all) { + subprojectApi = all.gitlab.git_projects[project].api; + subprojectUrl = all.gitlab.git_projects[project].url; + // console.log(project_url); + + // Load the job log and search for the pipeline string + var request = new XMLHttpRequest(); + requestURL = gitlabApi+chainedCiProjectId+'/jobs/'+jobId+'/trace'; + request.open('GET', requestURL); + request.setRequestHeader('PRIVATE-TOKEN', getToken(gitlabApi) ); + request.send(); + request.onload = function() { + log = request.response; + regex = '\\* ' + subprojectUrl +'/pipelines/\\d+'; + regex = regex.replace(/\//g,'\\/'); + regex = regex.replace(/\:/g,'\\:'); + regex = regex.replace(/\./g,'\\.'); + regex = regex.replace(/\-/g,'\\-'); + filter = new RegExp(regex, 'm'); + m = log.match(filter); + if (m){ + subpipelineId = m[0].split('/').slice(-1)[0]; + // List subpipeline jobs + gitlabCall( + '', + 'pipelines/'+ subpipelineId, + function(pipeline) { + taskDetailsVue.pipeline.name = project; + taskDetailsVue.pipeline.id = subpipelineId; + taskDetailsVue.pipeline.parentTaskId = jobId; + taskDetailsVue.pipeline.parentTaskName = originalJobName; + taskDetailsVue.pipeline.chainedCiFailure = false; + taskDetailsVue.pipeline.status = pipeline.status; + taskDetailsVue.pipeline.statusIcon = getIcon(pipeline.status); + taskDetailsVue.pipeline.url = subprojectUrl+'/pipelines/'+subpipelineId; + taskDetailsVue.pipeline.console = chainedCiUrl+'/-/jobs/'+jobId; + }, + subprojectApi); + gitlabCall( + '', + 'pipelines/'+ subpipelineId +'/jobs', + function(jobs) { + jobs.forEach(function(job){ + if(job.name.search('triggered')>=0){ + job.name = job.name.split(':').slice(0,-1).join(':') + } + }); + stages = jobsByStages(jobs); + // console.log(stages); + taskDetailsVue.pipeline.stages = stages; + taskDetailsVue.showWaiting = false; + taskDetailsVue.showPipeline = true; + taskDetailsVue.chainedCiFailure = false; + }, + subprojectApi); + }else{ + taskDetailsVue.showWaiting = false; + taskDetailsVue.showPipeline = false; + taskDetailsVue.chainedCiFailure = true; + taskDetailsVue.pipeline.name = project; + taskDetailsVue.pipeline.status = 'failed'; + taskDetailsVue.pipeline.statusIcon = getIcon('failed'); + taskDetailsVue.pipeline.url = chainedCiUrl+'/-/jobs/'+jobId; + taskDetailsVue.pipeline.console = chainedCiUrl+'/-/jobs/'+jobId; + } + } + }); + }); +} + +/** + * Change icon on mouse over + * + * @params {object} target The target object + */ +function iconMouseOver(target){ + switch(target.status){ + case 'failed': + case 'success': + target.statusIcon = getIcon('retry'); + break; + case 'running': + target.statusIcon = getIcon('stop'); + break; + } +} + +/** + * Change icon on mouse leave + * + * @params {object} target The target object + */ +function iconMouseLeave(target){ + target.statusIcon = getIcon(target.status); +} + + +/** + * Action depending on job status + * + * @params {string} status The job status + * @params {int} target The job id + */ +function jobActionSwitch(status, jobId){ + switch(status){ + case 'failed': + case 'success': + jobAction('retry', jobId) + break; + case 'running': + jobAction('cancel', jobId) + break; + } +} diff --git a/chained-ci-vue/js/visibility.LICENSE b/chained-ci-vue/js/visibility.LICENSE new file mode 100644 index 0000000..9ef56c8 --- /dev/null +++ b/chained-ci-vue/js/visibility.LICENSE @@ -0,0 +1,3 @@ +source: https://github.com/ai/visibilityjs +LICENSE: MIT - https://github.com/ai/visibilityjs/blob/master/LICENSE +author: Andrey Sitnik - https://github.com/ai diff --git a/chained-ci-vue/js/visibility.core.js b/chained-ci-vue/js/visibility.core.js new file mode 100644 index 0000000..6dda095 --- /dev/null +++ b/chained-ci-vue/js/visibility.core.js @@ -0,0 +1,189 @@ +;(function (global) { + var lastId = -1; + + // Visibility.js allow you to know, that your web page is in the background + // tab and thus not visible to the user. This library is wrap under + // Page Visibility API. It fix problems with different vendor prefixes and + // add high-level useful functions. + var self = { + + // Call callback only when page become to visible for user or + // call it now if page is visible now or Page Visibility API + // doesn’t supported. + // + // Return false if API isn’t supported, true if page is already visible + // or listener ID (you can use it in `unbind` method) if page isn’t + // visible now. + // + // Visibility.onVisible(function () { + // startIntroAnimation(); + // }); + onVisible: function (callback) { + var support = self.isSupported(); + if ( !support || !self.hidden() ) { + callback(); + return support; + } + + var listener = self.change(function (e, state) { + if ( !self.hidden() ) { + self.unbind(listener); + callback(); + } + }); + return listener; + }, + + // Call callback when visibility will be changed. First argument for + // callback will be original event object, second will be visibility + // state name. + // + // Return listener ID to unbind listener by `unbind` method. + // + // If Page Visibility API doesn’t supported method will be return false + // and callback never will be called. + // + // Visibility.change(function(e, state) { + // Statistics.visibilityChange(state); + // }); + // + // It is just proxy to `visibilitychange` event, but use vendor prefix. + change: function (callback) { + if ( !self.isSupported() ) { + return false; + } + lastId += 1; + var number = lastId; + self._callbacks[number] = callback; + self._listen(); + return number; + }, + + // Remove `change` listener by it ID. + // + // var id = Visibility.change(function(e, state) { + // firstChangeCallback(); + // Visibility.unbind(id); + // }); + unbind: function (id) { + delete self._callbacks[id]; + }, + + // Call `callback` in any state, expect “prerender”. If current state + // is “prerender” it will wait until state will be changed. + // If Page Visibility API doesn’t supported, it will call `callback` + // immediately. + // + // Return false if API isn’t supported, true if page is already after + // prerendering or listener ID (you can use it in `unbind` method) + // if page is prerended now. + // + // Visibility.afterPrerendering(function () { + // Statistics.countVisitor(); + // }); + afterPrerendering: function (callback) { + var support = self.isSupported(); + var prerender = 'prerender'; + + if ( !support || prerender != self.state() ) { + callback(); + return support; + } + + var listener = self.change(function (e, state) { + if ( prerender != state ) { + self.unbind(listener); + callback(); + } + }); + return listener; + }, + + // Return true if page now isn’t visible to user. + // + // if ( !Visibility.hidden() ) { + // VideoPlayer.play(); + // } + // + // It is just proxy to `document.hidden`, but use vendor prefix. + hidden: function () { + return !!(self._doc.hidden || self._doc.webkitHidden); + }, + + // Return visibility state: 'visible', 'hidden' or 'prerender'. + // + // if ( 'prerender' == Visibility.state() ) { + // Statistics.pageIsPrerendering(); + // } + // + // Don’t use `Visibility.state()` to detect, is page visible, because + // visibility states can extend in next API versions. + // Use more simpler and general `Visibility.hidden()` for this cases. + // + // It is just proxy to `document.visibilityState`, but use + // vendor prefix. + state: function () { + return self._doc.visibilityState || + self._doc.webkitVisibilityState || + 'visible'; + }, + + // Return true if browser support Page Visibility API. + // refs: https://developer.mozilla.org/en-US/docs/Web/API/Page_Visibility_API + // + // if ( Visibility.isSupported() ) { + // Statistics.startTrackingVisibility(); + // Visibility.change(function(e, state)) { + // Statistics.trackVisibility(state); + // }); + // } + isSupported: function () { + return self._doc.hidden !== undefined || self._doc.webkitHidden !== undefined; + }, + + // Link to document object to change it in tests. + _doc: document || {}, + + // Callbacks from `change` method, that wait visibility changes. + _callbacks: { }, + + // Listener for `visibilitychange` event. + _change: function(event) { + var state = self.state(); + + for ( var i in self._callbacks ) { + self._callbacks[i].call(self._doc, event, state); + } + }, + + // Set listener for `visibilitychange` event. + _listen: function () { + if ( self._init ) { + return; + } + + var event = 'visibilitychange'; + if ( self._doc.webkitVisibilityState ) { + event = 'webkit' + event; + } + + var listener = function () { + self._change.apply(self, arguments); + }; + if ( self._doc.addEventListener ) { + self._doc.addEventListener(event, listener); + } else { + self._doc.attachEvent(event, listener); + } + self._init = true; + } + + }; + + if ( typeof(module) != 'undefined' && module.exports ) { + module.exports = self; + } else { + global.Visibility = self; + } + +})(this); diff --git a/chained-ci-vue/js/visibility.timers.js b/chained-ci-vue/js/visibility.timers.js new file mode 100644 index 0000000..546c24e --- /dev/null +++ b/chained-ci-vue/js/visibility.timers.js @@ -0,0 +1,161 @@ +;(function (window) { + var lastTimer = -1; + + var install = function (Visibility) { + + // Run callback every `interval` milliseconds if page is visible and + // every `hiddenInterval` milliseconds if page is hidden. + // + // Visibility.every(60 * 1000, 5 * 60 * 1000, function () { + // checkNewMails(); + // }); + // + // You can skip `hiddenInterval` and callback will be called only if + // page is visible. + // + // Visibility.every(1000, function () { + // updateCountdown(); + // }); + // + // It is analog of `setInterval(callback, interval)` but use visibility + // state. + // + // It return timer ID, that you can use in `Visibility.stop(id)` to stop + // timer (`clearInterval` analog). + // Warning: timer ID is different from interval ID from `setInterval`, + // so don’t use it in `clearInterval`. + // + // On change state from hidden to visible timers will be execute. + Visibility.every = function (interval, hiddenInterval, callback) { + Visibility._time(); + + if ( !callback ) { + callback = hiddenInterval; + hiddenInterval = null; + } + + lastTimer += 1; + var number = lastTimer; + + Visibility._timers[number] = { + visible: interval, + hidden: hiddenInterval, + callback: callback + }; + Visibility._run(number, false); + + if ( Visibility.isSupported() ) { + Visibility._listen(); + } + return number; + }; + + // Stop timer from `every` method by it ID (`every` method return it). + // + // slideshow = Visibility.every(5 * 1000, function () { + // changeSlide(); + // }); + // $('.stopSlideshow').click(function () { + // Visibility.stop(slideshow); + // }); + Visibility.stop = function(id) { + if ( !Visibility._timers[id] ) { + return false; + } + Visibility._stop(id); + delete Visibility._timers[id]; + return true; + }; + + // Callbacks and intervals added by `every` method. + Visibility._timers = { }; + + // Initialize variables on page loading. + Visibility._time = function () { + if ( Visibility._timed ) { + return; + } + Visibility._timed = true; + Visibility._wasHidden = Visibility.hidden(); + + Visibility.change(function () { + Visibility._stopRun(); + Visibility._wasHidden = Visibility.hidden(); + }); + }; + + // Try to run timer from every method by it’s ID. It will be use + // `interval` or `hiddenInterval` depending on visibility state. + // If page is hidden and `hiddenInterval` is null, + // it will not run timer. + // + // Argument `runNow` say, that timers must be execute now too. + Visibility._run = function (id, runNow) { + var interval, + timer = Visibility._timers[id]; + + if ( Visibility.hidden() ) { + if ( null === timer.hidden ) { + return; + } + interval = timer.hidden; + } else { + interval = timer.visible; + } + + var runner = function () { + timer.last = new Date(); + timer.callback.call(window); + } + + if ( runNow ) { + var now = new Date(); + var last = now - timer.last ; + + if ( interval > last ) { + timer.delay = setTimeout(function () { + timer.id = setInterval(runner, interval); + runner(); + }, interval - last); + } else { + timer.id = setInterval(runner, interval); + runner(); + } + + } else { + timer.id = setInterval(runner, interval); + } + }; + + // Stop timer from `every` method by it’s ID. + Visibility._stop = function (id) { + var timer = Visibility._timers[id]; + clearInterval(timer.id); + clearTimeout(timer.delay); + delete timer.id; + delete timer.delay; + }; + + // Listener for `visibilitychange` event. + Visibility._stopRun = function (event) { + var isHidden = Visibility.hidden(), + wasHidden = Visibility._wasHidden; + + if ( (isHidden && !wasHidden) || (!isHidden && wasHidden) ) { + for ( var i in Visibility._timers ) { + Visibility._stop(i); + Visibility._run(i, !isHidden); + } + } + }; + + return Visibility; + } + + if ( typeof(module) != 'undefined' && module.exports ) { + module.exports = install(require('./visibility.core')); + } else { + install(window.Visibility || require('./visibility.core')) + } + +})(window); diff --git a/chained-ci-vue/logo.svg b/chained-ci-vue/logo.svg new file mode 100644 index 0000000..1eb9231 --- /dev/null +++ b/chained-ci-vue/logo.svg @@ -0,0 +1,130 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/chained-ci-vue/style.css b/chained-ci-vue/style.css new file mode 100644 index 0000000..d2c7f8b --- /dev/null +++ b/chained-ci-vue/style.css @@ -0,0 +1,219 @@ +a { + text-decoration: none; +} + +html, body { + margin: 5px; + padding: 0; +} + +.main{ + margin: auto; + width: 70%; + min-width: 1200px; +} + +.tools { + margin: 10px auto; +} +.tools > div { + padding: 0.3em; + margin: 1em; +}.tools { + display: grid; + text-align: center; + grid-template-columns: repeat(8, 1fr ); + grid-gap: 5px; +} +.tool_sc { + grid-column: 1 / 3; +} +.tool_timer { + grid-column: 3 / 8; +} +.tool_new { + grid-column: 8; +} + + +.pipeline { + margin: 10px auto; +} +.pipeline > div { + padding: 1em; + margin: 1em; +}.pipeline { + display: grid; + grid-template-columns: repeat( 6, 1fr ); + grid-gap: 5px; +} + + +.pipeline_header { + grid-column: 1; + grid-row: 1; +}.pipeline_header { + display: grid; + grid-template-columns: repeat( 2, 1fr ); + grid-template-rows: repeat(5, 3fr); + grid-gap: 5px; +} + +.pipeline_user_icon { + grid-column: 1 ; + grid-row: 4 / 6; +} + +.pipeline_statusIcon { + grid-column: 2 ; + grid-row: 4 / 6; +} img{ + width: 36px; + height: 36px; + border-radius: 50%; +} + +.pipeline_scenario { + font-weight: bold; + grid-column: 1 / 3; + grid-row:1; +} +.pipeline_branch { + grid-column: 1; + grid-row:2; +} +.pipeline_date { + grid-column: 1; + grid-row:3; +} +.pipeline_time { + grid-column: 2; + grid-row:3; +} + +.pipeline_duration { + grid-column: 2; + grid-row: 2; +} + +.pipeline_stages { + grid-column: 2 / 7; + grid-row: 1; +} + +.pipeline_loader { + grid-column: 1 / 7; + grid-row: 1; +} + +.stage { +} +.stage > div { + padding: 1px; + width: 100%; +}.stage { + float:left; + display: table; +} + +.stage_name{ + display: inline-block; + text-align: center; + padding: 2px; +} + +.job{ + display: inline-block; +} > div { +}.job{ + display: grid; + grid-template-columns: repeat(6, 1fr); + grid-gap: 0px; +} + +.job_statusIcon{ + grid-column: 1; + grid-row: 1; +} + +.job_name{ + padding: 2px; + grid-column: 2 / 7; + grid-row: 1; +} + +.statusIcon{ + padding: 2px; +} + + + + + + + + + +.modal-mask { + position: fixed; + z-index: 9998; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, .5); + display: table; + transition: opacity .3s ease; +} + +.modal-wrapper { + display: table-cell; + vertical-align: middle; +} + +.modal-container { + width: 60%; + min-height: 600px;; + margin: 0px auto; + padding: 20px 30px; + background-color: #fff; + border-radius: 2px; + box-shadow: 0 2px 8px rgba(0, 0, 0, .33); + transition: all .3s ease; +} + +.modal-header h3 { + margin-top: 0; + color: #42b983; +} + +.modal-body { + margin: 20px 0; +} + +.modal-default-button { + float: right; +} + +/* + * The following styles are auto-applied to elements with + * transition="modal" when their visibility is toggled + * by Vue.js. + * + * You can easily play with the modal transition by editing + * these styles. + */ + +.modal-enter { + opacity: 0; +} + +.modal-leave-active { + opacity: 0; +} + +.modal-enter .modal-container, +.modal-leave-active .modal-container { + -webkit-transform: scale(1.1); + transform: scale(1.1); +} diff --git a/pod_config/config/artifacts/azure.zip b/pod_config/config/artifacts/azure.zip new file mode 100644 index 0000000..44f96a9 Binary files /dev/null and b/pod_config/config/artifacts/azure.zip differ diff --git a/pod_config/config/az14-gating3.yaml b/pod_config/config/az14-gating3.yaml new file mode 100644 index 0000000..9310f8c --- /dev/null +++ b/pod_config/config/az14-gating3.yaml @@ -0,0 +1,168 @@ +############################################################################## +# Copyright (c) 2017 Orange and others. +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## +--- +### vPOD descriptor file ### + +details: + pod_owner: OPNFV + contact: N/A + lab: OPNFV LaaS + location: N/A + type: production + link: http://wiki.opnfv.org/ +############################################################################## + +nodes: + - name: nfs01 + node: + type: virtual + vendor: azure + model: + offer: debian-10 + version: latest + publisher: Debian + sku: "10" + arch: x86_64 + cpus: 1 + cpu_cflags: host-model + cores: 1 + memory: 1G + flavor: Standard_B2s + disks: + - name: disk1 + disk_capacity: 27G + disk_type: qcow2 + disk_interface: ide + disk_rotation: + - name: disk-nfs + disk_capacity: 100G + disk_type: qcow2 + disk_interface: ide + disk_rotation: + remote_management: &remote_management + type: + - ipmi: NA + user: NA + pass: NA + address: NA + mac_address: NA + interfaces: + - mac_address: + name: nic1 + speed: + features: + network: internal + - name: devstack01 + node: + type: virtual + vendor: azure + model: + #offer: "0001-com-ubuntu-server-focal" + #sku: "20_04-lts-gen2" + offer: UbuntuServer + version: latest + publisher: Canonical + sku: "18_04-lts-gen2" + arch: x86_64 + cpus: 16 + cpu_cflags: host-model + cores: 16 + memory: 64G + flavor: Standard_D16s_v3 + disks: + - name: disk1 + disk_capacity: 32G + disk_type: qcow2 + disk_interface: ide + disk_rotation: + remote_management: *remote_management + interfaces: + - mac_address: + name: nic1 + speed: + features: + network: internal + - name: worker01 + node: &workerparams + type: virtual + vendor: aks + model: worker + arch: x86_64 + cpus: 4 + cpu_cflags: host-model + cores: 4 + memory: 16G + flavor: Standard_D4s_v3 + disks: &workerdisks + - name: disk1 + disk_capacity: 8G + disk_type: qcow2 + disk_interface: ide + disk_rotation: + remote_management: *remote_management + interfaces: &interfaces + - mac_address: + name: nic1 + speed: + features: + network: kubernetes + - name: worker02 + node: *workerparams + disks: *workerdisks + remote_management: *remote_management + interfaces: *interfaces + - name: worker03 + node: *workerparams + disks: *workerdisks + remote_management: *remote_management + interfaces: *interfaces + - name: worker04 + node: *workerparams + disks: *workerdisks + remote_management: *remote_management + interfaces: *interfaces + - name: worker05 + node: *workerparams + disks: *workerdisks + remote_management: *remote_management + interfaces: *interfaces + - name: worker06 + node: *workerparams + disks: *workerdisks + remote_management: *remote_management + interfaces: *interfaces + - name: worker07 + node: *workerparams + disks: *workerdisks + remote_management: *remote_management + interfaces: *interfaces + - name: worker08 + node: *workerparams + disks: *workerdisks + remote_management: *remote_management + interfaces: *interfaces + - name: worker09 + node: *workerparams + disks: *workerdisks + remote_management: *remote_management + interfaces: *interfaces + - name: worker10 + node: *workerparams + disks: *workerdisks + remote_management: *remote_management + interfaces: *interfaces + - name: worker11 + node: *workerparams + disks: *workerdisks + remote_management: *remote_management + interfaces: *interfaces + - name: worker12 + node: *workerparams + disks: *workerdisks + remote_management: *remote_management + interfaces: *interfaces diff --git a/pod_config/config/idf-az14-gating3.yaml b/pod_config/config/idf-az14-gating3.yaml new file mode 100644 index 0000000..e63289a --- /dev/null +++ b/pod_config/config/idf-az14-gating3.yaml @@ -0,0 +1,171 @@ +--- +idf: + net_config: &net_config + internal: + interface: 0 + network: 192.168.65.0 + mask: 24 + gateway: 192.168.65.1 + dns: 192.168.65.1 + +aks: + peering_network: internal + +az_infra: + jumphost_network: admin + +openstack: + release: stable/victoria + +os_infra: + net_config: *net_config + user: + name: cloud + is_admin: false + tenant: + name: Gating + images_to_push: + image_default: debian-10 + image2user_mapping: + debian-10: debian + nodes_roles: + nfs01: [kube-master, nfs-server] + devstack01: [devstack] + roles_group: + k8s-cluster: + - kube-master + - kube-node + k8s-full-cluster: + - k8s-cluster + - jumphost + - devstack + + onap: + global_storage: + enabled: true + rwx_class: nfs + class: default + + dns: + update: true + provider: gandiv5 + zone: onap.eu + name: azure3 + master: nfs01 + + kubernetes: + dns_prefix: onap-gating + name: cluster + kubernetes_version: 1.23.15 + helm_version: v3.8.2 + worker_role: kube-node + helm: + repositories: + - name: jetstack + url: https://charts.jetstack.io + - name: minio + url: https://helm.min.io/ + charts: + cert-manager: + chart: jetstack/cert-manager + namespace: cert-manager + istioEnabled: true + content: | + --- + installCRDs: true + prometheus: + servicemonitor: + enabled: true + minio: + chart: minio/minio + namespace: minio + content: | + --- + accessKey: "{{ lookup('env','S3_ACCESS_KEY') }}" + secretKey: "{{ lookup('env','S3_SECRET_KEY') }}" + replicas: 1 + persistence: + size: 100Gi + service: + type: LoadBalancer + nodePort: 30345 + metrics: + serviceMonitor: + enabled: true + nfs-server-provisioner: + chart: stable/nfs-server-provisioner + namespace: nfs-server + enabled: true + + ssh_pub_key_default: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCqbo9WQwjZp6Op6a\ + wsgRaBdR+gJhbmMHOm+2Ol7TJlXe2wV5r2vFQhbmd0GHTdz4g8MH+y7oO5637RoAMZlY4ieZdS\ + VGZZ5HvIoMqoS7FVT6u1kvVTVBmlpEgF4gXLsJi3s4bGlXqVnYA42SoKX65DhvHgLIScL1E6uS\ + 63MLRP+clFchNASyhz8CgRPdazE7vy4LJJNE8C6hj0MVSOVasJKMZQAmqjSFkuMy0ECNDj5sQm\ + oPYozpvM9xsP/UWiW8MCWuTh33JRBPH/06OIUMVN0dD+xhfVgYY+aueFRjs4oaMm2kfgiTJsQw\ + uAziLrvnusSse+idq9t49YeiAPGpuZ sylvain@MacBook-Pro-de-Sylvain.local + + ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDBlukmibuf0LMrK5NmZkxIziYrOovBzYG8JmQ\ + KONN3KtRZBy9ooL5JBFfmdWMvdcWoLee23R024DwWGF1ooR9SyolXQ+K7lbsSzw0NR/+feRs3of\ + asxj0bXJsWjC48C7eD8v48Y2PMs5uP5Yp/RMOThHVN7w2gbm2OZGG5MIV1ixML/coqo39clm8dC\ + pFhSZmPfmVFliOBW8dFk+DSoKClVy7FH56qEzKoenLJy/+j0B10/5t6T7Sk1QUWDOfIikBomGa2\ + gZAozgGFnuoK9b7FBwlyZiYbbbL4EShiqkEaY938zYcXr1/bqcZWj/F30hK5ddo6f1IDwr57U6l\ + 1W5Vd thierry.hardy@orange.com + + ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDB22+oh+TaQs5XbJtDY3elDWHnTlPCOwqr0kh\ + wYGn/teOr9hPDtqADPjaymoowNDbFIB9CZleZuLFAIryCplb563EwHoaGllRaBom/Alzj3QRIhx\ + FjhWCBzes+ix6NRqKQZ7wPGB7u5QuQCvqlMgLKDNz2UmPoIoYLrhrFyUE5cxWtZ2vmSMTkOpLwO\ + Aw+9UWMww6jzCLNcQjl25zyfSLXVCLLbuyBUza0SvcyxH4glbjTx3BjdCp6ovDDhO2z9tvofvzF\ + j5ceGlohD40ETiROT9C7hKj7Bn2MPDCKPEEjBVjkh9YkpUD9W6uW/t1loGoajLZeJEbN946L3Lt\ + Yi0wz morgan.richomme@orange.com + + ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDRslHbwKe4AzMa9dRXnpEDjrbqMvPuGxG2zzZ\ + KbKWsqtzUhgIAMhUL6vw4ePI34bN6uqq8qsQDDu5JYJ3sO1WwpFhX0ZEvWohQK55oe94kfXfnbu\ + sOwRfd8uiWh2s8lfCL7BlbruKGIJcueX+bMLJfUPo3vo5Ae3bBn+J4sG1tLLpf9UxvuXmnZiRyv\ + QJjCy2f6BOeS2AzRhezkC23IS2gylzG3I3MT1acw3i9wAF2yZ2ca++6MdLb6e6Bu30Y9rO7PAj8\ + 76myts4GaBhbXmi1+rjb0HbUm3V9wxG7drKv7C0y0nftBS0Q9yCKqld96WQgseYL3CcwEe4qVqn\ + a7DxWQCJROJRi9/DcSSoL73jPcfSWyRqeutWp55S/ajvseAG2yZExbMiGOmW5qsyAGMigjGik59\ + H7gSJR0RitQtGpoqBzsuFaGgYnpfUkAgnjdqqlLT7iRO6OZAXVVSZODbb6LE+2av4B0Yp7SHx8U\ + Rps4CGBS8HyGHQNelnOmra8SnVmXtOhYU87kdOoPaTU7GkuYiy8ctewSoHdgX40tRbB45AMlc+L\ + avf+4hx/5K/u50gei19tUM9aKf6TmdH8LFOPtwUtMd/4nNVYmuwdEFqTVTkVPYdpIF75N5NDF7d\ + uDRyYakTPG/rvj9l0zT5V7sfc3QA0TVNdHu6NLPHHqvkjsQ== k.opasiak@samsung.com + + ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDHfYKtwwhaiw+uq8G4G6TVFRsvq9kbaN+vZ/H\ + cToLN1LfIJVKFCGykbnrm+f4LrGM6CTvCxt6YU2wpszHoCIVQc+9FTe58O/fuSrrSPVS3JuwIqO\ + /QT4pTnxGrP/FrEEkefoEUD1kLFxYLKy0z4+d0uLee1aBRlSwbk+dWkVoedkoXQmUfTugsORiuk\ + beuFEJR77MQDAGYC0AVbl7Eb0Fv/DNNV/SLNWHh0OP5ilLeSNpqC5s51u7iALhbOzI1kRHZrc/8\ + SExyXjeBNy01l4cNfOse3/3AWo8gOVqI49b5UggQPh1fuBQ7QZkzbRp9/U7+t4v6b+JJg6eBeoj\ + A/yuL0uMrKuX4hrT5euTOBup4GyTv8GHS8EN4jRWbWMz6hQ1FYwssnAIpVWXyl/4xD2jJ0Av9mG\ + /W0htB/wnNeOss+RokiWA5HQ+Dupbcp2GucMhs2atRPD0V30HbvcUMxPE/BC15R1uqQapV5hkz1\ + WrP5KQTHdYmDRvKU5ZxBNBAkR8= j.latusek@samsung.com + + ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCSNiMbJEDhn32OXdWxRwftB8LRvF9X1npSlfB\ + 2M3ezxKqzm3bx4yhBMm7sCBhS6f1dQuIiLz4BE6h1P+AdXfg+zkqkV44lhGLGAu7L2ysRRCUGoU\ + +h4LwRVBuKyd5WL3u3jzwBK/tp1uHNqSVEoRPFgfkBNZwALoclymw/2W7gfQ2OAVeu6pFycLF9e\ + oUSkAWQxlZcYaVXSIL7EhSVs7pP41BzQ3DIHv1fN/bVzbss8bePUa5j8gHJiSrKfU/gslnCN8m7\ + yJuj1IpmfK/XJl+JAU9Y0+GSqvN6mitIe6m+qhkCK75A6+pXdycbZlYT/fUlFBv1rZCuzapb3NC\ + b5V8gO7B1Rx3W2lu50m5lmRjLjRichyRsZCtWQNAOUi7I7BJcq8voEqb5sjuIuZThezMT5TW+d4\ + 1+JlbD2bQjAiFz5TEtnrHDXERW5PmsVqlqhQnMc/lSrLmHQaQPFTZtyI0jfB5Jvehp0y7PdC5Li\ + rz8wSuWB2cXLToxhGqxz3xgZ4s= samuel.liard@orange.com + + ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCx/xA4zpJOXj03X3H8jNAZynKGOJvOkqnoRwq\ + 1gEw46WDnZoElAkTojjCZSqBXYsmK48uLNMsys0FufL4XPjtYEvjrApooQBXsH+JdGKjm9M16pv\ + MSPjcxov0IQ+GuTrBFEbg6ismmLNgAGdXBvJa4q+Ne0yaPEh3WbffbPEShIUj6wiFFI7pdd4je2\ + Dka2kPrFBQUsJe6qUrQ3nbpXpNg3XGnKm3fqNfKWSw5Lc5UfvKxgLxU+9ur46O63LwgAyako2FY\ + J5dC6RkAe91fpqUNcM4JhjuqmeTrqpi8QEjX4t22Zr6W26Ueepd7uuKyiRnXYd27uwjvHCgC41V\ + 0BGiB krzysztof.kuzmicki@nokia.com + + ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCwJS4K/mt2MfH2VOlOWwEqFW4T/oTIzM8lcz8\ + e3QeKMBJJa5jWiviEruXhbc3kG4nHuq1xkaQLxqbOUN4TF1GH4AHkCl1mLtb9TnBWtihV4A9zQD\ + bjluNmsT0lotOOK3Qfqee35BXkoDX4IXjjqjHXmUM2n4DDajJUd38H7Owd28nlpDhzXpFhfDzM5\ + 8wO586frKs0UGYM6gc8JyaLRPACum0wFz9HzXb4NShDQh+smW+v6fIeKe9d4HC8t/b8XZFChhGe\ + pLN/i0EGLZqwX5uHrGoq3/6i7gAmHBmNs1wR8qX5nVl28VKdz4Dbum9hJjoxSNC54K8Yyh5JbCB\ + G7ft/eXLGY+Dm5moPaoPBkpOHcIk5aP/MVNgOBmAETxnujlaXYmrorrH0aTB9UGE0zHezzXfq0a\ + VjuSsFFmXDFOoKJLyc/C8WnMDdB+GD3UBnCdhlKPjbod8OAPjq1OdFpLgR0APTb3MB/v1Nfdhf3\ + hqYatkG0P9TCVvjfLqzeHqHhfs= ahmad@Ahmads-MBP + + ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDA/6kqp3MNvYCajLYIunRG4w8aZFbfNZqFmhO\ + nvR/8h0vrTJAxSVPMZVETlEkIdZMB8wKHnvj75TepQVxP2dXtqlgrmVnCOrKDYNgCQsMoDr/EaY\ + Xbv+ph/asWWFuIQ9wPVZG3+Qlf+Y5ne88WVnjlLOvqoB7UJYEqNnhqDR4OVgBVhnROlcnPZE4mh\ + 3TOxFXMDNctOQUr+4h4Cp7hBzXW7SbbpDMiQizIGSEFrzA/L0/peSK1f105KDdenWSfSxsbqABu\ + KL+2/68BheRqcXxhiXf3AUGMy2awhLRLPCmTbE10J2Ky6z1bNgB0Wnqb0bwWLerow0B3Urb+c/0\ + Lv9EZ Michal.Jagiello@t-mobile.pl" diff --git a/pod_inventory/group_vars/all.yml b/pod_inventory/group_vars/all.yml index f31df15..7257da5 100644 --- a/pod_inventory/group_vars/all.yml +++ b/pod_inventory/group_vars/all.yml @@ -14,7 +14,7 @@ runner: tags: - "$RUNNER_TAG" env_vars: - CHAINED_CI_SRC: https://gitlab.devops.telekom.de/tnap/onapcommunity/integrationproject/onapdeployment/chained-ci.git #TODO: Chained CI on onap gitlab + CHAINED_CI_SRC: https://gitlab.com/onap/integration/pipelines/chained-ci.git #TODO: Chained CI on onap gitlab docker_proxy: image: registry.gitlab.com/orange-opensource/lfn/ci_cd/docker_ansible image_tag: 2.7.10-alpine @@ -22,31 +22,39 @@ runner: gitlab: pipeline: delay: 15 - base_url: https://gitlab.devops.telekom.de - api_url: https://gitlab.devops.telekom.de/api/v4 + base_url: https://gitlab.com + api_url: https://gitlab.com/api/v4 private_token: "{{ lookup('env','CI_private_token') }}" git_projects: config: stage: config - url: https://gitlab.devops.telekom.de/tnap/onapcommunity/integrationproject/onapdeployment/chained-ci.git #TODO Chained CI on onap gitlab - api: https://gitlab.devops.telekom.de/api/v4/projects/36215 + url: https://gitlab.com/onap/integration/pipelines/chained-ci.git #TODO Chained CI on onap gitlab + api: https://gitlab.com/api/v4/projects/39992873 branch: "{{ lookup('env','config_branch')|default('master', true) }}" path: pod_config - trigger: - stage: apps + build_integration: + stage: infra_install + api: https://gitlab.com/api/v4/projects/24365265 + url: https://gitlab.com/Orange-OpenSource/lfn/onap/build-integration trigger_token: !vault | $ANSIBLE_VAULT;1.1;AES256 - 64386138616464653132353964363032346464373363323366616436346263323230353961363263 - 3562653664303631323134313864393364636538643430640a363766316230633932376466643333 - 64386331633737623164313831633537666638623534663736313331313266396438306266636632 - 3532313263396532300a306661393438613734323064313064343361363763636664393231363934 - 37633335396563623462653935393236356139303864646135303935373937623739 - branch: "{{ lookup('env','CI_BUILD_REF_NAME')|default('master', true) }}" + 61313463643234303366353965653038363162386565613266326237373634326465 + 3365323331306531363834326264613736393836633362323635323365300a333963 + 30346666663636653238306265393833663463393538613466633831383234336332 + 3239613634383063386635653836626634633136623831396362640a363332666465 + 66396131663861326163666536346336356430303933363035373830363162373036 + 323433383436616461373231386464666232353932383162 + branch: "{{ lookup('env','build_integration_branch')|default('master', true) }}" + pull_artifacts: + timeout: 1400 + get_encrypt: true + get_bin: true parameters: - ansible_verbose: "{{ lookup('env','ansible_verbose') }}" - RUNNER_TAG: "{{ lookup('env','RUNNER_TAG') }}" + GERRIT_REVIEW: "{{ lookup('env','GERRIT_REVIEW') }}" + GERRIT_PATCHSET: "{{ lookup('env','GERRIT_PATCHSET') }}" + PROJECT: "{{ lookup('env','PROJECT') }}" cloud-infra: stage: infra_install @@ -69,53 +77,6 @@ gitlab: RUNNER_TAG: "{{ lookup('env','RUNNER_TAG') }}" USER_ROLE: "{{ lookup('env','USER_ROLE') }}" - # rke-install: - # stage: virt_install - # api: https://gitlab.devops.telekom.de/api/v4/projects/36232 - # url: https://gitlab.devops.telekom.de/tnap/onapcommunity/integrationproject/onapdeployment/rke-install.git - # trigger_token: !vault | - # $ANSIBLE_VAULT;1.1;AES256 - # 31366336336230663530613735643963626665633538643838353762386364363632393039623165 - # 3939326531333765393964373431633961656663303933340a393934326464646436313839666662 - # 33313662333662386362316666316232623364346134646165326562303439373861616162653938 - # 6130363366366634320a363231326234346530333665353134616435643136353638613332313061 - # 34353035336437306130396366343566376362366630613233613464663962626539 - # branch: "{{ lookup('env','vim_branch')|default('master', true) }}" - # get_artifacts: - # pull_artifacts: "postconfigure" - # timeout: 900 - # parameters: - # ANSIBLE_VERBOSE: "{{ lookup('env','ansible_verbose') }}" - # docker_version: "{{ lookup('env','docker_version') }}" - # kubernetes_release: "{{ lookup('env','kubernetes_release') }}" - # kubespray_version: "{{ lookup('env','kubespray_version') }}" - # helm_version: "{{ lookup('env','helm_version') }}" - # RUNNER_TAG: "{{ lookup('env','RUNNER_TAG') }}" - - kubespray-install: - stage: virt_install - api: https://gitlab.devops.telekom.de/api/v4/projects/36231 #TODO k8s install on onap gitlab - url: https://gitlab.devops.telekom.de/tnap/onapcommunity/integrationproject/onapdeployment/k8s-install.git #TODO k8s install on onap gitlab - trigger_token: !vault | - $ANSIBLE_VAULT;1.1;AES256 - 64376532616532636231396662336134396561643133323432393330623132353063643635336665 - 6232643832343133366333656438616463616336613331320a313166366234356537383639303133 - 64353338653639623034313735653561356362366236636363376431336264653332356134616335 - 3539626665613336350a313035636665383939656461306664386135623139346531343935373763 - 34323539663261363634373031383838376362623135386565643465376661616534 - branch: "{{ lookup('env','vim_branch')|default('master', true) }}" - get_artifacts: - pull_artifacts: "postconfigure" - timeout: 600 - get_bin: true - parameters: - ANSIBLE_VERBOSE: "{{ lookup('env','ansible_verbose') }}" - docker_version: "{{ lookup('env','docker_version') }}" - kubernetes_release: "{{ lookup('env','kubernetes_release') }}" - kubespray_version: "{{ lookup('env','kubespray_version') }}" - helm_version: "{{ lookup('env','helm_version') }}" - RUNNER_TAG: "{{ lookup('env','RUNNER_TAG') }}" - onap-install: stage: apps api: https://gitlab.devops.telekom.de/api/v4/projects/36235 #TODO onap oom install on onap gitlab @@ -171,23 +132,96 @@ gitlab: PROJECT: "{{ lookup('env','PROJECT') }}" DEBUG: true RUNNER_TAG: "{{ lookup('env','RUNNER_TAG') }}" - #EXT_NET: "admin-daily-{{ lookup('env','onap-testing_branch')|default('master', true) }}" - # onap-operate: - # stage: check - # api: https://gitlab.devops.telekom.de/api/v4/projects/36337 - # url: https://gitlab.devops.telekom.de/tnap/onapcommunity/labtools/tenantaccess.git - # trigger_token: !vault | - # $ANSIBLE_VAULT;1.1;AES256 - # 31646539393535313462666661336239336234333436376438333165383264613535323534373665 - # 3737313238313139613564326639393239333839636531350a323735343565656665386666346237 - # 39663539393636653739343762613233363862393630336135656633333565393535366561613735 - # 3934393735383266650a326530636434633163363631316634323966383662623664316331343465 - # 32636161376133393765633130326134333661666239323835633164316433636431 - # branch: "{{ lookup('env','onap-operate_branch')|default('master', true) }}" - # pull_artifacts: - # timeout: 300 - # parameters: - # ANSIBLE_VERBOSE: "{{ lookup('env','ansible_verbose') }}" - # RUNNER_TAG: "{{ lookup('env','RUNNER_TAG') }}" + kubespray-install: + stage: virt_install + api: https://gitlab.devops.telekom.de/api/v4/projects/36231 #TODO k8s install on onap gitlab + url: https://gitlab.devops.telekom.de/tnap/onapcommunity/integrationproject/onapdeployment/k8s-install.git #TODO k8s install on onap gitlab + trigger_token: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 64376532616532636231396662336134396561643133323432393330623132353063643635336665 + 6232643832343133366333656438616463616336613331320a313166366234356537383639303133 + 64353338653639623034313735653561356362366236636363376431336264653332356134616335 + 3539626665613336350a313035636665383939656461306664386135623139346531343935373763 + 34323539663261363634373031383838376362623135386565643465376661616534 + branch: "{{ lookup('env','vim_branch')|default('master', true) }}" + get_artifacts: + pull_artifacts: "postconfigure" + timeout: 600 + get_bin: true + parameters: + ANSIBLE_VERBOSE: "{{ lookup('env','ansible_verbose') }}" + docker_version: "{{ lookup('env','docker_version') }}" + kubernetes_release: "{{ lookup('env','kubernetes_release') }}" + kubespray_version: "{{ lookup('env','kubespray_version') }}" + helm_version: "{{ lookup('env','helm_version') }}" + RUNNER_TAG: "{{ lookup('env','RUNNER_TAG') }}" + + oom: + stage: apps + api: https://gitlab.com/api/v4/projects/6550110 + url: https://gitlab.com/Orange-OpenSource/lfn/onap/onap_oom_automatic_installation/ + trigger_token: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 363633373665306138306339646434343366613963393165346661373436633032643430 + 326536646361313061663837633137663134306331346439313638390a38623034643463 + 626666366366633764373132373634626436666132333031303033653133613464363632 + 6363366466396136303232356639623961653637340a3136666438333263636436326463 + 616462646239323066316231346131623237646238393361643634366436356639386533 + 3632353462663933643835656364 + branch: "{{ lookup('env','oom_deploy_branch')|default('master', true) }}" + pull_artifacts: postinstallation + timeout: 1400 + get_encrypt: true + get_bin: true + parameters: + GERRIT_REVIEW: "{{ lookup('env','GERRIT_REVIEW') }}" + GERRIT_PATCHSET: "{{ lookup('env','GERRIT_PATCHSET') }}" + OOM_BRANCH: "{{ lookup('env','OOM_BRANCH') }}" + OOM_GIT_REPO: "{{ lookup('env','OOM_GIT_REPO') }}" + OOM_ON_GITLAB: "{{ lookup('env','OOM_ON_GITLAB') }}" + OOM_VERSION: "{{ lookup('env','OOM_VERSION') }}" + PROJECT: "{{ lookup('env','PROJECT') }}" + ANSIBLE_VERBOSE: "{{ lookup('env','ansible_verbose') }}" + TEST_RESULT_DB_URL: "http://onap.api.testresults.opnfv.fr/api/v1/results" + xtesting-onap: + stage: check + api: https://gitlab.com/api/v4/projects/10614465 + url: https://gitlab.com/Orange-OpenSource/lfn/onap/xtesting-onap + trigger_token: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 376564383532616465343061313138373763333833653463333165313062623262303930 + 626531653332333063663134393038623661646430633335393266360a35653732613063 + 333338356136656332323337623534663964653234613836336530303564653463613838 + 3566306635613566373036356135646364613034660a3037323932396165363334616264 + 393938316636316437303261323066326530393363303365623036316463613032343533 + 3234633838343731333166616632 + branch: "{{ lookup('env','xtesting-onap_branch')|default('master', true) }}" + get_artifacts: vim + pull_artifacts: + timeout: 1400 + get_encrypt: true + get_bin: true + parameters: + GERRIT_REVIEW: "{{ lookup('env','GERRIT_REVIEW') }}" + GERRIT_PATCHSET: "{{ lookup('env','GERRIT_PATCHSET') }}" + EXPERIMENTAL: "{{ lookup('env','EXPERIMENTAL') }}" + ONAP_VERSION: "{{ lookup('env','OOM_BRANCH') }}" + DEPLOY_SCENARIO: os-nosdn-nofeature-ha + TEST_RESULT_DB_URL: "http://testresults.opnfv.org/onap/api/v1/results" + PROJECT: "{{ lookup('env','PROJECT') }}" + + trigger: + stage: apps + trigger_token: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 64386138616464653132353964363032346464373363323366616436346263323230353961363263 + 3562653664303631323134313864393364636538643430640a363766316230633932376466643333 + 64386331633737623164313831633537666638623534663736313331313266396438306266636632 + 3532313263396532300a306661393438613734323064313064343361363763636664393231363934 + 37633335396563623462653935393236356139303864646135303935373937623739 + branch: "{{ lookup('env','CI_BUILD_REF_NAME')|default('master', true) }}" + parameters: + ansible_verbose: "{{ lookup('env','ansible_verbose') }}" + RUNNER_TAG: "{{ lookup('env','RUNNER_TAG') }}" diff --git a/pod_inventory/host_vars/onap_oom_gating_azure_3.yml b/pod_inventory/host_vars/onap_oom_gating_azure_3.yml new file mode 100644 index 0000000..545f0bb --- /dev/null +++ b/pod_inventory/host_vars/onap_oom_gating_azure_3.yml @@ -0,0 +1,85 @@ +--- +jumphost: + server: rebond.francecentral.cloudapp.azure.com + user: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 3530396436393930316538613463623538376563633364623938623136353961393336326538 + 39363265373139623432353864306265666437663433363338630a3638386530613233326439 + 3932303435363761346338373534613363336633616537636361396466376435626335333462 + 3234633631396331323639393435370a35646638343365616234383835303932323734613739 + 61313533373262363530 +environment: azure/onap_gating_3/oom_gating +scenario_steps: + config: + project: config + get_artifacts: + - name: azure + static_src: true + infra: az14-gating3 + ssh_access: azure.eyml + build_integration: + project: build_integration + get_artifacts: config + only: + - "PROJECT == testsuite/pythonsdk-tests" + onap_deploy: + branch: add_strimzi_chartmuseum + extra_parameters: + CLEAN: True + WORKAROUND: False + ONAP_REPOSITORY: docker.nexus.azure.onap.eu + ONAP_FLAVOR: unlimited + SERVICEMESH: true + INGRESS: true + DOCKER_HUB_PROXY: docker.nexus.azure.onap.eu + ELASTIC_PROXY: docker.nexus.azure.onap.eu + K8S_GCR_PROXY: docker.nexus.azure.onap.eu + GATHER_NODE_FACTS: False + METRICS_CRD: True + get_artifacts: + - name: vim_deploy:onap_oom_gating_k8s_azure_3 + in_pipeline: false + limit_to: + - vars/clouds.yaml: vars/user_cloud.yml + - name: k8s_deploy:onap_oom_gating_k8s_azure_3 + in_pipeline: false + limit_to: + - inventory/infra: inventory/infra + - vars/pdf.yml: vars/pdf.yml + - vars/idf.yml: vars/idf.yml + - name: config + limit_to: + - vars/ssh_gateways.yml: vars/ssh_gateways.yml + - vars/vaulted_ssh_credentials.yml: vars/vaulted_ssh_credentials.yml + project: oom + onap_test: + project: xtesting-onap + branch: master + get_artifacts: + - name: k8s_deploy:onap_oom_gating_k8s_azure_3 + in_pipeline: false + limit_to: + - vars/kube-config: vars/kube-config + - inventory/infra: inventory/infra + - vars/pdf.yml: vars/pdf.yml + - name: config + limit_to: + - vars/ssh_gateways.yml: vars/ssh_gateways.yml + - vars/vaulted_ssh_credentials.yml: vars/vaulted_ssh_credentials.yml + - name: onap_deploy + limit_to: + - vars/cluster.yml: vars/cluster.yml + extra_parameters: + DEPLOYMENT: oom + INFRA_DEPLOYMENT: aks + DEPLOYMENT_TYPE: full + DEPLOY_SCENARIO: onap-ftw + INGRESS: true + POD: azure-gating-3 + OS_TEST_CLOUD: admin + TEST_RESULT_DB_URL: http://testresults.opnfv.org/onap/api/v1/results + S3_ENDPOINT_URL: http://minio.azure3.onap.eu:9000 + S3_INTERNAL_ENDPOINT_URL: http://minio.minio:9000 + S3_HTTP_DST_URL: http://minio.minio:8181 + RANDOM_WAIT: True + diff --git a/pod_inventory/inventory b/pod_inventory/inventory index 34b2e74..c5c627d 100644 --- a/pod_inventory/inventory +++ b/pod_inventory/inventory @@ -1,2 +1,4 @@ [LF-UNH] -onap-daily-unh-oom-master \ No newline at end of file +onap-daily-unh-oom-master +[LF-GATING] +onap_oom_gating_azure_3 \ No newline at end of file diff --git a/roles/LICENSE b/roles/LICENSE new file mode 100644 index 0000000..3be62f8 --- /dev/null +++ b/roles/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018 Orange-OpenSource / lfn / ci_cd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/roles/README.md b/roles/README.md new file mode 100644 index 0000000..9002afc --- /dev/null +++ b/roles/README.md @@ -0,0 +1,15 @@ +Chained-CI-roles +========== + +Role +---- +Chained-CI is a way to run a set of projects, each one as a job in a top level +pipeline. + +This project, running can run on a gitlab CE, is triggering configured projects +one after the other, or in parallele, sharing configuration through artifacts. +This allow to integrate projects managed by third parties, or running together +independant projects. + +___This project is hosting the roles needed to run the pipelines. The running +project hosting pipelines and the inventory is not yet public___ diff --git a/roles/artifact_init/defaults/main.yaml b/roles/artifact_init/defaults/main.yaml new file mode 100644 index 0000000..c1ccbb9 --- /dev/null +++ b/roles/artifact_init/defaults/main.yaml @@ -0,0 +1,2 @@ +--- +step: "{{ lookup('env', 'CONFIG_NAME') | default('config', true )}}" diff --git a/roles/artifact_init/filter_plugins/filters.py b/roles/artifact_init/filter_plugins/filters.py new file mode 100644 index 0000000..db38fc6 --- /dev/null +++ b/roles/artifact_init/filter_plugins/filters.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 + +import os +import sys + +sys.path.append(os.path.normpath(os.path.join(os.path.dirname(__file__),'../../'))) + +from library.filepath import FilterModule diff --git a/roles/artifact_init/tasks/main.yml b/roles/artifact_init/tasks/main.yml new file mode 100644 index 0000000..e4e4fb6 --- /dev/null +++ b/roles/artifact_init/tasks/main.yml @@ -0,0 +1,180 @@ +--- +## +# Warn if log level is high +## +- name: Warn if log level is high + debug: + msg: "{{ msg.split('\n') }}" + verbosity: 3 + vars: + msg: | + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! Log level is HIGH ! !! + !! Some sensitive data may be visible to everyone. !! + !! Don't forget to clean the task output ! !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +## +# get the config +## + +- name: get artifact_src if we refer to a previous one + when: artifacts_src is defined + uri: + url: "{{ artifacts_src }}" + headers: + PRIVATE-TOKEN: "{{ gitlab.private_token }}" + dest: "{{ playbook_dir }}/artifacts.zip" + +- name: unzip get_artifact archive + when: artifacts_src is defined or artifacts_bin is defined + unarchive: + src: "{{ playbook_dir }}/artifacts.zip" + dest: "{{ playbook_dir }}" + remote_src: "yes" + +- name: delete archive + file: + path: "{{ playbook_dir }}/artifacts.zip" + state: absent + +- name: create artifacts folders + file: + path: "{{ item }}" + state: directory + mode: 0775 + when: item[-1] == '/' + with_items: "{{ vars[lookup( 'env', 'CI_JOB_NAME')].artifacts.paths }}" + +- name: ensure configs can be written + file: + path: "{{ playbook_dir }}/{{ item }}" + mode: 0660 + ignore_errors: true + with_items: + - vars/pdf.yml + - vars/idf.yml + - vars/certificates.yml + - vars/vaulted_ssh_credentials.yml + - vars/ssh_gateways.yml + +- name: get the infra config name + set_fact: + infra_config: "{{ config.infra | default(inventory_hostname) }}" + +- name: get the infra PDF/IDF + when: infra_config != 'NONE' + block: + - name: get PDF configs + uri: + url: >- + {{ config.api }}/repository/files/{{ + [config.path | default(''), 'config'] | + filepath(infra_config, '.yaml') }}?ref={{ config.branch }} + headers: + PRIVATE-TOKEN: "{{ gitlab.private_token }}" + status_code: 200 + return_content: yes + register: pdf_get + + - name: save PDF config + copy: + content: "{{ pdf_get.json.content | b64decode }}" + dest: "{{ playbook_dir }}/vars/pdf.yml" + force: true + mode: 0660 + decrypt: false + + - name: get IDF configs + uri: + url: >- + {{ config.api }}/repository/files/{{ [config.path | default(''), + 'config'] | filepath('idf-', infra_config, '.yaml') + }}?ref={{ config.branch }} + headers: + PRIVATE-TOKEN: "{{ gitlab.private_token }}" + status_code: 200 + return_content: yes + register: idf_get + + - name: save IDF config + copy: + content: "{{ idf_get.json.content | b64decode }}" + dest: "{{ playbook_dir }}/vars/idf.yml" + force: true + mode: 0660 + decrypt: false + +- name: get certificate + uri: + url: >- + {{ config.api }}/repository/files/{{ + [config.path | default(''), 'certificats'] + | filepath(config.certificates) }}?ref={{ config.branch }} + headers: + PRIVATE-TOKEN: "{{ gitlab.private_token }}" + status_code: 200 + return_content: yes + register: certs_get + when: config.certificates is defined + +- name: save certificate + copy: + content: "{{ certs_get.json.content | b64decode }}" + dest: "{{ playbook_dir }}/vars/certificates.yml" + force: true + mode: 0660 + decrypt: false + when: config.certificates is defined + +- name: get ssh credentials + uri: + url: >- + {{ config.api }}/repository/files/{{ + [config.path | default(''), 'ssh_creds'] | + filepath(config.ssh_creds | default(ansible_ssh_creds)) + }}?ref={{ config.branch }} + headers: + PRIVATE-TOKEN: "{{ gitlab.private_token }}" + status_code: 200 + return_content: yes + register: ssh_creds_get + when: config.ansible_ssh_creds is defined or ansible_ssh_creds is defined + +- name: save ssh credentials + copy: + content: "{{ ssh_creds_get.json.content | b64decode }}" + dest: "{{ playbook_dir }}/vars/vaulted_ssh_credentials.yml" + force: true + mode: 0660 + decrypt: false + when: config.ansible_ssh_creds is defined or ansible_ssh_creds is defined + +- name: set ssh gateways config + uri: + url: >- + {{ config.api }}/repository/files/{{ + [config.path | default(''), 'config/ssh_gateways'] + | filepath(config.ssh_access) }}?ref={{ config.branch }} + headers: + PRIVATE-TOKEN: "{{ gitlab.private_token }}" + status_code: 200 + return_content: yes + register: ssh_gw_get + when: config.ssh_access is defined + +- name: save ssh gateways config + copy: + content: "{{ ssh_gw_get.json.content | b64decode }}" + dest: "{{ playbook_dir }}/vars/ssh_gateways.yml" + force: true + mode: 0660 + decrypt: false + when: config.ssh_access is defined + +- name: set basic inventory + copy: + dest: "{{ playbook_dir }}/inventory/inventory" + content: > + jumphost ansible_host={{ jumphost.server }} + ansible_user={{ jumphost.user }} pod={{ inventory_hostname }} diff --git a/roles/get_artifacts/defaults/main.yml b/roles/get_artifacts/defaults/main.yml new file mode 100644 index 0000000..112aa4a --- /dev/null +++ b/roles/get_artifacts/defaults/main.yml @@ -0,0 +1,7 @@ +--- +previous_artifacts_folder: "{{ playbook_dir }}/previous_artifacts" +final_artifacts_folder: "{{  playbook_dir }}/FINAL_ARTIFACT" + +job_id_fetch: + max_page: 100 + per_page: 100 diff --git a/roles/get_artifacts/filter_plugins/filters.py b/roles/get_artifacts/filter_plugins/filters.py new file mode 100644 index 0000000..db38fc6 --- /dev/null +++ b/roles/get_artifacts/filter_plugins/filters.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 + +import os +import sys + +sys.path.append(os.path.normpath(os.path.join(os.path.dirname(__file__),'../../'))) + +from library.filepath import FilterModule diff --git a/roles/get_artifacts/tasks/binary.yml b/roles/get_artifacts/tasks/binary.yml new file mode 100644 index 0000000..99ba930 --- /dev/null +++ b/roles/get_artifacts/tasks/binary.yml @@ -0,0 +1,244 @@ +--- +## +# Handle different get_artifacts types +## +- name: value change for coherency + set_fact: + config: >- + {{ config|combine({'get_artifacts': [] }) }} + when: config.get_artifacts is not defined +- name: value change for coherency + set_fact: + config: >- + {{ config|combine({'get_artifacts': + [{ 'name': config.get_artifacts }] }) }} + when: config.get_artifacts is string + +- debug: + var: config + verbosity: 3 +## +# Prepare a folder for +## + +- name: set previous_artifacts_folder + file: + path: "{{ item }}" + state: directory + loop: + - "{{ previous_artifacts_folder }}" + - "{{ final_artifacts_folder }}" + +- name: create dest folders for the jobs artifacts + file: + path: "{{ previous_artifacts_folder }}/{{ item.name }}" + state: directory + loop: "{{ config.get_artifacts }}" + loop_control: + label: "{{ item.name }}" + +## +# Get all artifacts job ids +## +- name: loop on get_artifacts + include_tasks: get_one_artifact.yml + vars: + artifact_job_name: "{{ item.name }}" + artifact_in_pipeline: "{{ item.in_pipeline | default(true) }}" + when: not (item.static_src | default(false)) + loop: "{{ config.get_artifacts }}" + loop_control: + label: "{{ artifact_job_name }}" + +- name: download all job artifacts + uri: + url: >- + {{ gitlab.api_url }}/projects/{{ lookup('env', 'CI_PROJECT_ID') + }}/jobs/{{ artifact_job_ids[idx] }}/artifacts + headers: + PRIVATE-TOKEN: "{{ gitlab.private_token }}" + status_code: 200 + dest: >- + {{ previous_artifacts_folder }}/{{ item.name }}/artifacts.zip + when: not (item.static_src | default(false)) + loop: "{{ config.get_artifacts }}" + loop_control: + index_var: idx + label: "{{ item.name }}" + +- name: download all static artifacts on public projects + uri: + url: >- + {{ config.url }}/raw/{{ config.branch }}/{{ + config.path | default('') }}/config/artifacts/{{ + item.name }}.zip?inline=false + status_code: 200 + dest: >- + {{ previous_artifacts_folder }}/{{ item.name }}/artifacts.zip + when: (item.static_src | default(false)) and (config.api is not defined) + loop: "{{ config.get_artifacts }}" + loop_control: + label: "{{ item.name }}" + +- name: download all static artifacts using api + uri: + url: >- + {{ config.api }}/repository/files/{{ + [config.path | default('') , 'config/artifacts'] | + filepath(item.name, '.zip') + }}/raw?ref={{ config.branch }} + headers: + PRIVATE-TOKEN: "{{ gitlab.private_token }}" + status_code: 200 + dest: >- + {{ previous_artifacts_folder }}/{{ item.name }}/artifacts.zip + when: (item.static_src | default(false)) and (config.api is defined) + loop: "{{ config.get_artifacts }}" + loop_control: + label: "{{ item.name }}" + +- name: unarchive all artifacts + unarchive: + src: "{{ previous_artifacts_folder }}/{{ item.name }}/artifacts.zip" + dest: "{{ previous_artifacts_folder }}/{{ item.name }}/" + remote_src: "yes" + loop: "{{ config.get_artifacts }}" + loop_control: + label: "{{ item.name }}" + +- name: remove all artifacts archives + file: + path: "{{ previous_artifacts_folder }}/{{ item.name }}/artifacts.zip" + state: absent + loop: "{{ config.get_artifacts }}" + loop_control: + label: "{{ item.name }}" + +- name: create artifacts folders + file: + path: "{{ final_artifacts_folder }}/{{ item }}" + state: directory + recurse: true + mode: 0775 + when: item[-1] == '/' + with_items: "{{ vars['.artifacts_root'].paths }}" + +- name: copy all files if no filters + copy: + decrypt: false + src: "{{ previous_artifacts_folder }}/{{ item.name }}/" + dest: "{{ final_artifacts_folder }}/" + when: item.limit_to is not defined or item.limit_to == None + loop: "{{ config.get_artifacts }}" + loop_control: + label: "{{ item.name }}" + +- name: copy filtered files if filters + include_tasks: limit_to.yml + when: item.limit_to is defined + loop: "{{ config.get_artifacts }}" + vars: + job_name: "{{ item.name }}" + limit_to: "{{ item.limit_to }}" + loop_control: + label: "{{ item.name }}" + +## +# get list of files to archive +## +- name: get list of files to encrypt + find: + paths: "{{ final_artifacts_folder }}" + recurse: true + register: artifacts_files + +- name: set file list + set_fact: + files_list: "{{ artifacts_files.files | map(attribute='path')| list }}" + +## +# If we encode file via ansible vault +## +- name: encrypt files + shell: > + ansible-vault encrypt --vault-password-file {{ + lookup( 'env', 'VAULT_FILE') }} {{ item }} + register: res + loop: "{{ files_list }}" + failed_when: + res.rc == 1 and res.stderr != "ERROR! input is already encrypted" + when: + config.get_encrypt is defined and (config.get_encrypt | bool) + + +## +# Add ssh_gateways file if needed +## + +- name: get config step parameters + set_fact: + config_step: >- + {{ gitlab.git_projects[ + hostvars[inventory_hostname].scenario_steps['config'].project] | + combine(hostvars[inventory_hostname].scenario_steps['config']) }} + +- name: get ssh gateways config + uri: + url: >- + {{ config_step.api }}/repository/files/{{ + [config_step.path | default(''), 'config/ssh_gateways'] | + filepath(config.ssh_access) + }}?ref={{ config_step.branch }} + headers: + PRIVATE-TOKEN: "{{ gitlab.private_token }}" + status_code: 200 + return_content: yes + register: ssh_gw_get + when: config.ssh_access is defined + +- name: save ssh gateways config + copy: + content: "{{ ssh_gw_get.json.content | b64decode }}" + dest: "{{ final_artifacts_folder }}/vars/ssh_gateways.yml" + force: true + mode: 0660 + when: config.ssh_access is defined + +## +# get list of files and folders to archive +## +- name: set file list + set_fact: + arch_files: + "{{ (arch_files | default([])) + + [ final_artifacts_folder + '/' + item ] }}" + loop: "{{ vars['.artifacts_root'].paths }}" + +- name: Prepare artifact archive for binary transmission + archive: + path: "{{ arch_files }}" + dest: "{{ playbook_dir }}/artifacts.zip" + format: zip + +## +# Set the artifact to send +## +- name: "Prepare artifact archive for binary transmission" + slurp: + src: artifacts.zip + register: slurped_artifact + +- name: Add artifacts bin if requested + set_fact: + artifacts_bin: "{{ slurped_artifact.content }}" + +## +# Clean +## +- name: delete temporary folders + file: + path: "{{ item }}" + state: absent + loop: + - "{{ previous_artifacts_folder }}" + - "{{ final_artifacts_folder }}" diff --git a/roles/get_artifacts/tasks/get_one_artifact.yml b/roles/get_artifacts/tasks/get_one_artifact.yml new file mode 100644 index 0000000..ccbdc48 --- /dev/null +++ b/roles/get_artifacts/tasks/get_one_artifact.yml @@ -0,0 +1,49 @@ +--- +## +# Search for a job id +# with name: artifact_job_name +# limit to pipeline if artifact_in_pipeline (default: true) +## + +- name: set empty fact for job + set_fact: + job: {} + artifact_in_pipeline: "{{ artifact_in_pipeline | default(true) }}" + +- name: get job id in this pipeline + when: artifact_in_pipeline | bool + block: + - name: "Get job successful job ids of the pipeline" + uri: + url: >- + {{ gitlab.api_url }}/projects/{{ + lookup( 'env', 'CI_PROJECT_ID') }}/pipelines/{{ + lookup( 'env', 'CI_PIPELINE_ID') }}/jobs?scope[]=success + method: GET + headers: + PRIVATE-TOKEN: "{{ gitlab.private_token }}" + register: pipeline_success_jobs + - name: get the job id + set_fact: + job: >- + {{ { 'id': + pipeline_success_jobs.json |json_query( + '[?name==`'+ artifact_job_name + ':' + + inventory_hostname +'`].id') | last + } }} + +- name: fetch the job id corresponding to get_artifact value if not in pipeline + include_tasks: job_id_fetch.yml + loop: "{{ range(0, job_id_fetch.max_page)| list }}" + when: not (artifact_in_pipeline | bool ) + loop_control: + loop_var: page + +- name: check we found an artifact job id + fail: + msg: 'We can not found a correct job id' + when: job.id is not defined + +- name: get last successful job id + set_fact: + artifact_job_ids: "{{ (artifact_job_ids|default([])) + [job.id] }}" diff --git a/roles/get_artifacts/tasks/job_id_fetch.yml b/roles/get_artifacts/tasks/job_id_fetch.yml new file mode 100644 index 0000000..cab4bcb --- /dev/null +++ b/roles/get_artifacts/tasks/job_id_fetch.yml @@ -0,0 +1,20 @@ +--- + +- block: + - name: "Get successful job ids if artifact fetching" + uri: + url: >- + {{ gitlab.api_url }}/projects/{{ lookup( 'env', 'CI_PROJECT_ID') + }}/jobs?scope[]=success&per_page={{ job_id_fetch.per_page + }}&page={{ page }} + method: GET + headers: + PRIVATE-TOKEN: "{{ gitlab.private_token }}" + register: successful_jobs + - name: save successful job + set_fact: + job: >- + {{ successful_jobs.json| + selectattr('name', 'equalto', artifact_job_name)| list | + first | default({}) }} + when: job.id is not defined diff --git a/roles/get_artifacts/tasks/limit_to.yml b/roles/get_artifacts/tasks/limit_to.yml new file mode 100644 index 0000000..2e1b782 --- /dev/null +++ b/roles/get_artifacts/tasks/limit_to.yml @@ -0,0 +1,20 @@ +--- + +- debug: + var: limit_to + verbosity: 3 +- debug: + var: job_name + verbosity: 3 +- name: copy all files if filters and rename if needed + copy: + decrypt: false + src: "{{ previous_artifacts_folder }}/{{ job_name }}/{{ original }}" + dest: "{{ final_artifacts_folder }}/{{ renamed }}" + loop: "{{ limit_to }}" + vars: + original: "{{ file.keys()|first }}" + renamed: "{{ file.values()|first }}" + loop_control: + loop_var: file + label: "{{ original }}" diff --git a/roles/get_artifacts/tasks/main.yml b/roles/get_artifacts/tasks/main.yml new file mode 100644 index 0000000..605521c --- /dev/null +++ b/roles/get_artifacts/tasks/main.yml @@ -0,0 +1,34 @@ +--- +## +# Check config is prepared +## +- name: check 'step' is set + fail: + msg: 'Prepare role must be run before' + when: config is not defined + + +- name: recover previous artifacts + when: + config.get_artifacts is defined and + config.get_artifacts + block: + ## + # If we get previous artifacts via url + ## + - name: Add artifacts via source + include_tasks: url.yml + when: + (config.get_bin is not defined or not (config.get_bin | bool)) + and (config.ssh_access is not defined) + and (config.get_artifacts is string) + + ## + # If we get previous artifacts via url + ## + - name: Add artifacts via binary + include_tasks: binary.yml + when: + (config.get_bin is defined and (config.get_bin | bool)) + or (config.ssh_access is defined) + or (config.get_artifacts is not string) diff --git a/roles/get_artifacts/tasks/url.yml b/roles/get_artifacts/tasks/url.yml new file mode 100644 index 0000000..a2b5a91 --- /dev/null +++ b/roles/get_artifacts/tasks/url.yml @@ -0,0 +1,13 @@ +--- + +- name: get_artifacts with just one value + include_tasks: get_one_artifact.yml + vars: + artifact_job_name: "{{ config.get_artifacts }}" + +- name: get the url of the artifact + set_fact: + artifacts_src: >- + {{ gitlab.api_url }}/projects/{{ + lookup( 'env', 'CI_PROJECT_ID') }}/jobs/{{ + artifact_job_ids[0] }}/artifacts diff --git a/roles/gitlab-ci-generator/defaults/main.yml b/roles/gitlab-ci-generator/defaults/main.yml new file mode 100644 index 0000000..11b8726 --- /dev/null +++ b/roles/gitlab-ci-generator/defaults/main.yml @@ -0,0 +1,3 @@ +--- +ci_file: "{{ lookup('env', 'CI_FILE') + | default(playbook_dir +'/.gitlab-ci.yml', true)}}" diff --git a/roles/gitlab-ci-generator/tasks/main.yml b/roles/gitlab-ci-generator/tasks/main.yml new file mode 100644 index 0000000..a96ae7c --- /dev/null +++ b/roles/gitlab-ci-generator/tasks/main.yml @@ -0,0 +1,45 @@ +--- +## +# Warn if log level is high +## +- name: Warn if log level is high + debug: + msg: "{{ msg.split('\n') }}" + verbosity: 3 + vars: + msg: | + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! Log level is HIGH ! !! + !! Some sensitive data may be visible to everyone. !! + !! Don't forget to clean the task output ! !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +## +# Generate the CI file +## +- name: generate the new gitlab-ci file from inventory + run_once: true + block: + - name: create a tempfile + tempfile: + state: file + suffix: temp + register: tmp_file + - copy: + src: "{{ ci_file }}" + dest: "{{ tmp_file.path }}" + ignore_errors: true + - name: generate the gitlab-ci.yml + template: + src: gitlab-ci.yml + dest: "{{ ci_file }}" + rescue: + - name: restore gitlab-ci + copy: + src: "{{ tmp_file.path }}" + dest: "{{ ci_file }}" + always: + - name: destroy temp file + file: + path: "{{ tmp_file.path }}" + state: absent diff --git a/roles/gitlab-ci-generator/templates/gitlab-ci.yml b/roles/gitlab-ci-generator/templates/gitlab-ci.yml new file mode 100644 index 0000000..51ceb05 --- /dev/null +++ b/roles/gitlab-ci-generator/templates/gitlab-ci.yml @@ -0,0 +1,204 @@ +--- +################################################################################ +# +# !! DO NOT EDIT MANUALLY !! +# +# This file is generated by gitlab-ci-generator +# +################################################################################ + +stages: +{% for stage in stages %} + - {{ stage }} +{% endfor %} + +variables: + GIT_SUBMODULE_STRATEGY: recursive + VAULT_FILE: .vault + +################################################################################ +# Shared parameters +################################################################################ +.runner_tags: &runner_tags + tags: +{% for tag in runner.tags %} + - {{ tag }} +{% endfor %} + +.syntax_checking: &syntax_checking + only: + - pushes + stage: lint + +.artifacts_root: &artifacts_root + name: "$CI_JOB_NAME-$CI_COMMIT_REF_NAME" + paths: + - vars/ + - inventory/ + +.artifacts: &artifacts + artifacts: + <<: *artifacts_root + expire_in: 15 days + +.artifacts_longexpire: &artifacts_longexpire + artifacts: + <<: *artifacts_root + expire_in: 1 yrs + +.runner_env: &runner_env +{% for var_name, var_value in runner.env_vars.items()|default({'foo': 'bar'}) %} + {{ var_name }}: "{{ var_value }}" +{% endfor %} + +################################################################################ +# Linting +################################################################################ + +yaml_checking: + <<: *syntax_checking + <<: *runner_tags + variables: + <<: *runner_env + image: {{ runner.docker_proxy }}sdesbure/yamllint:latest + script: + - > + yamllint -d "line-length: { + max: 80, + allow-non-breakable-words: true, + allow-non-breakable-inline-mappings: true}" + .gitlab-ci.yml + - yamllint *.yml + +ansible_linting: + <<: *syntax_checking + <<: *runner_tags + variables: + <<: *runner_env + image: {{ runner.docker_proxy }}sdesbure/ansible-lint:latest + script: + - ansible-lint -x ANSIBLE0010,ANSIBLE0013 run-ci.yml + +{% if not (disable_pages | default(false)) %} +################################################################################ +# Pages +################################################################################ + +pages: + image: {{ runner.docker_proxy }}{{ runner.image }}:{{ runner.image_tag }} + stage: lint + <<: *runner_tags + variables: + <<: *runner_env + script: + - ./chained-ci-vue/init.sh ./pod_inventory + artifacts: + paths: + - public + only: + - master + except: + - triggers + - api + - external + - pipelines + - schedules + - web + +{% endif %} + +################################################################################ +# Jobs +################################################################################ + +.vault_mgmt: &vault_mgmt + before_script: + - echo ${ANSIBLE_VAULT_PASSWORD} > ${PWD}/${VAULT_FILE} + after_script: + - rm -f $PWD/.vault + +.set_config: &set_config + <<: *runner_tags + <<: *vault_mgmt + image: {{ runner.docker_proxy }}{{ runner.image }}:{{ runner.image_tag }} + script: + - > + ansible-playbook -i pod_inventory/inventory --limit ${pod} + --vault-password-file ${PWD}/${VAULT_FILE} + ${ansible_verbose} artifacts_init.yml + +.run_ci: &run_ci + <<: *runner_tags + <<: *vault_mgmt + image: {{ runner.docker_proxy }}{{ runner.image }}:{{ runner.image_tag }} + script: + - > + ansible-playbook -i pod_inventory/inventory --limit ${pod} + --extra-vars "step=${CI_JOB_NAME%:*}" + --vault-password-file ${PWD}/${VAULT_FILE} + ${ansible_verbose} run-ci.yml + +.trigger: &trigger + <<: *runner_tags + <<: *vault_mgmt + image: {{ runner.docker_proxy }}{{ runner.image }}:{{ runner.image_tag }} + script: + - > + ansible-playbook -i pod_inventory/inventory --limit ${pod} + --vault-password-file ${PWD}/${VAULT_FILE} + ${ansible_verbose} --extra-vars "step=trigger" trigger_myself.yml + +{% for pipeline in groups['all'] %} +################################################################################ +# {{ pipeline }} +################################################################################ + +.{{ pipeline }}_global: &{{ pipeline }}_global + variables: + pod: {{ pipeline }} + <<: *runner_env +{% if hostvars[pipeline].environment is defined %} + environment: + name: {{ hostvars[pipeline].environment }} +{% endif %} + only: + variables: + - $POD == "{{ pipeline }}" +{% if hostvars[pipeline].inpod is defined %} + - $INPOD == "{{ hostvars[pipeline].inpod }}" +{% endif %} + refs: + - web + - schedules + - triggers + +{% for stage in stages %} +{% for task in hostvars[pipeline].scenario_steps %} +{% if hostvars[pipeline].scenario_steps[task].stage | default( + gitlab.git_projects[hostvars[pipeline].scenario_steps[task].project].stage + ) == stage %} +{{ task }}:{{ pipeline }}: + stage: {{ stage }} + <<: *{{ pipeline }}_global +{% if hostvars[pipeline].scenario_steps[task].project == 'config' %} + <<: *set_config +{% elif hostvars[pipeline].scenario_steps[task].project == 'trigger' %} + <<: *trigger +{% else %} + <<: *run_ci +{% endif %} +{% if (hostvars[pipeline].scenario_steps[task].pull_artifacts + | default(gitlab.git_projects[hostvars[pipeline].scenario_steps[task].project].pull_artifacts) + | default(false)) + or task == 'config' %} + <<: *artifacts{% if hostvars[pipeline].longlife_artifact | default(false) | bool %}_longexpire{% endif %} + +{% endif %} +{% endif %} +{% endfor %} +{% endfor %} + +{% endfor %} +## +# End of generated file +## diff --git a/roles/library/filepath.py b/roles/library/filepath.py new file mode 100644 index 0000000..356f5fb --- /dev/null +++ b/roles/library/filepath.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 + +import os +from urllib.parse import quote + +class FilterModule(object): + def filters(self): + return { + 'filepath': self.filepath + } + + def filepath(self, path, *filename): + # + # path: a string or a list of string that contains successive parts + # of the path. Nul or empty parts are removed + # filename: the optionnal filename to be used after the path. It may + # be specified using multiple args to be concatenate (useful + # when building dynamic names in ansible/jinja templates) + # + '''build a gitlab filepath given `path' and `filename'.''' + + if path is not None: + if not isinstance(path, list): + path = [path] + path = list(filter(None, path)) + if path: + path = os.path.normpath(os.path.join(path[0], *path[1:])) + + if filename: + filename = ''.join(list(filter(None, filename))) + + if path and filename: + path = os.path.join(path, filename) + elif filename: + path = filename + + if path: + return quote(path, safe='') + + return None diff --git a/roles/logo.png b/roles/logo.png new file mode 100644 index 0000000..1fc981e Binary files /dev/null and b/roles/logo.png differ diff --git a/roles/logo.svg b/roles/logo.svg new file mode 100644 index 0000000..b2e6875 --- /dev/null +++ b/roles/logo.svg @@ -0,0 +1,130 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/roles/prepare/README.md b/roles/prepare/README.md new file mode 100644 index 0000000..2cdcd0e --- /dev/null +++ b/roles/prepare/README.md @@ -0,0 +1,28 @@ +# Chained CI Prepare role + +This role prepare the settings before getting artifacts and run the playbook. +It: + - Warn if log level is HIGH to avoid data leaking + - Check the step parameter is set + - prepare the `config` fact + - test `only` and `except` step parameters to limit when jobs are runned. + This will __SKIP__ this job if __ONE of__ the `except` condition is + successful __AND__ if __ALL__ the `only` conditions are failing. Those + conditions are testing environment variables like this: + - `VAR`: this test the presence of a variable that is not empty + - `VAR == value`: this test the exact value of a variable + - `VAR != value`: this test the exact difference of a variable. + - `VAR in [value1, value2]`: this test the exact value of a variable is a + set of possibilities + +## Example + +``` +except: + - "XXX in [aaa, aab]" + - "YYY" +only: + - "AAA == yes" + - "BBB != no" + - "CCC in [pitet, possible]" +``` diff --git a/roles/prepare/tasks/continue.yml b/roles/prepare/tasks/continue.yml new file mode 100644 index 0000000..5d664c7 --- /dev/null +++ b/roles/prepare/tasks/continue.yml @@ -0,0 +1,15 @@ +--- + +- name: we have to continue this role + debug: + msg: "{{ msg.split('\n') }}" + vars: + msg: | + ************************************************************************** + ** We continue the play + ** REASON = '{{ condition }}' + ************************************************************************** + +- name: Do not skip the run of the play + set_fact: + skip_run: false diff --git a/roles/prepare/tasks/except.yml b/roles/prepare/tasks/except.yml new file mode 100644 index 0000000..8d8abff --- /dev/null +++ b/roles/prepare/tasks/except.yml @@ -0,0 +1,55 @@ +--- +# in this file, default variable value is '-666-', I hope no one will ever +# test the number of the beast :) + + +- name: Testing 'EXCEPT' condition + debug: + var: condition + +- name: if condition is only one word + block: + - name: check variable is present + include_tasks: exit.yml + when: lookup('env', condition)| default(False, true) + when: condition.split()| length == 1 + +- name: if condition contains '==' + block: + - name: split condition with '==' + set_fact: + cond: "{{ (condition|replace(' == ', '==')).split('==') }}" + - debug: msg="{{ cond[1:]| join('==') }}" + - name: test condition + include_tasks: exit.yml + when: (lookup('env', cond[0])| default('-666-', true)) == ( + cond[1:]| join('==')) + when: condition is search('==') + +- name: if condition contains '!=' + block: + - name: split condition with '!=' + set_fact: + cond: "{{ (condition|replace(' != ', '!=')).split('!=') }}" + - name: test condition + include_tasks: exit.yml + when: (lookup('env', cond[0])| default('-666-', true)) != ( + cond[1:]| join('!=')) + when: condition is search('!=') + +- name: if condition contains 'in' + block: + - name: split condition with ' in ' + set_fact: + cond: "{{ condition.split(' in ') }}" + - name: split list + set_fact: + inlist: | + {{ (cond[1]| + replace(', ', ',')| replace(' ,', ',')| + replace(' ]', '') | replace(']', '')| + replace('[ ', '') | replace('[', '')).split(',') }} + - name: test condition + include_tasks: exit.yml + when: (lookup('env', cond[0])| default('-666-', true)) in inlist + when: condition is search(' in ') diff --git a/roles/prepare/tasks/exit.yml b/roles/prepare/tasks/exit.yml new file mode 100644 index 0000000..58fb43d --- /dev/null +++ b/roles/prepare/tasks/exit.yml @@ -0,0 +1,13 @@ +--- + +- name: we have to end this role + debug: + msg: "{{ msg.split('\n') }}" + vars: + msg: | + ************************************************************************** + ** We finish the play here + ** REASON = '{{ condition }}' + ************************************************************************** + +- meta: end_play diff --git a/roles/prepare/tasks/main.yml b/roles/prepare/tasks/main.yml new file mode 100644 index 0000000..ce08540 --- /dev/null +++ b/roles/prepare/tasks/main.yml @@ -0,0 +1,93 @@ +--- +## +# Warn if log level is high +## +- name: Echo running pipeline link + debug: + msg: "{{ msg.split('\n') }}" + verbosity: 3 + vars: + msg: | + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! Log level is HIGH ! !! + !! Some sensitive data may be visible to everyone. !! + !! Don't forget to clean the task output ! !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +## +# Check Step parameters +## +- name: check 'step' is set + fail: + msg: 'Step must be defined ! (use --extra-vars "step=test1")' + when: step is not defined + +## +# Check the pod is not protected +## +- name: clean var + set_fact: + protected_pods: [] + when: protected_pods|default() == None + +- name: check pod protection + fail: + msg: 'This pod is protected' + when: + inventory_hostname in protected_pods and + lookup( 'env', 'AREYOUSURE') != 'MAIS OUI !!!' + +## +# Prepare the step config +## +- name: get default step parameters + set_fact: + config: >- + {{ gitlab.git_projects[ + hostvars[inventory_hostname].scenario_steps[step].project] | + combine(hostvars[inventory_hostname].scenario_steps[step]) }} + +- name: merge step parameters + set_fact: + config: >- + {{ config| combine( + {'parameters': config.parameters| + combine(config.extra_parameters)}) }} + when: config.extra_parameters is defined + +## +# Check if we must run this step - Must be run at the end of this role +## + +- name: Set default skip_run value + set_fact: + skip_run: false + +- name: run except parameter + include_tasks: except.yml + loop: "{{ config.except }}" + loop_control: + loop_var: condition + label: "{{ condition }}" + when: config.except is defined + +- name: Set default skip_run value + set_fact: + skip_run: true + when: config.only is defined + +- name: run only parameter + include_tasks: only.yml + loop: "{{ config.only }}" + vars: + skip_all: false + loop_control: + loop_var: condition + label: "{{ condition }}" + when: config.only is defined + +- name: Skip if none of ONLY is successful + include_tasks: exit.yml + vars: + condition: "None of ONLY conditions are successful" + when: config.only is defined and skip_run diff --git a/roles/prepare/tasks/only.yml b/roles/prepare/tasks/only.yml new file mode 100644 index 0000000..893d32b --- /dev/null +++ b/roles/prepare/tasks/only.yml @@ -0,0 +1,57 @@ +--- +# in this file, default variable value is '-666-', I hope no one will ever +# test the number of the beast :) + +- name: test condition only if the previous failed + when: skip_run + block: + - name: Testing 'ONLY' condition + debug: + var: condition + + - name: if condition is only one word + block: + - name: check variable is present + include_tasks: continue.yml + when: lookup('env', condition)| default(False, true) + when: condition.split()| length == 1 + + - name: if condition contains '==' + block: + - name: split condition with '==' + set_fact: + cond: "{{ (condition|replace(' == ', '==')).split('==') }}" + - debug: msg="{{ cond[1:]| join('==') }}" + - name: test condition + include_tasks: continue.yml + when: (lookup('env', cond[0])| default('-666-', true)) == ( + cond[1:]| join('==')) + when: condition is search('==') + + - name: if condition contains '!=' + block: + - name: split condition with '!=' + set_fact: + cond: "{{ (condition|replace(' != ', '!=')).split('!=') }}" + - name: test condition + include_tasks: continue.yml + when: (lookup('env', cond[0])| default('-666-', true)) != ( + cond[1:]| join('!=')) + when: condition is search('!=') + + - name: if condition contains 'in' + block: + - name: split condition with ' in ' + set_fact: + cond: "{{ condition.split(' in ') }}" + - name: split list + set_fact: + inlist: | + {{ (cond[1]| + replace(', ', ',')| replace(' ,', ',')| + replace(' ]', '') | replace(']', '')| + replace('[ ', '') | replace('[', '')).split(',') }} + - name: test condition + include_tasks: continue.yml + when: (lookup('env', cond[0])| default('-666-', true)) in inlist + when: condition is search(' in ') diff --git a/roles/run-ci/tasks/grafana_start.yml b/roles/run-ci/tasks/grafana_start.yml new file mode 100644 index 0000000..183a439 --- /dev/null +++ b/roles/run-ci/tasks/grafana_start.yml @@ -0,0 +1,42 @@ +--- +- block: + - name: get start time (epoch+milliseconds) + set_fact: + time_start: "{{ lookup('pipe', 'date +%s%N | head -c 13' ) | int }}" + + - name: set tags + set_fact: + grafana_tags: "{{ [ inventory_hostname ] }}" + + - name: add inpod in tags + set_fact: + grafana_tags: "{{ grafana_tags + [ inpod ] }}" + when: inpod is defined + + - name: "Create a grafana annotation" + uri: + url: "{{ grafana.api | regex_replace('\\/$', '') }}/annotations" + method: POST + status_code: 200 + body_format: "json" + body: "{{ + { + 'time': time_start | int, + 'isRegion': true, + 'timeEnd': (time_start | int + 10000000), + 'tags': grafana_tags, + 'title': step, + 'text': text + } + }}" + headers: + Content-Type: "application/json" + Accept: "application/json" + Authorization: "Bearer {{ grafana.token }}" + register: grafana_events + vars: + text: + "{{ step }} running" + + delegate_to: "{{ grafana.jumphost }}" + ignore_errors: true diff --git a/roles/run-ci/tasks/grafana_stop.yml b/roles/run-ci/tasks/grafana_stop.yml new file mode 100644 index 0000000..4d5a7e7 --- /dev/null +++ b/roles/run-ci/tasks/grafana_stop.yml @@ -0,0 +1,53 @@ +--- +- block: + - name: get end time + set_fact: + time_end: "{{ lookup('pipe', 'date +%s%N | head -c 13' ) | int }}" + + - name: calculate duration + set_fact: + duration: + "{{ ((time_end|int) - (time_start|int))/1000 }}" + + - name: "update a grafana annotation start" + uri: + url: + "{{ grafana.api | regex_replace('\\/$', '') }}/annotations/{{ + grafana_events.json.id }}" + method: PUT + status_code: 200 + body_format: "json" + body: "{{ + { + 'time': time_start | int, + 'tags': grafana_tags + [ result ], + 'text': text + '
Duration (s): ' + duration + } + }}" + headers: + Content-Type: "application/json" + Accept: "application/json" + Authorization: "Bearer {{ grafana.token }}" + + - name: "update a grafana annotation end" + uri: + url: + "{{ grafana.api | regex_replace('\\/$', '') }}/annotations/{{ + grafana_events.json.endId }}" + method: PUT + status_code: 200 + body_format: "json" + body: "{{ + { + 'time': time_end | int, + 'tags': grafana_tags + [ result ], + 'text': text + '
Duration (s): ' + duration + } + }}" + headers: + Content-Type: "application/json" + Accept: "application/json" + Authorization: "Bearer {{ grafana.token }}" + + delegate_to: "{{ grafana.jumphost }}" + ignore_errors: true diff --git a/roles/run-ci/tasks/main.yml b/roles/run-ci/tasks/main.yml new file mode 100644 index 0000000..eed27f8 --- /dev/null +++ b/roles/run-ci/tasks/main.yml @@ -0,0 +1,270 @@ +--- + +## +# Prepare base of variables to send +## +- name: prepare variables to sent + set_fact: + params: + { + 'token': "{{ config.trigger_token }}", + 'ref': "{{ config.branch }}", + 'variables[source_job_name]': "{{ step }}", + 'variables[pod]': "{{ inventory_hostname }}", + 'variables[jumphost]': "{{ jumphost }}", + } + +## +# Prepare the artifacts to get +## + +- name: add bin artifacts param + when: artifacts_bin is defined + set_fact: + params: + "{{ params|combine({'variables[artifacts_bin]': artifacts_bin }) }}" + +- name: add src artifacts param + when: artifacts_src is defined + set_fact: + params: + "{{ params|combine({'variables[artifacts_src]': artifacts_src }) }}" + +- name: ensure artifacts.zip is not present + file: + path: "{{ playbook_dir }}/artifacts.zip" + state: absent + +- name: set healthchecks base url + set_fact: + base_url: "{{ gitlab.healthchecks_url }}/ping/{{ healthchecks_id }}" + when: healthchecks_id is defined + +## +# Run the step +## +- name: Run step + block: + ## + # add step parameters in the parameters to send + ## + - name: Add step parameters + set_fact: + params: "{{ params|combine({key: value}) }}" + vars: + key: "variables[{{ item.key }}]" + value: "{{ item.value }}" + with_dict: "{{ config.parameters }}" + when: config.parameters is defined and config.parameters != None + + ## + # add NOVAULT_LIST parameter in the parameters to send + ## + - name: Add NOVAULT_LIST parameter + set_fact: + params: "{{ params|combine({key: value}) }}" + vars: + key: "variables[NOVAULT_LIST]" + value: "{{ config.novault |join(\"\n\") }}" + when: config.novault is defined + + ## + # Trigger the pipeline + ## + - name: "Trigger a new pipeline for step {{ step }}" + uri: + url: "{{ config.api }}/trigger/pipeline" + method: POST + status_code: 201 + body_format: raw + body: "{{ params| urlencode }}" + headers: + Content-Type: "application/x-www-form-urlencoded" + register: trigger_out + + - name: set pipeline url + set_fact: + pipeline_url: "{{ config.url }}/pipelines/{{ trigger_out.json.id }}" + api_pipeline_url: "{{ config.api }}/pipelines/{{ trigger_out.json.id }}" + + - name: Echo running pipeline link + debug: + msg: "{{ msg.split('\n') }}" + vars: + msg: | + ****************************************************************** + * Pipeline triggered for step '{{ step }}' + * {{ pipeline_url }} + ****************************************************************** + + - name: set grafana start point + include_tasks: grafana_start.yml + when: grafana is defined + + - name: "Wait for pipeline result {{ step }}" + uri: + url: "{{ config.api }}/pipelines/{{ trigger_out.json.id }}" + method: GET + status_code: 200 + return_content: 'yes' + headers: + PRIVATE-TOKEN: + "{{ config.api_token|default(gitlab.private_token, 'true') }}" + register: pipeline_out + retries: "{{ config.timeout }}" + delay: "{{ gitlab.pipeline.delay }}" + until: (((pipeline_out.json + |default({'status':'unknown'})).status + |default('unknown')) + not in ['created', 'waiting_for_resource', 'preparing', + 'pending', 'running', 'unknown']) or ( + pipeline_out.status == 401 + ) + + + - name: Exit -1 + fail: + when: pipeline_out.json.status not in ['success'] + + ## + # When finished, recover an artifact if requested + ## + - name: pull artifacts_src + when: + config.pull_artifacts is defined and config.pull_artifacts != None + block: + - name: "Get job id for the artifact to get" + uri: + url: >- + {{ config.api + }}/pipelines/{{ trigger_out.json.id }}/jobs?scope[]=success + method: GET + headers: + PRIVATE-TOKEN: + "{{ config.api_token|default(gitlab.private_token, 'true') }}" + register: pipeline_success_jobs + + - name: download job artifact + uri: + url: "{{ config.api }}/jobs/{{ job_id[0] }}/artifacts" + headers: + PRIVATE-TOKEN: + "{{ config.api_token|default(gitlab.private_token, 'true') }}" + dest: "{{ playbook_dir }}/artifacts.zip" + vars: + job_id: >- + {{ pipeline_success_jobs.json |json_query( + '[?name==`'+ config.pull_artifacts +'`].id') }} + + - name: remove actual artifacts + file: + path: "{{ item }}" + state: absent + when: item[-1] == '/' + with_items: + "{{ vars[lookup( 'env', 'CI_JOB_NAME')].artifacts.paths }}" + + - name: create artifacts folders + file: + path: "{{ item }}" + state: directory + recurse: true + mode: 0775 + when: item[-1] == '/' + with_items: + "{{ vars[lookup( 'env', 'CI_JOB_NAME')].artifacts.paths }}" + + - name: unarchive artifacts + unarchive: + src: "{{ playbook_dir }}/artifacts.zip" + dest: "{{ playbook_dir }}" + remote_src: "yes" + + - name: trigger OK healthchecks + uri: + url: "{{ base_url }}" + when: healthchecks_id is defined + ignore_errors: true + + - name: update grafana stop point + include_tasks: grafana_stop.yml + vars: + result: "{{ pipeline_out.json.status }}" + text: "{{ step }} succeeded" + when: grafana is defined and grafana_events is defined + + ## + # If something failed, print the jobs that failed + ## + rescue: + - name: print last pipeline result for forensic + debug: + var: pipeline_out + verbosity: 3 + + - name: update grafana stop point + include_tasks: grafana_stop.yml + vars: + result: "{{ pipeline_out.json.status }}" + text: "{{ step }} failed" + when: grafana is defined and grafana_events is defined + + - name: trigger Failed healthcheck + uri: + url: "{{ base_url }}/fail" + when: healthchecks_id is defined + + - name: "Show last pipeline_out value" + debug: + msg: "{{ pipeline_out.json | default('No pipeline out') }}" + verbosity: 3 + + - name: "RESCUE - Get jobs list that failed" + uri: + url: "{{ config.api }}/pipelines/{{ trigger_out.json.id }}/jobs/" + method: GET + status_code: 200 + return_content: 'yes' + headers: + PRIVATE-TOKEN: + "{{ config.api_token|default(gitlab.private_token, 'true') }}" + register: jobs_list + + - name: RESCUE - filter failed jobs + set_fact: + failed_jobs: + "{{ failed_jobs | default({}) | combine({ item.id: + {'stage': item.stage, + 'name': item.name, + 'status': item.status, + 'duration': item.duration, + 'url': url + }})}}" + vars: + url: "{{ config.url }}/-/jobs/{{ item.id }}" + when: item.status not in ['success', 'skipped'] + with_items: "{{ jobs_list.json }}" + + - name: RESCUE - run failed ! + when: true + fail: + msg: "{{ msg.split('\n') }}" + vars: + msg: | + ****************************************************************** + * Oh ! NO !!! Pipeling of the project failed !!! + * ----------------------- + * Step: {{ step }} + * Project: {{ config.project }} + * Status: {{ pipeline_out.json.status }} + * Pipeline: '{{ pipeline_url }}' + * API pipeline url: '{{ api_pipeline_url }}' + * Failed jobs: + {% for job_id, job_status in failed_jobs.items() -%} + * - id: {{ job_id }} + * name: {{ job_status.stage }}/{{ job_status.name }} + * status: {{ job_status.status }} + * duration: {{ job_status.duration }} + * link: {{ job_status.url }} + {% endfor %} + ****************************************************************** diff --git a/roles/trigger_myself/tasks/main.yml b/roles/trigger_myself/tasks/main.yml new file mode 100644 index 0000000..bbea974 --- /dev/null +++ b/roles/trigger_myself/tasks/main.yml @@ -0,0 +1,75 @@ +--- +- name: check 'step' is set + fail: + msg: 'Step must be defined ! (use --extra-vars "step=test1")' + when: step is not defined + +- name: get default step parameters + set_fact: + config: >- + {{ gitlab.git_projects[ + hostvars[inventory_hostname].scenario_steps[step].project] | + combine(hostvars[inventory_hostname].scenario_steps[step]) }} + +- name: merge step parameters + set_fact: + config: >- + {{ config| combine( + {'parameters': config.parameters| + combine(config.extra_parameters)}) }} + when: config.extra_parameters is defined + + +## +# Prepare base of variables to send +## +- name: prepare variables to sent + set_fact: + params: + { + 'token': "{{ config.trigger_token}}", + 'ref': "{{ config.branch }}", + 'variables[source_job_name]': "{{ step }}", + 'variables[triggered_from]': "{{ lookup('env','CI_JOB_NAME') }}", + 'variables[INPOD]': "{{ inventory_hostname }}", + 'variables[jumphost]': "{{ jumphost }}", + } + +- name: Add step parameters + set_fact: + params: "{{ params|combine({key: value}) }}" + vars: + key: "variables[{{ item.key }}]" + value: "{{ item.value }}" + with_dict: "{{ config.parameters }}" + when: config.parameters is defined + + +## +# Trigger the pipeline +## +- name: "Trigger a new pipeline for step {{ step }}" + uri: + url: >- + {{ gitlab.api_url}}/projects/{{ lookup( 'env', 'CI_PROJECT_ID') + }}/trigger/pipeline + method: POST + status_code: 201 + body_format: raw + body: "{{params| urlencode}}" + headers: + Content-Type: "application/x-www-form-urlencoded" + register: trigger_out + +- name: Echo running pipeline link + debug: + msg: "{{ msg.split('\n') }}" + vars: + url: >- + {{ lookup('env','CI_PROJECT_URL') }}/pipelines/{{ + trigger_out.json.id }}" + msg: | + ****************************************************************** + * Pipeline triggered for step '{{ step }}' + * {{ url }} + ****************************************************************** -- cgit 1.2.3-korg