gitlab_service.js 8.2 KB
Newer Older
1
const vscode = require('vscode');
2
const request = require('request-promise');
P
Pierre Carru 已提交
3
const fs = require('fs');
4
const gitService = require('./git_service');
5
const openers = require('./openers');
6
const tokenService = require('./token_service');
F
Fatih Acet 已提交
7
const statusBar = require('./status_bar');
F
Fatih Acet 已提交
8

9
let version = null;
F
Fatih Acet 已提交
10
let branchMR = null;
11

F
Fatih Acet 已提交
12
async function fetch(path, method = 'GET', data = null) {
13
  const { instanceUrl, ignoreCertificateErrors, ca } = vscode.workspace.getConfiguration('gitlab');
14
  const { proxy } = vscode.workspace.getConfiguration('http');
15
  const apiRoot = `${instanceUrl}/api/v4`;
16
  const glToken = tokenService.getToken(instanceUrl);
17

F
Fatih Acet 已提交
18
  if (!glToken) {
F
Fatih Acet 已提交
19 20 21
    return vscode.window.showInformationMessage(
      'GitLab Workflow: Cannot make request. No token found.',
    );
F
Fatih Acet 已提交
22 23
  }

24 25
  const config = {
    url: `${apiRoot}${path}`,
F
Fatih Acet 已提交
26
    method,
27
    headers: {
F
Fatih Acet 已提交
28
      'PRIVATE-TOKEN': glToken,
F
Fatih Acet 已提交
29
    },
30
    rejectUnauthorized: !ignoreCertificateErrors,
31 32
  };

33
  if (proxy) {
34 35 36
    config.proxy = proxy;
  }

P
Pierre Carru 已提交
37 38 39 40 41 42 43 44
  if (ca) {
    try {
      config.ca = fs.readFileSync(ca);
    } catch (e) {
      vscode.window.showErrorMessage(`GitLab Workflow: Cannot read CA '${ca}'`);
    }
  }

F
Fatih Acet 已提交
45 46 47 48
  if (data) {
    config.formData = data;
  }

49 50 51 52 53
  const response = await request(config);

  try {
    return JSON.parse(response);
  } catch (e) {
54
    vscode.window.showInformationMessage('GitLab Workflow: Failed to perform your operation.');
F
Fatih Acet 已提交
55
    console.log('Failed to execute fetch', e);
56 57 58 59
    return { error: e };
  }
}

60
async function fetchProjectData(remote) {
F
Fatih Acet 已提交
61 62 63 64 65 66 67 68 69 70
  if (remote) {
    const { namespace, project } = remote;
    const projectData = await fetch(`/projects/${namespace.replace(/\//g, '%2F')}%2F${project}`);

    return projectData || null;
  }

  return null;
}

71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
async function fetchCurrentProject() {
  try {
    const remote = await gitService.fetchGitRemote();

    return await fetchProjectData(remote);
  } catch (e) {
    console.log('Failed to execute fetch', e);

    return null;
  }
}

async function fetchCurrentPipelineProject() {
  try {
    const remote = await gitService.fetchGitRemotePipeline();

    return await fetchProjectData(remote);
  } catch (e) {
    console.log('Failed to execute fetch', e);

    return null;
  }
}

F
Fatih Acet 已提交
95
async function fetchUser(userName) {
F
Fatih Acet 已提交
96 97
  let user = null;

98
  try {
F
Fatih Acet 已提交
99
    const path = userName ? `/user?search=${userName}` : '/user';
F
Fatih Acet 已提交
100

F
Fatih Acet 已提交
101
    user = await fetch(path);
102
  } catch (e) {
F
Fatih Acet 已提交
103
    let message = 'GitLab Workflow: GitLab user not found.';
F
Fatih Acet 已提交
104 105 106 107 108 109

    if (!userName) {
      message += ' Check your Personal Access Token.';
    }

    vscode.window.showInformationMessage(message);
110
  }
F
Fatih Acet 已提交
111 112

  return user;
113 114
}

