aboutsummaryrefslogtreecommitdiffstats
path: root/chained-ci-vue/js/lib.js
diff options
context:
space:
mode:
Diffstat (limited to 'chained-ci-vue/js/lib.js')
-rw-r--r--chained-ci-vue/js/lib.js558
1 files changed, 558 insertions, 0 deletions
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;
+ }
+}