Last active
July 2, 2023 04:47
-
-
Save taras/1ce5be01e0503763466c6cbe1809a23c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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