Skip to main content

Check out Port for yourselfย 

GitHub migration guide

This guide will walk you through the process of migrating from Port's existing GitHub Cloud App to our new and improved GitHub Integration, which is powered by Ocean.

Improvementsโ€‹

The new Ocean-powered GitHub integration comes with several key improvements:

  • More authentication options - You can now connect the integration using a Personal Access Token (PAT) that you control, giving you more flexibility.
  • Enhanced performance - Faster resyncs thanks to improved API efficiency, making your data available sooner.
  • Better selectors - More granular control over what you sync with improved selectors for pull requests, issues, dependabot alerts, codescanning alerts, files, and folders.

Major changesโ€‹

Authentication modelโ€‹

Personal access token (PAT)โ€‹

You can now authenticate with our GitHub integration using a Personal Access Token (PAT) instead of a GitHub App. This gives you more control over the integration's permissions. For more details, see the installation page.

Below is a sample Helm value for this configuration:

integration:
secrets:
githubToken: "<GITHUB_PAT>"

GitHub Appโ€‹

If you prefer using a GitHub App, you can still authenticate with our Ocean-powered GitHub integration. You will need to create the app yourself, which is a process similar to our existing self-hosted app installation. This process is documented here.

Below is a sample Helm value for this configuration:

integration:
config:
githubAppId: "<GITHUB_APP_ID>" # app client id also works
secrets:
githubAppPrivateKey: "<BASE_64_ENCODED_PRIVATEKEY>"

Webhooksโ€‹

The integration now automatically configures webhooks on GitHub to receive live events. To enable this, you must grant your PAT permission to create organization webhooks. Additionally, you need to provide a public URL for the integration. You can do this by setting liveEvents.baseUrl when deploying with Helm or ArgoCD, or by setting the OCEAN__BASE_URL environment variable when running the integration as a Docker container. For more details, please refer to the live events documentation.

Deploymentโ€‹

We've expanded our self-hosted installation examples to support deploying on a Kubernetes cluster using Helm or ArgoCD. For more details, please refer to the deployment documentation.

Workflow runsโ€‹

We have increased the number of workflow runs ingested for any given workflow in a repository. The new integration now fetches up to 100 of the latest workflow runs, up from the previous limit of 10 per repository.

Repository typeโ€‹

You can now specify the type of repositories (public, private, or all) from which to ingest data. All other data kinds that are associated with repositories (like pull requests, issues, etc.) will only be fetched from repositories that match this configuration.

repositoryType: 'all' # โœ… fetch pull requests from all repositories. can also be "private", "public", etc
resources:
- kind: pull-request
selector:
# ...

Kind mapping changesโ€‹

The data blueprints for GitHub have been updated to provide cleaner data structures and improved relationships between different software catalog entities. Understanding these changes is crucial for a smooth migration.

A key change is how we denote custom attributes. We now add a double underscore prefix (e.g., __repository) to attributes that Port adds to the raw API response from GitHub. This makes it clear which fields are part of the original data and which are enrichments from the integration.

Files & GitOpsโ€‹

Existing configuration (click to expand)
resources:
- kind: file
selector:
query: 'true'
files:
# Note that glob patterns are supported, so you can use wildcards to match multiple files
- path: '**/package.json'
# The `repos` key can be used to filter the repositories from which the files will be fetched
repos:
- "MyRepo" # โŒ changed
port:
entity:
mappings:
identifier: .file.path # โŒ Changed
title: .file.name
blueprint: '"manifest"'
properties:
project_name: .file.content.name
project_version: .file.content.version
license: .file.content.license

New configuration (click to expand)
resources:
- kind: file
selector:
query: 'true'
files:
# Note that glob patterns are supported, so you can use wildcards to match multiple files
- path: '**/package.json'
# The `repos` key can be used to filter the repositories and branch where files should be fetched
repos:
- name: MyRepo # โœ… new key:value pairs rather than a string.
branch: main # โœ… new optional branch name for each specified repository
- name: MyOtherRepo
port:
entity:
mappings:
identifier: .path
title: .name
blueprint: '"manifest"'
properties:
project_name: .content.name
project_version: .content.version
license: .content.license

Here are the key changes for file mappings:

  1. The repos selector is now a list of objects, where each object can specify the repository name and an optional branch. This provides more granular control over which files are fetched.
  2. File attributes are no longer nested under a file key. They are now at the top level of the data structure. For example, instead of .file.path, you should now use .path.
  3. The repo key has been renamed to repository when referencing the repository a file belongs to, for consistency with other data kinds.

Repository relationshipsโ€‹

