Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bitbucket runner support #798

Merged
merged 16 commits into from
May 24, 2022
22 changes: 16 additions & 6 deletions src/cml.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,6 @@ class CML {
log.job = '';
log.status = 'job_ended';
log.success = data.includes('Succeeded');
log.level = log.success ? 'info' : 'error';
DavidGOrtega marked this conversation as resolved.
Show resolved Hide resolved
} else if (data.includes('Listening for Jobs')) {
log.status = 'ready';
}
Expand All @@ -252,12 +251,27 @@ class CML {
) {
log.status = 'job_ended';
log.success = !msg.startsWith('Job failed');
log.level = log.success ? 'info' : 'error';
dacbd marked this conversation as resolved.
Show resolved Hide resolved
} else if (msg.includes('Starting runner for')) {
log.status = 'ready';
}
return log;
}

if (this.driver === BB) {
if (data.includes('runner state to executing step.')) {
log.status = 'job_started';
} else if (data.includes('Result{status=')) {
log.status = 'job_ended';
log.success = data.includes('status=PASSED');
} else if (
data.includes('Updating runner status to "ONLINE" and checking')
) {
log.status = 'ready';
}

log.level = log.success ? 'info' : 'error';
return log.status ? log : null;
}
} catch (err) {
winston.warn(`Failed parsing log: ${err.message}`);
winston.warn(`Original log bytes, as Base64: ${data.toString('base64')}`);
Expand All @@ -268,10 +282,6 @@ class CML {
return await getDriver(this).startRunner(opts);
}

async registerRunner(opts = {}) {
return await getDriver(this).registerRunner(opts);
}

async unregisterRunner(opts = {}) {
const { id: runnerId } = await this.runnerByName(opts);
return await getDriver(this).unregisterRunner({ runnerId, ...opts });
Expand Down
103 changes: 92 additions & 11 deletions src/drivers/bitbucket_cloud.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
const fetch = require('node-fetch');
const { URL } = require('url');
const { spawn } = require('child_process');
const ProxyAgent = require('proxy-agent');

const winston = require('winston');

const { exec } = require('../utils');

const { BITBUCKET_COMMIT, BITBUCKET_BRANCH } = process.env;
class BitbucketCloud {
constructor(opts = {}) {
Expand Down Expand Up @@ -97,19 +102,93 @@ class BitbucketCloud {
}

async runnerToken() {
throw new Error('Bitbucket Cloud does not support runnerToken!');
return '';
}

async startRunner(opts) {
const { projectPath } = this;
const { name, labels } = opts;

try {
const { uuid: accountId } = await this.request({ endpoint: `/user` });
const { uuid: repoId } = await this.request({
endpoint: `/repositories/${projectPath}`
});
const {
uuid,
oauth_client: { id, secret }
} = await this.registerRunner({ name, labels });
const command = `docker container run -t -a stderr -a stdout --rm \
-v /tmp:/tmp \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /var/lib/docker/containers:/var/lib/docker/containers:ro \
-e ACCOUNT_UUID=${accountId} \
-e REPOSITORY_UUID=${repoId} \
-e RUNNER_UUID=${uuid} \
-e OAUTH_CLIENT_ID=${id} \
-e OAUTH_CLIENT_SECRET=${secret} \
-e WORKING_DIRECTORY=/tmp \
--name ${name} \
docker-public.packages.atlassian.com/sox/atlassian/bitbucket-pipelines-runner:1`;
DavidGOrtega marked this conversation as resolved.
Show resolved Hide resolved

return spawn(command, { shell: true });
} catch (err) {
throw new Error(`Failed preparing runner: ${err.message}`);
}
}

async registerRunner(opts = {}) {
throw new Error('Bitbucket Cloud does not support registerRunner!');
const { projectPath } = this;
const { name, labels } = opts;

const endpoint = `/repositories/${projectPath}/pipelines-config/runners`;

return await this.request({
api: 'https://api.bitbucket.org/internal',
endpoint,
method: 'POST',
body: JSON.stringify({ labels: ['self.hosted'].concat(labels), name })
});
}

async unregisterRunner(opts = {}) {
throw new Error('Bitbucket Cloud does not support unregisterRunner!');
const { projectPath } = this;
const { runnerId, name } = opts;
const endpoint = `/repositories/${projectPath}/pipelines-config/runners/${runnerId}`;

try {
await this.request({
api: 'https://api.bitbucket.org/internal',
endpoint,
method: 'DELETE'
});
} catch (err) {
if (!err.message.includes('invalid json response body')) {
await exec(`docker stop ${name}`);
throw err;
}
winston.warn(`Deleting: ${err.message}`);
}

await exec(`docker stop ${name}`);
}

async runners(opts = {}) {
throw new Error('Bitbucket Cloud does not support runners!');
const { projectPath } = this;

const endpoint = `/repositories/${projectPath}/pipelines-config/runners`;
const runners = await this.paginatedRequest({
api: 'https://api.bitbucket.org/internal',
endpoint
});

return runners.map(({ uuid: id, name, labels, state: { status } }) => ({
id,
name,
labels,
online: status === 'ONLINE',
busy: status === 'ONLINE'
}));
}

async prCreate(opts = {}) {
Expand Down Expand Up @@ -219,12 +298,18 @@ class BitbucketCloud {
}

async pipelineRestart(opts = {}) {
throw new Error('BitBucket Cloud does not support workflowRestart!');
winston.warn('BitBucket Cloud does not support workflowRestart yet!');
}

async pipelineJobs(opts = {}) {
winston.warn('BitBucket Cloud does not support pipelineJobs yet!');

return [];
}

async request(opts = {}) {
const { token, api } = this;
const { url, endpoint, method = 'GET', body } = opts;
const { token, api: bbApi } = this;
const { url, endpoint, body, method = 'GET', api = bbApi } = opts;
if (!(url || endpoint))
throw new Error('Bitbucket Cloud API endpoint not found');
const headers = {
Expand All @@ -249,10 +334,6 @@ class BitbucketCloud {
return await response.json();
}

async pipelineJobs(opts = {}) {
throw new Error('Not implemented');
}

async paginatedRequest(opts = {}) {
const { method = 'GET', body } = opts;
const { next, values } = await this.request(opts);
Expand Down