aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarek Szwałkiewicz <marek.szwalkiewicz@external.t-mobile.pl>2023-03-01 12:27:28 +0100
committerMarek Szwałkiewicz <marek.szwalkiewicz@external.t-mobile.pl>2023-03-03 13:46:02 +0100
commit70fa03898ee412e30b6b87cf961004bf16ccaef4 (patch)
tree10cca3196bd5db69ee643316365a00f1276dba04
parent0399d9842c2a5670e4ee21d45343d2ac168eee2d (diff)
[GATING] Add configuration for Azure3 gating in the fork of chained-ci
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 <marek.szwalkiewicz@external.t-mobile.pl> Change-Id: Idb475c166d78f10ed4204153ab634110aa9093f6
-rw-r--r--.gitlab-ci.yml87
-rw-r--r--.gitmodules6
-rw-r--r--chained-ci-vue/LICENSE201
-rw-r--r--chained-ci-vue/README.md4
-rw-r--r--chained-ci-vue/favicon.pngbin0 -> 5898 bytes
-rw-r--r--chained-ci-vue/index.html238
-rwxr-xr-xchained-ci-vue/init.sh68
-rw-r--r--chained-ci-vue/js/config.js16
-rw-r--r--chained-ci-vue/js/index.js250
-rw-r--r--chained-ci-vue/js/lib.js558
-rw-r--r--chained-ci-vue/js/visibility.LICENSE3
-rw-r--r--chained-ci-vue/js/visibility.core.js189
-rw-r--r--chained-ci-vue/js/visibility.timers.js161
-rw-r--r--chained-ci-vue/logo.svg130
-rw-r--r--chained-ci-vue/style.css219
-rw-r--r--pod_config/config/artifacts/azure.zipbin0 -> 1132 bytes
-rw-r--r--pod_config/config/az14-gating3.yaml168
-rw-r--r--pod_config/config/idf-az14-gating3.yaml171
-rw-r--r--pod_inventory/group_vars/all.yml194
-rw-r--r--pod_inventory/host_vars/onap_oom_gating_azure_3.yml85
-rw-r--r--pod_inventory/inventory4
-rw-r--r--roles/LICENSE201
-rw-r--r--roles/README.md15
-rw-r--r--roles/artifact_init/defaults/main.yaml2
-rw-r--r--roles/artifact_init/filter_plugins/filters.py8
-rw-r--r--roles/artifact_init/tasks/main.yml180
-rw-r--r--roles/get_artifacts/defaults/main.yml7
-rw-r--r--roles/get_artifacts/filter_plugins/filters.py8
-rw-r--r--roles/get_artifacts/tasks/binary.yml244
-rw-r--r--roles/get_artifacts/tasks/get_one_artifact.yml49
-rw-r--r--roles/get_artifacts/tasks/job_id_fetch.yml20
-rw-r--r--roles/get_artifacts/tasks/limit_to.yml20
-rw-r--r--roles/get_artifacts/tasks/main.yml34
-rw-r--r--roles/get_artifacts/tasks/url.yml13
-rw-r--r--roles/gitlab-ci-generator/defaults/main.yml3
-rw-r--r--roles/gitlab-ci-generator/tasks/main.yml45
-rw-r--r--roles/gitlab-ci-generator/templates/gitlab-ci.yml204
-rw-r--r--roles/library/filepath.py40
-rw-r--r--roles/logo.pngbin0 -> 4723 bytes
-rw-r--r--roles/logo.svg130
-rw-r--r--roles/prepare/README.md28
-rw-r--r--roles/prepare/tasks/continue.yml15
-rw-r--r--roles/prepare/tasks/except.yml55
-rw-r--r--roles/prepare/tasks/exit.yml13
-rw-r--r--roles/prepare/tasks/main.yml93
-rw-r--r--roles/prepare/tasks/only.yml57
-rw-r--r--roles/run-ci/tasks/grafana_start.yml42
-rw-r--r--roles/run-ci/tasks/grafana_stop.yml53
-rw-r--r--roles/run-ci/tasks/main.yml270
-rw-r--r--roles/trigger_myself/tasks/main.yml75
50 files changed, 4563 insertions, 113 deletions
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: <SET ME>
################################################################################
# Shared parameters
@@ -49,8 +48,7 @@ variables:
expire_in: 1 yrs
.runner_env: &runner_env
- CHAINED_CI_SRC: "<SET ME>" # 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
--- /dev/null
+++ b/chained-ci-vue/favicon.png
Binary files 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 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+
+ <title>Pipelines</title>
+
+ <!-- VUE JS development version, includes helpful console warnings -->
+ <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
+
+ <!-- My scripts -->
+ <script src="js/config.js"></script>
+ <script src="js/lib.js"></script>
+
+ <!-- Visisbilityjs -->
+ <script src="js/visibility.core.js"></script>
+ <script src="js/visibility.timers.js"></script>
+
+ <!-- CSS -->
+ <link rel="icon" href="favicon.png">
+ <link rel="stylesheet" href="style.css">
+ <link rel="stylesheet" href="https://www.w3schools.com/w3css/4/w3.css">
+ <link rel="stylesheet" href="https://www.w3schools.com/lib/w3-theme-blue-grey.css">
+ <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.5.0/css/all.css" integrity="sha384-B4dIYHKNBt8Bc12p+WXckhzcICo0wtJAoU8YZTY5qE0Id1GSseTk6S+L3BlXeVIU" crossorigin="anonymous">
+ </head>
+
+ <body>
+ <div class="main">
+ <header class="w3-container w3-theme w3-card" id="header">
+ <h1 v-on:click="update($event)"
+ class='header w3-center'>{{ project.name }} UI</h1>
+ </header>
+
+ <script type="text/x-template" id="modal-template">
+ <transition name="modal">
+ <div class="modal-mask" v-on:click="closeModal($event, $emit)">
+ <div class="modal-wrapper">
+ <div class="modal-container">
+ <div class="modal-header"><slot name="header"></slot></div>
+ <div class="modal-body"><slot name="body"></slot></div>
+ <div class="modal-footer">
+ <slot name="footer">
+ </slot>
+ </div>
+ </div>
+ </div>
+ </div>
+ </transition>
+ </script>
+
+ <section id="auth">
+ <div class="w3-card-4" v-if="!globalAccessGranted">
+ <div class="w3-container">
+ <h2>Please set your gitlab<span v-if="privateTokens.length > 1">s</span>
+ private token<span v-if="privateTokens.length > 1">s</span>
+ </h2>
+ </div>
+ <form @submit="checkForm" class="w3-container">
+ <div v-for="token in privateTokens">
+ <label>
+ <a v-bind:href="'https://'+token.target+gitlabProfileToken">{{ token.target }}</a></label>
+ <label v-if="token.msg">[ {{ token.msg }} ]</label>
+ <div class="w3-xlarge statusIcon"
+ v-bind:class="[ token.icon ]"></div>
+ <input
+ v-model="token.value"
+ class="w3-input"
+ v-bind:id="token.target"
+ type="password">
+ </input>
+ </div>
+ <button type="submit" class="w3-btn">Validate</button>
+ </form>
+ <div>
+ <div>
+ <div>this is required and can be generated on your user profile like:
+ <a v-bind="{ href: gitlabProfileToken }">{{gitlabProfileToken}}</a>
+ (Only API option is needed)</div>
+ </div>
+ </div>
+ </div>
+ </section>
+
+ <section class="w3-ul w3-border-top" id="pipelines">
+ <div v-if="accessGranted">
+ <div class="tools w3-theme-l5">
+ <div class='tool_sc w3-theme-l4 w3-opacity'>
+ <b>Scenario filter:</b>
+ <input v-model="pipelineFilter" placeholder="filter">
+ </div>
+ <div class='tool_timer w3-theme-l4 w3-opacity'>
+ <b>Next update:&nbsp;</b>{{ timer }}&nbsp;/&nbsp;{{ actualRefresh }}
+ <i v-if="! optimizedRefresh">
+ (Please set filter or optimize it to have a better update time)
+ </i>
+ </div>
+ <div class='tool_new w3-theme-l4'>
+ <a v-bind="{ href: newPipelineUrl }" target='_blank'>
+ <div class='fab fa-gitlab w3-text-orange w3-large w3-statusIcon'></div>
+ New pipeline
+ </a>
+ </div>
+ </div>
+ <div v-for="id in sortedPipelinesIds">
+ <div class='pipeline w3-theme-l5'>
+ <div class='pipeline_header w3-center w3-theme-l4 w3-display-container '>
+ <a v-bind="{ href: pipelines[id].url }" target='_blank'>
+ <div class='pipeline_statusIcon w3-xxlarge statusIcon w3-padding w3-display-middle'
+ v-bind:class="[ pipelines[id].statusIcon ]"></div></a>
+ <div class='pipeline_scenario w3-opacity'>{{ pipelines[id].scenario }}</div>
+ <div class='pipeline_branch'>{{ pipelines[id].branch }}</div>
+ <div class='pipeline_date'>{{ pipelines[id].date }}</div>
+ <div class='pipeline_time'>{{ pipelines[id].time }}</div>
+ <div class='pipeline_duration'>{{ Math.round(pipelines[id].details.duration/60) }} min</div>
+ <div class='pipeline_user_icon w3-padding'>
+ <img v-bind="{ src:pipelines[id].userAvatar, alt:pipelines[id].user}"></img>
+ </div>
+ </div>
+ <div class='pipeline_stages w3-theme-l4'>
+ <div v-for="stage in pipelines[id].stages">
+ <div class='stage'>
+ <div class='stage_name w3-opacity'>{{ stage.name }}</div>
+ <div v-for='job in stage.jobs'>
+ <div class='w3-round w3-theme-l5 w3-btn w3-padding-small w3-block'>
+ <div v-if="!job.internal">
+ <div class='job'>
+ <div class='job_statusIcon w3-large statusIcon'
+ v-bind:class="[ job.statusIcon ]"
+ @mouseover="mouseOverJob(job)"
+ @mouseleave="mouseLeaveJob(job)"
+ v-on:click="jobAction(job)"
+ ></div>
+ <div class='job_name'
+ v-on:click="jobDetails($event, job)">
+ {{ job.shortname }}</div>
+ </div>
+ </div>
+ <div v-if="job.internal">
+ <div class='job'>
+ <div class='job_statusIcon w3-large statusIcon'
+ v-bind:class="[ job.statusIcon ]"
+ @mouseover="mouseOverJob(job)"
+ @mouseleave="mouseLeaveJob(job)"
+ v-on:click="jobAction(job)"></div>
+ <div class='job_name'>
+ <a v-bind="{ href: job.web_url }" target='_blank'>
+ {{ job.shortname }}</a>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div>
+ <div class='pipeline w3-theme-l5'>
+ <div class='pipeline_loader w3-theme-l2 w3-center w3-opacity'
+ v-on:click="loadMore()">
+ Load more pipelines
+ </div>
+ </div>
+ </div>
+ </div>
+ </section>
+
+
+
+ <section id='alert'>
+ <div class='masq'>
+ <modal v-if="showModal" @close="showModal = false">
+ <h3 slot="header">
+ {{ title }}
+ </h3>
+ <div slot="body">
+ {{ message }}
+ </div>
+ </modal>
+ </div>
+ </section>
+
+
+ <section id='task_details'>
+ <div class='masq'>
+ <modal v-if="showModal" @close="showModal = false">
+ <h3 slot="header">
+ <div class='job_statusIcon w3-large statusIcon'
+ v-bind:class="[ pipeline.statusIcon ]"
+ @mouseover="mouseOverPipeline(pipeline)"
+ @mouseleave="mouseLeavePipeline(pipeline)"
+ v-on:click="jobAction(pipeline)"></div>
+ <a v-bind="{ href: pipeline.url }" target='_blank'>
+ Pipeline {{ pipeline.name }} {{ pipeline.id }}
+ </a>
+ <a v-bind="{ href: pipeline.console }" target='_blank'>
+ <div class='fa fa-terminal w3-theme-l2 w3-large w3-statusIcon'></div>
+ </a>
+
+ </h3>
+ <div slot="body">
+ <div v-if="showWaiting">
+ <div class='w3-xxlarge fa fa-sync w3-text-blue-gray statusIcon'>Loading, please wait...</div>
+ </div>
+ <div v-if="showPipeline">
+ <div v-for="stage in pipeline.stages">
+ <div class='stage'>
+ <div class='stage_name w3-opacity'>{{ stage.name }}</div>
+ <div v-for='job in stage.jobs'>
+ <div class='w3-round w3-theme-l5 w3-btn w3-block'>
+ <a v-bind="{ href: job.web_url }" target='_blank'>
+ <div class='job'>
+ <div class='job_statusIcon w3-large statusIcon'
+ v-bind:class="[ job.statusIcon ]"></div>
+ <div class='job_name'>{{ job.name }}</div>
+ </div>
+ </a>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div v-if="chainedCiFailure">
+ <div>The pipeline was probably not triggered, check console:
+ <a v-bind="{ href: pipeline.console }" target='_blank'>
+ <div class='fa fa-terminal w3-theme-l2 w3-large w3-statusIcon'></div>
+ </a>
+ </div>
+ </div>
+ </div>
+ </modal>
+ </div>
+ </section>
+ </div>
+ <script src="js/index.js"></script>
+ </body>
+</html>
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 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="19.586046mm"
+ height="19.586046mm"
+ viewBox="0 0 19.586046 19.586046"
+ version="1.1"
+ id="svg8"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)"
+ sodipodi:docname="logo.svg"
+ inkscape:export-filename="/home/edby8475/Dev/chained-ci-vue/logo.png"
+ inkscape:export-xdpi="98"
+ inkscape:export-ydpi="98">
+ <defs
+ id="defs2" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="6.5333333"
+ inkscape:cx="7.8188803"
+ inkscape:cy="30.617961"
+ inkscape:document-units="mm"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:window-width="2560"
+ inkscape:window-height="1403"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0" />
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Calque 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-76.099242,-143.22085)">
+ <circle
+ style="opacity:1;vector-effect:none;fill:#abc837;fill-opacity:1;stroke:#4d4d4d;stroke-width:0.50260705;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path921"
+ cx="85.892265"
+ cy="153.01387"
+ r="9.5417194" />
+ <g
+ id="g933">
+ <path
+ sodipodi:nodetypes="cccc"
+ inkscape:connector-curvature="0"
+ id="path919"
+ d="m 87.755086,149.65335 h -3.722685 c 3.722685,0 0.03786,6.84943 3.737003,6.84401 v 0"
+ style="fill:none;stroke:#f2f2f2;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(-2.1166667)"
+ id="g862">
+ <circle
+ style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#44aa00;stroke-width:0.5291667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path848"
+ cx="83.371094"
+ cy="149.57428"
+ r="2.7487407" />
+ <path
+ style="fill:none;stroke:#44aa00;stroke-width:0.62900001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 82.386279,149.28284 0.715902,0.81612 1.267143,-1.24567"
+ id="path850"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="translate(-0.03559777,6.879167)"
+ id="g858">
+ <circle
+ r="2.7487407"
+ cy="149.57428"
+ cx="90.565697"
+ id="circle852"
+ style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#2a7fff;stroke-width:0.5291667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ style="opacity:1;vector-effect:none;fill:#2a7fff;fill-opacity:1;stroke:none;stroke-width:0.36824697;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="circle854"
+ sodipodi:type="arc"
+ sodipodi:cx="90.415558"
+ sodipodi:cy="149.57428"
+ sodipodi:rx="1.9128479"
+ sodipodi:ry="1.9128479"
+ sodipodi:start="4.7106229"
+ sodipodi:end="2.5736704"
+ d="m 90.41218,147.66143 a 1.9128479,1.9128479 0 0 1 1.881627,1.55068 1.9128479,1.9128479 0 0 1 -1.17015,2.13913 1.9128479,1.9128479 0 0 1 -2.320669,-0.74807 l 1.61257,-1.02889 z" />
+ </g>
+ <g
+ transform="translate(7.1590052)"
+ id="g868">
+ <circle
+ r="2.7487407"
+ cy="149.57428"
+ cx="83.371094"
+ id="circle864"
+ style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#44aa00;stroke-width:0.5291667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path866"
+ d="m 82.386279,149.28284 0.715902,0.81612 1.267143,-1.24567"
+ style="fill:none;stroke:#44aa00;stroke-width:0.62900001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ </g>
+ </g>
+</svg>
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
--- /dev/null
+++ b/pod_config/config/artifacts/azure.zip
Binary files 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
--- /dev/null
+++ b/roles/logo.png
Binary files 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 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="19.586046mm"
+ height="19.586046mm"
+ viewBox="0 0 19.586046 19.586046"
+ version="1.1"
+ id="svg8"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)"
+ sodipodi:docname="logo.svg"
+ inkscape:export-filename="/home/edby8475/Dev/chained-ci-roles/logo.png"
+ inkscape:export-xdpi="98"
+ inkscape:export-ydpi="98">
+ <defs
+ id="defs2" />
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="6.5333333"
+ inkscape:cx="7.8188803"
+ inkscape:cy="30.617961"
+ inkscape:document-units="mm"
+ inkscape:current-layer="g933"
+ showgrid="false"
+ inkscape:window-width="2560"
+ inkscape:window-height="1403"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ fit-margin-top="0"
+ fit-margin-left="0"
+ fit-margin-right="0"
+ fit-margin-bottom="0" />
+ <metadata
+ id="metadata5">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Calque 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(-76.099242,-143.22085)">
+ <circle
+ style="opacity:1;vector-effect:none;fill:#4d4d4d;fill-opacity:1;stroke:#4d4d4d;stroke-width:0.50260705;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path921"
+ cx="85.892265"
+ cy="153.01387"
+ r="9.5417194" />
+ <g
+ id="g933">
+ <path
+ sodipodi:nodetypes="cccc"
+ inkscape:connector-curvature="0"
+ id="path919"
+ d="m 87.755086,149.65335 h -3.722685 c 3.722685,0 0.03786,6.84943 3.737003,6.84401 v 0"
+ style="fill:none;stroke:#f2f2f2;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <g
+ transform="translate(-2.1166667)"
+ id="g862">
+ <circle
+ style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#44aa00;stroke-width:0.5291667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path848"
+ cx="83.371094"
+ cy="149.57428"
+ r="2.7487407" />
+ <path
+ style="fill:none;stroke:#44aa00;stroke-width:0.62900001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 82.386279,149.28284 0.715902,0.81612 1.267143,-1.24567"
+ id="path850"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ transform="translate(-0.03559777,6.879167)"
+ id="g858">
+ <circle
+ r="2.7487407"
+ cy="149.57428"
+ cx="90.565697"
+ id="circle852"
+ style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#2a7fff;stroke-width:0.5291667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ style="opacity:1;vector-effect:none;fill:#2a7fff;fill-opacity:1;stroke:none;stroke-width:0.36824697;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="circle854"
+ sodipodi:type="arc"
+ sodipodi:cx="90.415558"
+ sodipodi:cy="149.57428"
+ sodipodi:rx="1.9128479"
+ sodipodi:ry="1.9128479"
+ sodipodi:start="4.7106229"
+ sodipodi:end="2.5736704"
+ d="m 90.41218,147.66143 a 1.9128479,1.9128479 0 0 1 1.881627,1.55068 1.9128479,1.9128479 0 0 1 -1.17015,2.13913 1.9128479,1.9128479 0 0 1 -2.320669,-0.74807 l 1.61257,-1.02889 z" />
+ </g>
+ <g
+ transform="translate(7.1590052)"
+ id="g868">
+ <circle
+ r="2.7487407"
+ cy="149.57428"
+ cx="83.371094"
+ id="circle864"
+ style="opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;stroke:#44aa00;stroke-width:0.5291667;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ inkscape:connector-curvature="0"
+ id="path866"
+ d="m 82.386279,149.28284 0.715902,0.81612 1.267143,-1.24567"
+ style="fill:none;stroke:#44aa00;stroke-width:0.62900001;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ </g>
+ </g>
+</svg>
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:
+ "<a href=\"{{ pipeline_url }}\">{{ step }}</a> 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 + '<br/>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 + '<br/>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: "<a href={{ pipeline_url }}>{{ step }}</a> 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: "<a href={{ pipeline_url }}>{{ step }}</a> 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 }}
+ ******************************************************************