F
Fatih Acet 已提交
115
async function fetchIssuables(params = {}) {
116 117
  let project = null;
  let issuables = [];
118

F
Fatih Acet 已提交
119 120 121
  const { type, scope, state } = params;
  const config = {
    type: type || 'merge_requests',
122
    scope: scope || 'created_by_me',
F
Fatih Acet 已提交
123 124
    state: state || 'opened',
  }
125 126 127

  try {
    project = await fetchCurrentProject();
128 129 130 131

    if (!version) {
      version = await fetchVersion();
    }
132 133 134
  } catch (e) {
    // Fail silently
  }
F
Fatih Acet 已提交
135 136

  if (project) {
137 138 139 140 141 142
    // Normalize scope parameter for version < 11 instances.
    const [ major ] = version.split('.');
    if (parseInt(major, 10) < 11) {
      config.scope = config.scope.replace(/_/g, '-');
    }

F
Fatih Acet 已提交
143 144
    const path = `/projects/${project.id}/${config.type}?scope=${config.scope}&state=${config.state}`;
    issuables = await fetch(path);
F
Fatih Acet 已提交
145 146
  }

F
Fatih Acet 已提交
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
  return issuables;
}

async function fetchIssuesAssignedToMe() {
  return await fetchIssuables({
    type: 'issues',
    scope: 'assigned_to_me',
  });
}

async function fetchIssuesCreatedByMe() {
  return await fetchIssuables({
    type: 'issues',
    scope: 'created_by_me',
  });
}

async function fetchMergeRequestsAssignedToMe() {
  return await fetchIssuables({
166
    scope: 'assigned_to_me',
F
Fatih Acet 已提交
167 168 169 170 171 172 173 174 175
  });
}

async function fetchMergeRequestsCreatedByMe() {
  return await fetchIssuables();
}

async function fetchMyOpenMergeRequests() {
  return await fetchIssuables();
176 177 178
}

async function fetchLastPipelineForCurrentBranch() {
179
  const project = await fetchCurrentPipelineProject();
F
Fatih Acet 已提交
180
  let pipeline = null;
181

F
Fatih Acet 已提交
182 183 184 185
  if (project) {
    const branchName = await gitService.fetchTrackingBranchName();
    const pipelinesRootPath = `/projects/${project.id}/pipelines`;
    const pipelines = await fetch(`${pipelinesRootPath}?ref=${branchName}`);
186 187

    if (pipelines.length) {
F
Fatih Acet 已提交
188
      pipeline = await fetch(`${pipelinesRootPath}/${pipelines[0].id}`);
189
    }
F
Fatih Acet 已提交
190 191
  }

F
Fatih Acet 已提交
192
  return pipeline;
F
Fatih Acet 已提交
193 194
}

F
Fatih Acet 已提交
195 196 197 198 199
/**
 * GitLab API doesn't support getting open MR by commit ID or branch name.
 * Using this recursive fetcher method, we fetch 100 MRs at a time and do pagination
 * until we find the MR for current branch. This method will retry max 5 times.
 */
F
Fatih Acet 已提交
200
async function fetchOpenMergeRequestForCurrentBranch() {
F
Fatih Acet 已提交
201 202 203 204
  if (branchMR) {
    return branchMR;
  }

F
Fatih Acet 已提交
205 206 207 208
  const project = await fetchCurrentProject();
  const branchName = await gitService.fetchTrackingBranchName();
  let page = 1;

F
Fatih Acet 已提交
209
  // Recursive fetcher method to find the branch MR in MR list.
F
Fatih Acet 已提交
210
  async function fetcher() {
F
Fatih Acet 已提交
211 212 213
    const path = `/projects/${project.id}/merge_requests?state=opened&per_page=100&page=${page}`;
    const mrs = await fetch(path);
    const [mr] = mrs.filter(m => m.source_branch === branchName);
F
Fatih Acet 已提交
214 215

    if (mr) {
F
Fatih Acet 已提交
216 217
      if (page > 1) {
        // Cache only if we need to do pagination.
F
Fatih Acet 已提交
218 219 220
        branchMR = mr;
      }

F
Fatih Acet 已提交
221 222 223
      return mr;
    }

F
Fatih Acet 已提交
224 225 226
    if (page <= 5 && mrs.length === 100) {
      // Retry max 5 times.
      page += 1;
F
Fatih Acet 已提交
227 228
      return await fetcher();
    }
F
Fatih Acet 已提交
229 230

    return null;
F
Fatih Acet 已提交
231 232 233 234 235
  }

  return project ? await fetcher() : null;
}