Fetching related data for a repository, like teams and collaborators, is now managed through a unified include selector. This replaces the previous method of using separate boolean flags for each data type, offering a more consistent and streamlined approach.

Repository and teamsโ€‹

Existing configuration (click to expand)
resources:
- kind: repository
selector:
query: "true" # JQ boolean query. If evaluated to false - skip syncing the object.
teams: true # โŒ changed
port:
entity:
mappings:
identifier: .name
title: .name
blueprint: '"githubRepository"'
properties:
readme: file://README.md
url: .html_url
defaultBranch: .default_branch
relations:
githubTeams: "[.teams[].id | tostring]" # โŒ changed
New configuration (click to expand)
resources:
- kind: repository
selector:
query: "true"
include: ["teams"] # โœ… new
port:
entity:
mappings:
identifier: .name
title: .name
blueprint: '"githubRepository"'
properties:
readme: file://README.md
url: .html_url
defaultBranch: .default_branch
relations:
githubTeams: '[.__teams[].id | tostring]' # โœ… new

Repository and collaboratorsโ€‹

Existing configuration (click to expand)
resources:
- kind: repository
selector:
query: "true"
collaborators: true # โŒ changed
port:
entity:
mappings:
identifier: .name
title: .name
blueprint: '"githubRepository"'
properties:
readme: file://README.md
url: .html_url
defaultBranch: .default_branch
relations:
collaborators: "[.collaborators[].login]" # โŒ changed
New configuration (click to expand)
resources:
- kind: repository
selector:
query: "true"
include: ["collaborators"] # โœ… new
port:
entity:
mappings:
identifier: .name
title: .name
blueprint: '"githubRepository"'
properties:
readme: file://README.md
url: .html_url
defaultBranch: .default_branch
relations:
collaborators: '[.__collaborators[].login]' # โœ… new

Issuesโ€‹

We've introduced a new state selector. This allows you to filter which objects are ingested based on their state (e.g., open, closed).

Existing configuration (click to expand)
resources:
- kind: issue
selector:
query: ".pull_request == null" # JQ boolean query. If evaluated to false - skip syncing the object.
port:
entity:
mappings:
identifier: ".repo + (.id|tostring)"
title: ".title"
blueprint: '"githubIssue"'
properties:
creator: ".user.login"
assignees: "[.assignees[].login]"
labels: "[.labels[].name]"
status: ".state"
createdAt: ".created_at"
link: ".html_url"
relations:
repository: ".repo" # โŒ changed
New configuration (click to expand)
resources:
- kind: issue
selector:
query: ".pull_request == null" # JQ boolean query. If evaluated to false - skip syncing the object.
state: "closed" # โœ… new
port:
entity:
mappings:
identifier: ".__repository + (.id|tostring)"
title: ".title"
blueprint: '"githubIssue"'
properties:
creator: ".user.login"
assignees: "[.assignees[].login]"
labels: "[.labels[].name]"
status: ".state"
createdAt: ".created_at"
closedAt: ".closed_at"
updatedAt: ".updated_at"
description: ".body"
issueNumber: ".number"
link: ".html_url"
relations:
repository: ".__repository" # โœ… new, uses leading underscore to indicate custom enrichment.

Pull requestsโ€‹

We've introduced new selectors to give you more control over which pull requests are ingested. The states selector allows you to filter pull requests by their state (e.g., open, closed). Additionally, you can use maxResults to limit the number of closed pull requests fetched and since to fetch pull requests created within a specific time period (in days).

Existing configuration (click to expand)
resources:
- kind: pull-request
selector:
query: "true" # JQ boolean query. If evaluated to false - skip syncing the object.
port:
entity:
mappings:
identifier: ".head.repo.name + (.id|tostring)" # The Entity identifier will be the repository name + the pull request ID.
title: ".title"
blueprint: '"githubPullRequest"'
properties:
creator: ".user.login"
assignees: "[.assignees[].login]"
reviewers: "[.requested_reviewers[].login]"
status: ".status" # merged, closed, opened
closedAt: ".closed_at"
updatedAt: ".updated_at"
mergedAt: ".merged_at"
createdAt: ".created_at"
relations:
repository: .head.repo.name

New configuration (click to expand)
resources:
- kind: pull-request
selector:
query: "true" # JQ boolean query. If evaluated to false - skip syncing the object.
states: ["open"] # โœ… new
maxResults: 50 # โœ… new, limit closed PRs to 50 capped at 300
since: 60 # โœ… new, fetch closed PRs within 60 days capped at 90 days
port:
entity:
mappings:
identifier: ".head.repo.name + (.id|tostring)" # The Entity identifier will be the repository name + the pull request ID.
title: ".title"
blueprint: '"githubPullRequest"'
properties:
creator: ".user.login"
assignees: "[.assignees[].login]"
reviewers: "[.requested_reviewers[].login]"
status: ".state" # merged, closed, opened
closedAt: ".closed_at"
updatedAt: ".updated_at"
mergedAt: ".merged_at"
createdAt: ".created_at"
prNumber: ".id"
relations:
repository: .__repository # โœ… new, it is now obvious when an attribute is added to the raw API response by the integration.

