Bitbucket (self-hosted)
Port's Bitbucket (Self-Hosted) integration allows you to model Bitbucket Server / Bitbucket Data Center resources in your software catalog and ingest data into them.
Core support
Note: While Bitbucket Server has been deprecated and replaced by Bitbucket Data Center, they expose the same set of APIs and this integration covers both. For Bitbucket Cloud, see the Bitbucket Cloud integration.
Setup
Choose one of the following installation methods: Not sure which method is right for your use case? Check the available installation methods.
Configuration
Port integrations use a YAML mapping block to ingest data from the third-party api into Port.
The mapping makes use of the JQ JSON processor to select, modify, concatenate, transform and perform other operations on existing fields and values from the integration API.
Filtering data
The integration supports several selector properties to control which data is ingested from Bitbucket Server into Port.
Filtering projects
You can filter projects by providing a list of specific project keys using the projects selector. When set, only repositories belonging to the listed projects are ingested:
Filter by project keys (click to expand)
resources:
- kind: repository
selector:
query: "true"
projects:
- "PROJ"
- "TEAM-A"
port:
entity:
mappings:
identifier: .slug
title: .name
blueprint: '"bitbucketRepository"'
properties:
description: .description
state: .state
forkable: .forkable
public: .public
link: .links.self[0].href
relations:
project: .project.key
The projects selector is available for the project, repository, and pull-request kinds, and applies the same filtering to those resources.
Filtering projects using regex
You can also use the projectFilterRegex selector to filter projects by a regex pattern applied to project keys. This is useful when you want to match a set of projects by naming convention:
Filter by project regex (click to expand)
resources:
- kind: repository
selector:
query: "true"
projectFilterRegex: "^TEAM-.*"
port:
entity:
mappings:
identifier: .slug
title: .name
blueprint: '"bitbucketRepository"'
properties:
description: .description
state: .state
forkable: .forkable
public: .public
link: .links.self[0].href
relations:
project: .project.key
Common regex patterns include:
^TEAM-.*— matches projects whose keys start withTEAM-..*-PROD$— matches projects whose keys end with-PROD.^(PROJ|INFRA)$— matches the exact keysPROJorINFRA.
When both projects and projectFilterRegex are set on the same resource kind, both conditions must match (AND logic). A project will only be included if its key appears in the projects list and matches the projectFilterRegex pattern.
Filtering pull request state
For the pull-request kind, you can use the state selector to control which pull requests are ingested. Accepted values are ALL, OPEN, MERGED, and DECLINED. The default is OPEN:
Filter pull request state (click to expand)
resources:
- kind: pull-request
selector:
query: "true"
state: "ALL"
port:
entity:
mappings:
identifier: .id | tostring
title: .title
blueprint: '"bitbucketPullRequest"'
properties:
created_on: .createdDate | (tonumber / 1000 | strftime("%Y-%m-%dT%H:%M:%SZ"))
updated_on: .updatedDate | (tonumber / 1000 | strftime("%Y-%m-%dT%H:%M:%SZ"))
state: .state
owner: .author.user.emailAddress
link: .links.self[0].href
destination: .toRef.displayId
source: .fromRef.displayId
relations:
repository: .toRef.repository.slug
You can combine all the selectors together. For example, to sync only OPEN pull requests from projects matching a regex:
Combine selectors (click to expand)
resources:
- kind: pull-request
selector:
query: "true"
projectFilterRegex: "^TEAM-.*"
state: "OPEN"
port:
entity:
mappings:
identifier: .id | tostring
title: .title
blueprint: '"bitbucketPullRequest"'
properties:
created_on: .createdDate | (tonumber / 1000 | strftime("%Y-%m-%dT%H:%M:%SZ"))
updated_on: .updatedDate | (tonumber / 1000 | strftime("%Y-%m-%dT%H:%M:%SZ"))
state: .state
owner: .author.user.emailAddress
link: .links.self[0].href
destination: .toRef.displayId
source: .fromRef.displayId
relations:
repository: .toRef.repository.slug
Default mapping configuration
This is the default mapping configuration for this integration:
Default mapping configuration (click to expand)
createMissingRelatedEntities: true
deleteDependentEntities: true
resources:
- kind: project
selector:
query: "true"
port:
entity:
mappings:
identifier: .key
title: .name
blueprint: '"bitbucketProject"'
properties:
public: .public
type: .type
description: .description
link: .links.self[0].href
- kind: repository
selector:
query: "true"
port:
entity:
mappings:
identifier: .slug
title: .name
blueprint: '"bitbucketRepository"'
properties:
description: .description
state: .state
forkable: .forkable
public: .public
link: .links.self[0].href
documentation: .__readme
relations:
project: .project.key
latestCommitAuthor: .__latestCommit.author.emailAddress
- kind: pull-request
selector:
query: "true"
port:
entity:
mappings:
identifier: (.toRef.repository.slug // .toRef.repository.project.key) + "-" + (.id|tostring)
title: .title
blueprint: '"bitbucketPullRequest"'
properties:
created_on: .createdDate | (tonumber / 1000 | strftime("%Y-%m-%dT%H:%M:%SZ"))
updated_on: .updatedDate | (tonumber / 1000 | strftime("%Y-%m-%dT%H:%M:%SZ"))
merge_commit: .fromRef.latestCommit
state: .state
owner: .author.user.emailAddress
link: .links.self[0].href
destination: .toRef.displayId
source: .fromRef.displayId
mergedAt: .closedDate as $d | if $d == null then null else ($d / 1000 | strftime("%Y-%m-%dT%H:%M:%SZ")) end
reviewers: "[.reviewers[].user.emailAddress]"
relations:
repository: .toRef.repository.slug
participants: "[.participants[].user.emailAddress]"
- kind: user
selector:
query: "true"
port:
entity:
mappings:
identifier: .emailAddress
title: .displayName
blueprint: '"bitbucketUser"'
properties:
username: .name
url: .links.self[0].href
portUser: .emailAddress
Mapping & selectors per resource
To view and test the integration's mapping against examples of the third-party API responses, use the jq playground in your data sources page. Find the integration in the list of data sources and click on it to open the playground.
Set up live events
This integration supports live events, allowing real-time updates to your software catalog without waiting for the next scheduled sync.
Note that different events are supported depending on the resource type.
- Hosted by Port
- Self-hosted
Live events base URL
For integrations installed using the Hosted by Port method, you can obtain the live events base URL from the Get an integration API. The response includes liveEventsUuid and liveEventsIngestHostname in the appSpec section.
Call the endpoint with your integration's identifier:
curl -X GET \
'https://api.port.io/v1/integration/<INTEGRATION_IDENTIFIER>' \
-H 'Authorization: Bearer <PORT_ACCESS_TOKEN>'
The port_region, port.baseUrl, portBaseUrl, port_base_url and OCEAN__PORT__BASE_URL parameters select which Port API instance to use:
- EU (app.port.io) →
https://api.port.io - US (app.us.port.io) →
https://api.us.port.io
In the response, look for the following fields under spec.appSpec:
{
"spec": {
"appSpec": {
"liveEventsUuid": "abcdefghijk",
"liveEventsIngestHostname": "ingest.ocean.getport.io"
}
}
}
Combine them to build the live events base URL:
https://<liveEventsIngestHostname>/live-events/<liveEventsUuid>
For example, with the values above, the live events base URL is:
https://ingest.ocean.getport.io/live-events/abcdefghijk
This is the same URL configured for the integration.
BaseUrl & webhook configuration
To enable real-time updates of the data in your software catalog, you need to define the liveEvents.baseUrl parameter.
This parameter should be set to the URL of your Bitbucket (self-hosted) integration instance, which needs to have the option to setup webhooks via HTTP requests/receive HTTP requests, so ensure the network is configured accordingly.
- If Bitbucket (self-hosted) and the integration are in the same cluster/network: Use an internal URL (e.g., a Kubernetes service DNS name).
For Kubernetes deployments, create a service to expose the integration pod and use the service URL asliveEvents.baseUrl. If both the source system and integration are in the same cluster, an internal ClusterIP service is sufficient. - If Bitbucket (self-hosted) is external to the integration's network: The integration must be exposed via an ingress, load balancer, or public URL that Bitbucket (self-hosted) can reach.
If liveEvents.baseUrl is not provided, the integration will continue to function correctly. In such a configuration, to retrieve the latest information from the target system, the scheduledResyncInterval parameter has to be set, or a manual resync will need to be triggered through Port's UI.
To test webhooks or live event delivery to your local environment, expose your local pod or service to the internet using ngrok (e.g. ngrok http http://localhost:8000)
Securing Your Webhooks
The integration.secrets.bitbucketWebhookSecret parameter secures your webhooks. If not provided, the integration will process webhooks without validating the source of the events.
Monitoring and sync status
To learn more about how to monitor and check the sync status of your integration, see the relevant documentation.
GitOps
The Bitbucket (Self-Hosted) Ocean integration by itself does not support GitOps yet. This capability is planned for a future release and is WIP. If you really need GitOps support, you can use the webhook gitops installation method.