F
Fatih Acet 已提交
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
/**
 * Cancels or retries last pipeline or creates a new pipeline for current branch.
 *
 * @param {string} action create|retry|cancel
 */
async function handlePipelineAction(action) {
  const pipeline = await fetchLastPipelineForCurrentBranch();
  const project = await fetchCurrentProject();

  if (pipeline && project) {
    let endpoint = `/projects/${project.id}/pipelines/${pipeline.id}/${action}`;
    let newPipeline = null;

    if (action === 'create') {
      const branchName = await gitService.fetchTrackingBranchName();
      endpoint = `/projects/${project.id}/pipeline?ref=${branchName}`;
    }

    try {
      newPipeline = await fetch(endpoint, 'POST');
    } catch (e) {
      vscode.window.showErrorMessage(`GitLab Workflow: Failed to ${action} pipeline.`);
    }

    if (newPipeline) {
F
Fatih Acet 已提交
261
      statusBar.refreshPipeline();
F
Fatih Acet 已提交
262 263 264 265 266 267
    }
  } else {
    vscode.window.showErrorMessage('GitLab Workflow: No project or pipeline found.');
  }
}

F
Fatih Acet 已提交
268 269 270 271 272 273 274 275
async function fetchMRIssues(mrId) {
  const project = await fetchCurrentProject();
  let issues = [];

  if (project) {
    try {
      issues = await fetch(`/projects/${project.id}/merge_requests/${mrId}/closes_issues`);
    } catch (e) {
F
Fatih Acet 已提交
276
      console.log('Failed to execute fetchMRIssue', e);
F
Fatih Acet 已提交
277 278 279 280
    }
  }

  return issues;
F
Fatih Acet 已提交
281
}
F
Fatih Acet 已提交
282

F
Fatih Acet 已提交
283 284 285 286 287 288 289 290 291 292
async function createSnippet(data) {
  let snippet;

  try {
    snippet = await fetch(`/projects/${data.id}/snippets`, 'POST', data);
  } catch (e) {
    vscode.window.showInformationMessage('GitLab Workflow: Failed to create your snippet.');
  }

  return snippet;
F
Fatih Acet 已提交
293
}
F
Fatih Acet 已提交
294

F
Fatih Acet 已提交
295 296 297 298 299 300 301 302 303 304 305 306
async function validateCIConfig(content) {
  let response = null;

  try {
    response = await fetch('/ci/lint', 'POST', { content });
  } catch (e) {
    vscode.window.showInformationMessage('GitLab Workflow: Failed to validate CI configuration.');
  }

  return response;
}

307 308 309 310 311 312 313 314 315
async function fetchVersion() {
  try {
    const v = await fetch('/version');
    version = v.version;
  } catch (e) {}

  return version;
}

316
exports.fetchUser = fetchUser;
F
Fatih Acet 已提交
317 318 319 320
exports.fetchIssuesAssignedToMe = fetchIssuesAssignedToMe;
exports.fetchIssuesCreatedByMe = fetchIssuesCreatedByMe;
exports.fetchMergeRequestsAssignedToMe = fetchMergeRequestsAssignedToMe;
exports.fetchMergeRequestsCreatedByMe = fetchMergeRequestsCreatedByMe;
321 322 323
exports.fetchMyOpenMergeRequests = fetchMyOpenMergeRequests;
exports.fetchOpenMergeRequestForCurrentBranch = fetchOpenMergeRequestForCurrentBranch;
exports.fetchLastPipelineForCurrentBranch = fetchLastPipelineForCurrentBranch;
F
Fatih Acet 已提交
324
exports.fetchCurrentProject = fetchCurrentProject;
325
exports.fetchCurrentPipelineProject = fetchCurrentPipelineProject;
F
Fatih Acet 已提交
326
exports.handlePipelineAction = handlePipelineAction;
F
Fatih Acet 已提交
327
exports.fetchMRIssues = fetchMRIssues;
F
Fatih Acet 已提交
328
exports.createSnippet = createSnippet;
F
Fatih Acet 已提交
329
exports.validateCIConfig = validateCIConfig;
330
exports.fetchVersion = fetchVersion;