Foldersโ€‹

For the folder kind, the folder.name attribute is no longer part of the response. Instead, you can easily derive the folder name from the folder.path using a JQ expression, as shown in the example below:

Existing configuration (click to expand)
resources:
- kind: folder
selector:
query: "true"
folders:
- path: apps/*
repos:
- backend-service # โŒ changed
port:
entity:
mappings:
identifier: ".folder.name" # โŒ changed
title: ".folder.name" # โŒ changed
blueprint: '"githubRepository"'
properties:
url: .repo.html_url + "/tree/" + .repo.default_branch + "/" + .folder.path # โŒ changed
readme: file://README.md

New configuration (click to expand)
resources:
- kind: folder
selector:
query: "true"
folders:
- path: apps/*
repos:
- name: backend-service # โœ… new, now has a 'name' key
branch: main # โœ… new, optional branch name
port:
entity:
mappings:
identifier: .folder.path | split('/') | last # โœ… new, derived using JQ
title: .folder.path | split('/') | last
blueprint: '"githubRepository"'
properties:
url: .__repository.html_url + "/tree/" + .__repository.default_branch + "/" + .folder.path # โœ… new, repository is a custom enrichment
readme: file://README.md

Teamsโ€‹

To improve performance when fetching team members, we now use GitHub's GraphQL API instead of the REST API.

This change has two main consequences:

  1. The ID for a team may differ depending on whether you are fetching its members. This is due to differences between GitHub's REST and GraphQL APIs.
  2. Team members are now located in a nodes subarray within the team object.
Existing configuration (click to expand)

- kind: team
selector:
query: 'true'
members: true # โœ… unchanged
port:
entity:
mappings:
identifier: .id | tostring
title: .name
blueprint: '"githubTeam"'
properties:
slug: .slug
description: .description
link: .url
relations:
team_member: '[.members[].login]' # โŒ changed
New configuration (click to expand)

- kind: team
selector:
query: 'true'
members: true # โœ… unchanged
port:
entity:
mappings:
identifier: .id # toString is not neccesary, graphql id is a string
title: .name
blueprint: '"githubTeam"'
properties:
slug: .slug
description: .description
link: .url
relations:
team_member: '[.members.nodes[].login]' # โœ… new, nodes subarray

Other changesโ€‹

dependabot-alertโ€‹

The dependabot-alert kind now supports a states selector. This allows you to specify an array of states (e.g., open, fixed) to control which alerts are ingested:

resources:
- kind: dependabot-alert
selector:
query: "true"
states: # โœ… new
- "open"
- "fixed"

code-scanning-alertsโ€‹

The code-scanning-alerts kind now supports a state selector. This allows you to specify a single state (e.g., open) to control which alerts are ingested:

resources:
- kind: code-scanning-alerts
selector:
query: "true"
state: open # โœ… new

Summary of key changesโ€‹

This section provides a high-level summary of the key breaking changes for mappings.

AreaOld ValueNew ValueNotes
AuthenticationGitHub App InstallationPAT or Self-Created GitHub AppThe integration can be authenticated using a Personal Access Token (PAT) or a self-created GitHub App.
WebhooksApp WebhookAutomatic Setup by IntegrationThe integration now manages its own webhooks for live events. This requires webhook permissions and liveEvents.baseUrl to be set.
Workflow Runs10 per repository100 per workflowThe number of ingested workflow runs has been increased.
Repository TypeN/ArepositoryType configurationA new top-level configuration is available to filter repositories by type (public, private, or all).
Repository Relationshipsteams: true, collaborators: trueinclude: "teams", include: "collaborators"The include selector replaces boolean flags for fetching related data. The fetched data is also now prefixed with __ (e.g., .__teams).
Pull RequestsN/Astates, maxResults, since selectorsNew selectors are available for more granular filtering.
File properties.file.path.pathAll file properties are now at the top level of the object, no longer nested under .file.
Repository reference.repo or .head.repo.name.__repositoryThe integration now consistently provides repository information under the __repository field for all relevant kinds.
Folder name.folder.name.folder.path | split('/') | lastThe folder name is no longer directly available and should be derived from the folder path using a JQ expression.