Skip to content

Instantly share code, notes, and snippets.

@taras
Last active July 2, 2023 04:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save taras/1ce5be01e0503763466c6cbe1809a23c to your computer and use it in GitHub Desktop.
Save taras/1ce5be01e0503763466c6cbe1809a23c to your computer and use it in GitHub Desktop.
import axios, { AxiosInstance } from 'axios';
import dotenv from 'dotenv';
import {
IncrementalEntityProvider,
EntityIteratorResult,
} from '@backstage/plugin-catalog-backend-module-incremental-ingestion';
dotenv.config();
interface BitbucketApiResponse {
values: BitbucketRepository[];
next: string;
}
interface BitbucketRepository {
name: string;
mainbranch: {
type: string;
name: string;
};
project: {
key: string;
};
language: string;
owner: {
username: string;
};
}
interface Cursor {
next: string;
}
interface Context {
bitbucketClient: AxiosInstance;
}
export class BitbucketIncrementalEntityProvider implements IncrementalEntityProvider<Cursor, Context> {
private bitbucketClient: AxiosInstance;
private bitbucketWorkspace: string;
constructor() {
// you can create this client in around and pass it to next - you don't need to store it on the instance
this.bitbucketClient = axios.create();
// you should read these from config instead of environment
this.bitbucketWorkspace = process.env.BITBUCKET_WORKSPACE || '';
this.initializeBitbucketClient();
}
private initializeBitbucketClient() {
const bitbucketUsername = process.env.BITBUCKET_USERNAME || '';
const bitbucketAppPassword = process.env.BITBUCKET_APP_PASSWORD || '';
const authHeader = `Basic ${Buffer.from(`${bitbucketUsername}:${bitbucketAppPassword}`).toString('base64')}`;
this.bitbucketClient.defaults.headers.common['Authorization'] = authHeader;
}
getProviderName(): string {
return 'BitbucketIncrementalEntityProvider';
}
async around(burst: (context: Context) => Promise<void>): Promise<void> {
const context: Context = {
bitbucketClient: this.bitbucketClient,
};
await burst(context);
}
async next(context: Context, cursor: Cursor = { next: '1' }): Promise<EntityIteratorResult<Cursor>> {
const url = `https://api.bitbucket.org/2.0/repositories/${this.bitbucketWorkspace}?page=${cursor.next}`;
try {
const res = await context.bitbucketClient.get<BitbucketApiResponse>(url);
console.log('Successfully retrieved Bitbucket repositories data!');
const { values, next } = res.data;
const done = !next;
const entities = values.map((repo) => {
const entityData = this.extractEntityData(repo);
const entity = {
entity: {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
name: entityData.name,
annotations: {
'backstage.io/managed-by-location': `url:https://bitbucket.org/${this.bitbucketWorkspace}/${entityData.name}/src/${entityData.defaultBranch}`,
'backstage.io/managed-by-origin-location': `url:https://bitbucket.org/${this.bitbucketWorkspace}/${entityData.name}/src/${entityData.defaultBranch}`,
'jira/project-key': entityData.jiraProjectKey,
},
tags: entityData.tags,
},
spec: {
owner: entityData.name,
system: entityData.owner.username,
lifecycle: 'experimental',
repository: {
type: 'bitbucket',
url: `https://bitbucket.org/${this.bitbucketWorkspace}/${entityData.name}.git`,
},
type: 'Component',
},
// this should be `url:https://bitbucket.org/${this.bitbucketWorkspace}/${entityData.name}/src/${entityData.defaultBranch}`
locationKey: `url:${this.getProviderName()}`,
},
};
console.log('Entity:', JSON.stringify(entity, null, 2)); // Print response in formatted JSON
return entity;
});
return {
entities,
cursor: { next },
done,
};
} catch (error) {
// you should allow backoff mechanism to handle rate limiting,
// to do that you need to check on type of error and throw if it's a rate limiting error
console.error(`Error fetching data from Bitbucket: ${error}`);
return {
done: true,
entities: [],
cursor,
};
}
}
private extractEntityData(repo: BitbucketRepository) {
const { name, mainbranch, project, language, owner } = repo;
return {
name,
defaultBranch: mainbranch.name,
jiraProjectKey: project.key,
tags: [language],
owner: owner.username,
};
}
}
async function runProvider() {
const provider = new BitbucketIncrementalEntityProvider();
await provider.around(async (context) => {
await provider.next(context);
});
}
runProvider();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment