Skip to content

Commit

Permalink
Add support for Bitbucket native uploads (#943)
Browse files Browse the repository at this point in the history
* Bitbucket native uploads (#930)

* Update bitbucket_cloud.js

* Make it work

* Fix error logic

* Better yet

* Apply suggestions from code review

Co-authored-by: DavidGOrtega <[email protected]>

* Add tests 🙈

Co-authored-by: DavidGOrtega <[email protected]>
  • Loading branch information
0x2b3bfa0 and DavidGOrtega authored Apr 10, 2022
1 parent b52e337 commit fdfcb1b
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 21 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ module.exports = {
SharedArrayBuffer: 'readonly'
},
parserOptions: {
ecmaVersion: 2018
ecmaVersion: 2020
},
ignorePatterns: ['assets/', 'dist/', 'node_modules/'],
rules: {
Expand Down
61 changes: 44 additions & 17 deletions src/drivers/bitbucket_cloud.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
const crypto = require('crypto');
const fetch = require('node-fetch');
const winston = require('winston');
const { URL } = require('url');
const FormData = require('form-data');
const ProxyAgent = require('proxy-agent');

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

const { BITBUCKET_COMMIT, BITBUCKET_BRANCH, BITBUCKET_PIPELINE_UUID } =
process.env;

class BitbucketCloud {
constructor(opts = {}) {
const { repo, token } = opts;
Expand Down Expand Up @@ -87,7 +92,29 @@ class BitbucketCloud {
}

async upload(opts = {}) {
throw new Error('Bitbucket Cloud does not support upload!');
const { projectPath } = this;
const { size, mime, data } = await fetchUploadData(opts);

const chunks = [];
for await (const chunk of data) chunks.push(chunk);
const buffer = Buffer.concat(chunks);

const filename = `cml-${crypto
.createHash('sha256')
.update(buffer)
.digest('hex')}`;
const body = new FormData();
body.append('files', buffer, { filename });

const endpoint = `/repositories/${projectPath}/downloads`;
await this.request({ endpoint, method: 'POST', body });
return {
uri: `https://bitbucket.org/${decodeURIComponent(
projectPath
)}/downloads/${filename}`,
mime,
size
};
}

async runnerToken() {
Expand Down Expand Up @@ -329,10 +356,10 @@ class BitbucketCloud {

if (!(url || endpoint))
throw new Error('Bitbucket Cloud API endpoint not found');
const headers = {
'Content-Type': 'application/json',
Authorization: 'Basic ' + `${token}`
};

const headers = { Authorization: `Basic ${token}` };
if (body.constructor !== FormData)
headers['Content-Type'] = 'application/json';

const requestUrl = url || `${api}${endpoint}`;
winston.debug(`${method} ${requestUrl}`);
Expand All @@ -344,20 +371,20 @@ class BitbucketCloud {
agent: new ProxyAgent()
});

if (response.status > 300) {
try {
const json = await response.json();
winston.debug(json);
// Attempt to get additional context. We have observed two different error schemas
// from BitBucket API responses: `{"error": {"message": "Error message"}}` and
// `{"error": "Error message"}`.
throw new Error(json.error.message || json.error);
} catch (err) {
throw new Error(`${response.statusText} ${err.message}`);
}
const responseBody = response.headers.get('Content-Type').includes('json')
? await response.json()
: await response.text();

if (!response.ok) {
// Attempt to get additional context. We have observed two different error schemas
// from BitBucket API responses: `{"error": {"message": "Error message"}}` and
// `{"error": "Error message"}`, apart from plain text responses like `Bad Request`.
const error =
responseBody?.error?.message || responseBody?.error || responseBody;
throw new Error(`${response.statusText} ${error}`.trim());
}

return await response.json();
return responseBody;
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/drivers/bitbucket_cloud.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ describe('Non Enviromental tests', () => {

test('Publish', async () => {
const path = `${__dirname}/../../assets/logo.png`;
await expect(client.upload({ path })).rejects.toThrow(
'Bitbucket Cloud does not support upload!'
);
const { uri } = await client.upload({ path });

expect(uri).not.toBeUndefined();
});

test('Runner token', async () => {
Expand Down

3 comments on commit fdfcb1b

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test Comment

CML watermark

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test Comment

CML watermark

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test Comment

CML watermark

Please sign in to comment.